council.ts 26 KB

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