council.ts 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. import { EventContext, StoreContext, DatabaseManager } from '@dzlzv/hydra-common'
  2. import { bytesToString, deserializeMetadata, genericEventFields } from './common'
  3. import BN from 'bn.js'
  4. import { FindConditions } from 'typeorm'
  5. import {
  6. // Council events
  7. AnnouncingPeriodStartedEvent,
  8. NotEnoughCandidatesEvent,
  9. VotingPeriodStartedEvent,
  10. NewCandidateEvent,
  11. NewCouncilElectedEvent,
  12. NewCouncilNotElectedEvent,
  13. CandidacyStakeReleaseEvent,
  14. CandidacyWithdrawEvent,
  15. CandidacyNoteSetEvent,
  16. RewardPaymentEvent,
  17. BudgetBalanceSetEvent,
  18. BudgetRefillEvent,
  19. BudgetRefillPlannedEvent,
  20. BudgetIncrementUpdatedEvent,
  21. CouncilorRewardUpdatedEvent,
  22. RequestFundedEvent,
  23. // Referendum events
  24. ReferendumStartedEvent,
  25. ReferendumStartedForcefullyEvent,
  26. RevealingStageStartedEvent,
  27. ReferendumFinishedEvent,
  28. VoteCastEvent,
  29. VoteRevealedEvent,
  30. StakeReleasedEvent,
  31. // Council & referendum structures
  32. ReferendumStageRevealingOptionResult,
  33. // Council & referendum schema types
  34. ElectionProblemNotEnoughCandidates,
  35. CouncilStageUpdate,
  36. CouncilStageAnnouncing,
  37. CouncilStage,
  38. ElectionProblem,
  39. Candidate,
  40. CouncilMember,
  41. ElectionRound,
  42. ElectedCouncil,
  43. CouncilStageElection,
  44. VariantNone,
  45. CastVote,
  46. CandidacyNoteMetadata,
  47. // Misc
  48. Membership,
  49. } from 'query-node/dist/model'
  50. import { Council, Referendum } from './generated/types'
  51. import { CouncilCandidacyNoteMetadata } from '@joystream/metadata-protobuf'
  52. /////////////////// Common - Gets //////////////////////////////////////////////
  53. /*
  54. Retrieves the member record by its id.
  55. */
  56. async function getMembership(store: DatabaseManager, memberId: string): Promise<Membership | undefined> {
  57. const member = await store.get(Membership, { where: { id: memberId } })
  58. if (!member) {
  59. throw new Error(`Membership not found. memberId '${memberId}'`)
  60. }
  61. return member
  62. }
  63. /*
  64. Retrieves the council candidate by its member id. Returns the last record for the member
  65. if the election round isn't explicitly set.
  66. */
  67. async function getCandidate(
  68. store: DatabaseManager,
  69. memberId: string,
  70. electionRound?: ElectionRound
  71. ): Promise<Candidate> {
  72. const where = { memberId: memberId } as FindConditions<Candidate>
  73. if (electionRound) {
  74. where.electionRound = electionRound
  75. }
  76. const candidate = await store.get(Candidate, { where, order: { createdAt: 'DESC' } })
  77. if (!candidate) {
  78. throw new Error(`Candidate not found. memberId '${memberId}' electionRound '${electionRound?.id}'`)
  79. }
  80. return candidate
  81. }
  82. /*
  83. Retrieves the member's last council member record.
  84. */
  85. async function getCouncilMember(store: DatabaseManager, memberId: string): Promise<CouncilMember> {
  86. const councilMember = await store.get(CouncilMember, {
  87. where: { memberId: memberId },
  88. order: { createdAt: 'DESC' },
  89. })
  90. if (!councilMember) {
  91. throw new Error(`Council member not found. memberId '${memberId}'`)
  92. }
  93. return councilMember
  94. }
  95. /*
  96. Returns the current election round record.
  97. */
  98. async function getCurrentElectionRound(store: DatabaseManager): Promise<ElectionRound> {
  99. const electionRound = await store.get(ElectionRound, { order: { cycleId: 'DESC' } })
  100. if (!electionRound) {
  101. throw new Error(`No election round found`)
  102. }
  103. return electionRound
  104. }
  105. /*
  106. Returns the last council stage update.
  107. */
  108. async function getCurrentStageUpdate(store: DatabaseManager): Promise<CouncilStageUpdate> {
  109. const stageUpdate = await store.get(CouncilStageUpdate, { order: { changedAt: 'DESC' } })
  110. if (!stageUpdate) {
  111. throw new Error('No stage update found.')
  112. }
  113. return stageUpdate
  114. }
  115. /*
  116. Returns current elected council record.
  117. */
  118. async function getCurrentElectedCouncil(
  119. store: DatabaseManager,
  120. canFail: boolean = false
  121. ): Promise<ElectedCouncil | undefined> {
  122. const electedCouncil = await store.get(ElectedCouncil, { order: { electedAtBlock: 'DESC' } })
  123. if (!electedCouncil && !canFail) {
  124. throw new Error('No council is elected.')
  125. }
  126. return electedCouncil
  127. }
  128. /*
  129. Returns the last vote cast in an election by the given account. Returns the last record for the account
  130. if the election round isn't explicitly set.
  131. */
  132. async function getAccountCastVote(
  133. store: DatabaseManager,
  134. account: string,
  135. electionRound?: ElectionRound
  136. ): Promise<CastVote> {
  137. const where = { castBy: account } as FindConditions<Candidate>
  138. if (electionRound) {
  139. where.electionRound = electionRound
  140. }
  141. const castVote = await store.get(CastVote, { where, order: { createdAt: 'DESC' } })
  142. if (!castVote) {
  143. throw new Error(
  144. `No vote cast by the given account in the curent election round. accountId '${account}', cycleId '${electionRound?.cycleId}'`
  145. )
  146. }
  147. return castVote
  148. }
  149. /*
  150. Vote power calculation should correspond to implementation of `referendum::Trait<ReferendumInstance>`
  151. in `runtime/src/lib.rs`.
  152. */
  153. function calculateVotePower(accountId: string, stake: BN): BN {
  154. return stake
  155. }
  156. /*
  157. Custom typeguard for council stage - announcing candidacy.
  158. */
  159. function isCouncilStageAnnouncing(councilStage: typeof CouncilStage): councilStage is CouncilStageAnnouncing {
  160. return councilStage.isTypeOf == 'CouncilStageAnnouncing'
  161. }
  162. /////////////////// Common /////////////////////////////////////////////////////
  163. /*
  164. Creates new council stage update record.
  165. */
  166. async function updateCouncilStage(
  167. store: DatabaseManager,
  168. councilStage: typeof CouncilStage,
  169. blockNumber: number,
  170. electionProblem?: typeof ElectionProblem
  171. ): Promise<void> {
  172. const councilStageUpdate = new CouncilStageUpdate({
  173. stage: councilStage,
  174. changedAt: new BN(blockNumber),
  175. electionProblem: electionProblem || new VariantNone(),
  176. })
  177. await store.save<CouncilStageUpdate>(councilStageUpdate)
  178. // update council record
  179. const electedCouncil = await getCurrentElectedCouncil(store, true)
  180. if (!electedCouncil) {
  181. return
  182. }
  183. // electedCouncil.updates.push(councilStageUpdate) // uncomment after solving https://github.com/Joystream/hydra/issues/462
  184. electedCouncil.updates = (electedCouncil.updates || []).concat([councilStageUpdate])
  185. await store.save<ElectedCouncil>(electedCouncil)
  186. }
  187. /*
  188. Concludes current election round and starts the next one.
  189. */
  190. async function startNextElectionRound(
  191. store: DatabaseManager,
  192. electedCouncil: ElectedCouncil,
  193. previousElectionRound?: ElectionRound
  194. ): Promise<ElectionRound> {
  195. // finish last election round
  196. const lastElectionRound = previousElectionRound || (await getCurrentElectionRound(store))
  197. lastElectionRound.isFinished = true
  198. lastElectionRound.nextElectedCouncil = electedCouncil
  199. // save last election
  200. await store.save<ElectionRound>(lastElectionRound)
  201. // create election round record
  202. const electionRound = new ElectionRound({
  203. cycleId: lastElectionRound.cycleId + 1,
  204. isFinished: false,
  205. castVotes: [],
  206. electedCouncil,
  207. candidates: [],
  208. })
  209. // save new election
  210. await store.save<ElectionRound>(electionRound)
  211. return electionRound
  212. }
  213. /*
  214. Converts successful council candidate records to council member records.
  215. */
  216. async function convertCandidatesToCouncilMembers(
  217. store: DatabaseManager,
  218. candidates: Candidate[],
  219. blockNumber: number
  220. ): Promise<CouncilMember[]> {
  221. const councilMembers = await candidates.reduce(async (councilMembersPromise, candidate) => {
  222. const councilMembers = await councilMembersPromise
  223. // cast to any needed because member is not eagerly loaded into candidate object
  224. const member = new Membership({ id: (candidate as any).memberId.toString() })
  225. const councilMember = new CouncilMember({
  226. // id: candidate.id // TODO: are ids needed?
  227. stakingAccountId: candidate.stakingAccountId,
  228. rewardAccountId: candidate.rewardAccountId,
  229. member,
  230. stake: candidate.stake,
  231. lastPaymentBlock: new BN(blockNumber),
  232. unpaidReward: new BN(0),
  233. accumulatedReward: new BN(0),
  234. })
  235. return [...councilMembers, councilMember]
  236. }, Promise.resolve([] as CouncilMember[]))
  237. return councilMembers
  238. }
  239. /////////////////// Council events /////////////////////////////////////////////
  240. /*
  241. The event is emitted when a new round of elections begins (can be caused by multiple reasons) and candidates can announce
  242. their candidacies.
  243. */
  244. export async function council_AnnouncingPeriodStarted({ event, store }: EventContext & StoreContext): Promise<void> {
  245. // common event processing
  246. const [] = new Council.AnnouncingPeriodStartedEvent(event).params
  247. const announcingPeriodStartedEvent = new AnnouncingPeriodStartedEvent({
  248. ...genericEventFields(event),
  249. })
  250. await store.save<AnnouncingPeriodStartedEvent>(announcingPeriodStartedEvent)
  251. // specific event processing
  252. const stage = new CouncilStageAnnouncing()
  253. stage.candidatesCount = new BN(0)
  254. await updateCouncilStage(store, stage, event.blockNumber)
  255. }
  256. /*
  257. The event is emitted when a candidacy announcment period has ended, but not enough members announced.
  258. */
  259. export async function council_NotEnoughCandidates({ event, store }: EventContext & StoreContext): Promise<void> {
  260. // common event processing
  261. const [] = new Council.NotEnoughCandidatesEvent(event).params
  262. const notEnoughCandidatesEvent = new NotEnoughCandidatesEvent({
  263. ...genericEventFields(event),
  264. })
  265. await store.save<NotEnoughCandidatesEvent>(notEnoughCandidatesEvent)
  266. // specific event processing
  267. const stage = new CouncilStageAnnouncing()
  268. stage.candidatesCount = new BN(0)
  269. await updateCouncilStage(store, stage, event.blockNumber, new ElectionProblemNotEnoughCandidates())
  270. }
  271. /*
  272. The event is emitted when a new round of elections begins (can be caused by multiple reasons).
  273. */
  274. export async function council_VotingPeriodStarted({ event, store }: EventContext & StoreContext): Promise<void> {
  275. // common event processing
  276. const [numOfCandidates] = new Council.VotingPeriodStartedEvent(event).params
  277. const votingPeriodStartedEvent = new VotingPeriodStartedEvent({
  278. ...genericEventFields(event),
  279. numOfCandidates,
  280. })
  281. await store.save<VotingPeriodStartedEvent>(votingPeriodStartedEvent)
  282. // specific event processing
  283. // add stage update record
  284. const stage = new CouncilStageElection()
  285. stage.candidatesCount = numOfCandidates
  286. await updateCouncilStage(store, stage, event.blockNumber)
  287. }
  288. /*
  289. The event is emitted when a member announces candidacy to the council.
  290. */
  291. export async function council_NewCandidate({ event, store }: EventContext & StoreContext): Promise<void> {
  292. // common event processing
  293. const [memberId, stakingAccount, rewardAccount, balance] = new Council.NewCandidateEvent(event).params
  294. const member = await getMembership(store, memberId.toString())
  295. const newCandidateEvent = new NewCandidateEvent({
  296. ...genericEventFields(event),
  297. member,
  298. stakingAccount: stakingAccount.toString(),
  299. rewardAccount: rewardAccount.toString(),
  300. balance,
  301. })
  302. await store.save<NewCandidateEvent>(newCandidateEvent)
  303. // specific event processing
  304. // increase candidate count in stage update record
  305. const lastStageUpdate = await getCurrentStageUpdate(store)
  306. if (!isCouncilStageAnnouncing(lastStageUpdate.stage)) {
  307. throw new Error(`Unexpected council stage "${lastStageUpdate.stage.isTypeOf}"`)
  308. }
  309. lastStageUpdate.stage.candidatesCount = new BN(lastStageUpdate.stage.candidatesCount).add(new BN(1))
  310. await store.save<CouncilStageUpdate>(lastStageUpdate)
  311. const electionRound = await getCurrentElectionRound(store)
  312. // prepare note metadata record (empty until explicitily set via different extrinsic)
  313. const noteMetadata = new CandidacyNoteMetadata({
  314. bulletPoints: [],
  315. })
  316. await store.save<CandidacyNoteMetadata>(noteMetadata)
  317. // save candidate record
  318. const candidate = new Candidate({
  319. stakingAccountId: stakingAccount.toString(),
  320. rewardAccountId: rewardAccount.toString(),
  321. member,
  322. electionRound,
  323. stake: balance,
  324. stakeLocked: true,
  325. candidacyWithdrawn: false,
  326. votePower: new BN(0),
  327. noteMetadata,
  328. })
  329. await store.save<Candidate>(candidate)
  330. }
  331. /*
  332. The event is emitted when the new council is elected. Sufficient members were elected and there is no other problem.
  333. */
  334. export async function council_NewCouncilElected({ event, store }: EventContext & StoreContext): Promise<void> {
  335. // common event processing
  336. const [memberIds] = new Council.NewCouncilElectedEvent(event).params
  337. const members = await store.getMany(Membership, { where: { id: memberIds.map((item) => item.toString()) } })
  338. const newCouncilElectedEvent = new NewCouncilElectedEvent({
  339. ...genericEventFields(event),
  340. electedMembers: members,
  341. })
  342. await store.save<NewCouncilElectedEvent>(newCouncilElectedEvent)
  343. // specific event processing
  344. // mark old council as resinged
  345. const oldElectedCouncil = await getCurrentElectedCouncil(store, true)
  346. if (oldElectedCouncil) {
  347. oldElectedCouncil.isResigned = true
  348. await store.save<ElectedCouncil>(oldElectedCouncil)
  349. }
  350. // get election round and its candidates
  351. const electionRound = await getCurrentElectionRound(store)
  352. const candidates = await store.getMany(Candidate, { where: { electionRoundId: electionRound.id } })
  353. // create new council record
  354. const electedCouncil = new ElectedCouncil({
  355. councilMembers: await convertCandidatesToCouncilMembers(store, candidates, event.blockNumber),
  356. updates: [],
  357. electedAtBlock: event.blockNumber,
  358. councilElections: oldElectedCouncil?.nextCouncilElections || [],
  359. nextCouncilElections: [],
  360. isResigned: false,
  361. })
  362. await store.save<ElectedCouncil>(electedCouncil)
  363. // save new council members
  364. await Promise.all(
  365. (electedCouncil.councilMembers || []).map(async (councilMember) => {
  366. councilMember.electedInCouncil = electedCouncil
  367. await store.save<CouncilMember>(councilMember)
  368. })
  369. )
  370. // end the last election round and start new one
  371. await startNextElectionRound(store, electedCouncil, electionRound)
  372. }
  373. /*
  374. The event is emitted when the new council couldn't be elected because not enough candidates received some votes.
  375. This can be vaguely translated as the public not having enough interest in the candidates.
  376. */
  377. export async function council_NewCouncilNotElected({ event, store }: EventContext & StoreContext): Promise<void> {
  378. // common event processing
  379. const [] = new Council.NewCouncilNotElectedEvent(event).params
  380. const newCouncilNotElectedEvent = new NewCouncilNotElectedEvent({
  381. ...genericEventFields(event),
  382. })
  383. await store.save<NewCouncilNotElectedEvent>(newCouncilNotElectedEvent)
  384. // specific event processing
  385. // restart elections
  386. const electedCouncil = (await getCurrentElectedCouncil(store))!
  387. await startNextElectionRound(store, electedCouncil)
  388. }
  389. /*
  390. The event is emitted when the member is releasing it's candidacy stake that is no longer needed.
  391. */
  392. export async function council_CandidacyStakeRelease({ event, store }: EventContext & StoreContext): Promise<void> {
  393. // common event processing
  394. const [memberId] = new Council.CandidacyStakeReleaseEvent(event).params
  395. const member = await getMembership(store, memberId.toString())
  396. const candidacyStakeReleaseEvent = new CandidacyStakeReleaseEvent({
  397. ...genericEventFields(event),
  398. member,
  399. })
  400. await store.save<CandidacyStakeReleaseEvent>(candidacyStakeReleaseEvent)
  401. // specific event processing
  402. // update candidate info about stake lock
  403. const candidate = await getCandidate(store, memberId.toString()) // get last member's candidacy record
  404. candidate.stakeLocked = false
  405. await store.save<Candidate>(candidate)
  406. }
  407. /*
  408. The event is emitted when the member is revoking its candidacy during a candidacy announcement stage.
  409. */
  410. export async function council_CandidacyWithdraw({ event, store }: EventContext & StoreContext): Promise<void> {
  411. // common event processing
  412. const [memberId] = new Council.CandidacyWithdrawEvent(event).params
  413. const member = await getMembership(store, memberId.toString())
  414. const candidacyWithdrawEvent = new CandidacyWithdrawEvent({
  415. ...genericEventFields(event),
  416. member,
  417. })
  418. await store.save<CandidacyWithdrawEvent>(candidacyWithdrawEvent)
  419. // specific event processing
  420. // mark candidacy as withdrawn
  421. const electionRound = await getCurrentElectionRound(store)
  422. const candidate = await getCandidate(store, memberId.toString(), electionRound)
  423. candidate.candidacyWithdrawn = true
  424. await store.save<Candidate>(candidate)
  425. }
  426. /*
  427. The event is emitted when the candidate changes its candidacy note.
  428. */
  429. export async function council_CandidacyNoteSet({ event, store }: EventContext & StoreContext): Promise<void> {
  430. // common event processing
  431. const [memberId, note] = new Council.CandidacyNoteSetEvent(event).params
  432. const member = await getMembership(store, memberId.toString())
  433. // load candidate recored
  434. const electionRound = await getCurrentElectionRound(store)
  435. const candidate = await getCandidate(store, memberId.toString(), electionRound)
  436. // unpack note's metadata and save it to db
  437. const metadata = deserializeMetadata(CouncilCandidacyNoteMetadata, note)
  438. const noteMetadata = candidate.noteMetadata
  439. noteMetadata.header = metadata?.header || undefined
  440. noteMetadata.bulletPoints = metadata?.bulletPoints || []
  441. noteMetadata.bannerImageUri = metadata?.bannerImageUri || undefined
  442. noteMetadata.description = metadata?.description || undefined
  443. await store.save<CandidacyNoteMetadata>(noteMetadata)
  444. const candidacyNoteSetEvent = new CandidacyNoteSetEvent({
  445. ...genericEventFields(event),
  446. member,
  447. noteMetadata,
  448. })
  449. await store.save<CandidacyNoteSetEvent>(candidacyNoteSetEvent)
  450. // no specific event processing
  451. }
  452. /*
  453. The event is emitted when the council member receives its reward.
  454. */
  455. export async function council_RewardPayment({ event, store }: EventContext & StoreContext): Promise<void> {
  456. // common event processing
  457. const [memberId, rewardAccount, paidBalance, missingBalance] = new Council.RewardPaymentEvent(event).params
  458. const member = await getMembership(store, memberId.toString())
  459. const rewardPaymentEvent = new RewardPaymentEvent({
  460. ...genericEventFields(event),
  461. member,
  462. rewardAccount: rewardAccount.toString(),
  463. paidBalance,
  464. missingBalance,
  465. })
  466. await store.save<RewardPaymentEvent>(rewardPaymentEvent)
  467. // specific event processing
  468. // update (un)paid reward info
  469. const councilMember = await getCouncilMember(store, memberId.toString())
  470. councilMember.accumulatedReward = councilMember.accumulatedReward.add(paidBalance)
  471. councilMember.unpaidReward = missingBalance
  472. councilMember.lastPaymentBlock = new BN(event.blockNumber)
  473. await store.save<CouncilMember>(councilMember)
  474. }
  475. /*
  476. The event is emitted when a new budget balance is set.
  477. */
  478. export async function council_BudgetBalanceSet({ event, store }: EventContext & StoreContext): Promise<void> {
  479. // common event processing
  480. const [balance] = new Council.BudgetBalanceSetEvent(event).params
  481. const budgetBalanceSetEvent = new BudgetBalanceSetEvent({
  482. ...genericEventFields(event),
  483. balance,
  484. })
  485. await store.save<BudgetBalanceSetEvent>(budgetBalanceSetEvent)
  486. // no specific event processing
  487. }
  488. /*
  489. The event is emitted when a planned budget refill occurs.
  490. */
  491. export async function council_BudgetRefill({ event, store }: EventContext & StoreContext): Promise<void> {
  492. const [balance] = new Council.BudgetRefillEvent(event).params
  493. const budgetRefillEvent = new BudgetRefillEvent({
  494. ...genericEventFields(event),
  495. balance,
  496. })
  497. await store.save<BudgetRefillEvent>(budgetRefillEvent)
  498. // no specific event processing
  499. }
  500. /*
  501. The event is emitted when a new budget refill is planned.
  502. */
  503. export async function council_BudgetRefillPlanned({ event, store }: EventContext & StoreContext): Promise<void> {
  504. // common event processing
  505. const [nextRefillInBlock] = new Council.BudgetRefillPlannedEvent(event).params
  506. const budgetRefillPlannedEvent = new BudgetRefillPlannedEvent({
  507. ...genericEventFields(event),
  508. nextRefillInBlock: nextRefillInBlock.toNumber(),
  509. })
  510. await store.save<BudgetRefillPlannedEvent>(budgetRefillPlannedEvent)
  511. // no specific event processing
  512. }
  513. /*
  514. The event is emitted when a regular budget increment amount is updated.
  515. */
  516. export async function council_BudgetIncrementUpdated({ event, store }: EventContext & StoreContext): Promise<void> {
  517. // common event processing
  518. const [amount] = new Council.BudgetIncrementUpdatedEvent(event).params
  519. const budgetIncrementUpdatedEvent = new BudgetIncrementUpdatedEvent({
  520. ...genericEventFields(event),
  521. amount,
  522. })
  523. await store.save<BudgetIncrementUpdatedEvent>(budgetIncrementUpdatedEvent)
  524. // no specific event processing
  525. }
  526. /*
  527. The event is emitted when the reward amount for council members is updated.
  528. */
  529. export async function council_CouncilorRewardUpdated({ event, store }: EventContext & StoreContext): Promise<void> {
  530. // common event processing
  531. const [rewardAmount] = new Council.CouncilorRewardUpdatedEvent(event).params
  532. const councilorRewardUpdatedEvent = new CouncilorRewardUpdatedEvent({
  533. ...genericEventFields(event),
  534. rewardAmount,
  535. })
  536. await store.save<CouncilorRewardUpdatedEvent>(councilorRewardUpdatedEvent)
  537. // no specific event processing
  538. }
  539. /*
  540. The event is emitted when funds are transfered from the council budget to an account.
  541. */
  542. export async function council_RequestFunded({ event, store }: EventContext & StoreContext): Promise<void> {
  543. // common event processing
  544. const [account, amount] = new Council.RequestFundedEvent(event).params
  545. const requestFundedEvent = new RequestFundedEvent({
  546. ...genericEventFields(event),
  547. account: account.toString(),
  548. amount,
  549. })
  550. await store.save<RequestFundedEvent>(requestFundedEvent)
  551. // no specific event processing
  552. }
  553. /////////////////// Referendum events //////////////////////////////////////////
  554. /*
  555. The event is emitted when the voting stage of elections starts.
  556. */
  557. export async function referendum_ReferendumStarted({ event, store }: EventContext & StoreContext): Promise<void> {
  558. // common event processing
  559. const [winningTargetCount] = new Referendum.ReferendumStartedEvent(event).params
  560. const referendumStartedEvent = new ReferendumStartedEvent({
  561. ...genericEventFields(event),
  562. winningTargetCount,
  563. })
  564. await store.save<ReferendumStartedEvent>(referendumStartedEvent)
  565. // no specific event processing
  566. }
  567. /*
  568. The event is emitted when the voting stage of elections starts (in a fail-safe way).
  569. */
  570. export async function referendum_ReferendumStartedForcefully({
  571. event,
  572. store,
  573. }: EventContext & StoreContext): Promise<void> {
  574. // common event processing
  575. const [winningTargetCount] = new Referendum.ReferendumStartedForcefullyEvent(event).params
  576. const referendumStartedForcefullyEvent = new ReferendumStartedForcefullyEvent({
  577. ...genericEventFields(event),
  578. winningTargetCount,
  579. })
  580. await store.save<ReferendumStartedForcefullyEvent>(referendumStartedForcefullyEvent)
  581. // no specific event processing
  582. }
  583. /*
  584. The event is emitted when the vote revealing stage of elections starts.
  585. */
  586. export async function referendum_RevealingStageStarted({ event, store }: EventContext & StoreContext): Promise<void> {
  587. // common event processing
  588. const [] = new Referendum.RevealingStageStartedEvent(event).params
  589. const revealingStageStartedEvent = new RevealingStageStartedEvent({
  590. ...genericEventFields(event),
  591. })
  592. await store.save<RevealingStageStartedEvent>(revealingStageStartedEvent)
  593. // no specific event processing
  594. }
  595. /*
  596. The event is emitted when referendum finished and all revealed votes were counted.
  597. */
  598. export async function referendum_ReferendumFinished({ event, store }: EventContext & StoreContext): Promise<void> {
  599. // common event processing
  600. const [optionResultsRaw] = new Referendum.ReferendumFinishedEvent(event).params
  601. const members = await store.getMany(Membership, {
  602. where: { id: optionResultsRaw.map((item) => item.option_id.toString()) },
  603. })
  604. const referendumFinishedEvent = new ReferendumFinishedEvent({
  605. ...genericEventFields(event),
  606. optionResults: optionResultsRaw.map(
  607. (item, index) =>
  608. new ReferendumStageRevealingOptionResult({
  609. votePower: item.vote_power,
  610. option: members[index],
  611. })
  612. ),
  613. })
  614. await store.save<ReferendumFinishedEvent>(referendumFinishedEvent)
  615. // no specific event processing
  616. }
  617. /*
  618. The event is emitted when a vote is casted in the council election.
  619. */
  620. export async function referendum_VoteCast({ event, store }: EventContext & StoreContext): Promise<void> {
  621. // common event processing
  622. const [account, hash, stake] = new Referendum.VoteCastEvent(event).params
  623. const votePower = calculateVotePower(account.toString(), stake)
  624. const hashString = bytesToString(hash)
  625. const voteCastEvent = new VoteCastEvent({
  626. ...genericEventFields(event),
  627. account: account.toString(),
  628. hash: hashString,
  629. votePower,
  630. })
  631. await store.save<VoteCastEvent>(voteCastEvent)
  632. // specific event processing
  633. const electionRound = await getCurrentElectionRound(store)
  634. const castVote = new CastVote({
  635. commitment: hashString,
  636. electionRound,
  637. stake,
  638. stakeLocked: true,
  639. castBy: account.toString(),
  640. votePower: votePower,
  641. })
  642. await store.save<CastVote>(castVote)
  643. }
  644. /*
  645. The event is emitted when a previously casted vote is revealed.
  646. */
  647. export async function referendum_VoteRevealed({ event, store }: EventContext & StoreContext): Promise<void> {
  648. // common event processing
  649. const [account, memberId, salt] = new Referendum.VoteRevealedEvent(event).params
  650. const member = await getMembership(store, memberId.toString())
  651. const voteRevealedEvent = new VoteRevealedEvent({
  652. ...genericEventFields(event),
  653. account: account.toString(),
  654. member: member,
  655. salt: bytesToString(salt),
  656. })
  657. await store.save<VoteRevealedEvent>(voteRevealedEvent)
  658. // specific event processing
  659. // read vote info
  660. const electionRound = await getCurrentElectionRound(store)
  661. const castVote = await getAccountCastVote(store, account.toString(), electionRound)
  662. // update cast vote's voteFor info
  663. castVote.voteFor = member
  664. await store.save<CastVote>(castVote)
  665. const candidate = await getCandidate(store, memberId.toString(), electionRound)
  666. candidate.votePower = candidate.votePower.add(castVote.votePower)
  667. await store.save<Candidate>(candidate)
  668. }
  669. /*
  670. The event is emitted when a vote's stake is released.
  671. */
  672. export async function referendum_StakeReleased({ event, store }: EventContext & StoreContext): Promise<void> {
  673. // common event processing
  674. const [stakingAccount] = new Referendum.StakeReleasedEvent(event).params
  675. const stakeReleasedEvent = new StakeReleasedEvent({
  676. ...genericEventFields(event),
  677. stakingAccount: stakingAccount.toString(),
  678. })
  679. await store.save<StakeReleasedEvent>(stakeReleasedEvent)
  680. // specific event processing
  681. const electionRound = await getCurrentElectionRound(store)
  682. const castVote = await getAccountCastVote(store, stakingAccount.toString())
  683. castVote.stakeLocked = false
  684. await store.save<CastVote>(castVote)
  685. }