council.ts 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. import { EventContext, StoreContext, DatabaseManager } from '@dzlzv/hydra-common'
  2. import { 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. CouncilStageUpdate,
  35. CouncilStageAnnouncing,
  36. CouncilStage,
  37. ElectionProblem,
  38. Candidate,
  39. CouncilMember,
  40. ElectionRound,
  41. ElectedCouncil,
  42. CouncilStageElection,
  43. VariantNone,
  44. CastVote,
  45. CandidacyNoteMetadata,
  46. // Misc
  47. Membership,
  48. } from 'query-node/dist/model'
  49. import { Council, Referendum } from './generated/types'
  50. import { CouncilCandidacyNoteMetadata } from '@joystream/metadata-protobuf'
  51. import { isSet } from '@joystream/metadata-protobuf/utils'
  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?: ElectionProblem
  171. ): Promise<void> {
  172. const electedCouncil = await getCurrentElectedCouncil(store, true)
  173. if (!electedCouncil) {
  174. return
  175. }
  176. const councilStageUpdate = new CouncilStageUpdate({
  177. stage: councilStage,
  178. changedAt: new BN(blockNumber),
  179. electionProblem,
  180. electedCouncil,
  181. })
  182. await store.save<CouncilStageUpdate>(councilStageUpdate)
  183. }
  184. /*
  185. Concludes current election round and starts the next one.
  186. */
  187. async function startNextElectionRound(
  188. store: DatabaseManager,
  189. electedCouncil: ElectedCouncil,
  190. previousElectionRound?: ElectionRound
  191. ): Promise<ElectionRound> {
  192. // finish last election round
  193. const lastElectionRound = previousElectionRound || (await getCurrentElectionRound(store))
  194. lastElectionRound.isFinished = true
  195. lastElectionRound.nextElectedCouncil = electedCouncil
  196. // save last election
  197. await store.save<ElectionRound>(lastElectionRound)
  198. // create election round record
  199. const electionRound = new ElectionRound({
  200. cycleId: lastElectionRound.cycleId + 1,
  201. isFinished: false,
  202. castVotes: [],
  203. electedCouncil,
  204. candidates: [],
  205. })
  206. // save new election
  207. await store.save<ElectionRound>(electionRound)
  208. return electionRound
  209. }
  210. /*
  211. Converts successful council candidate records to council member records.
  212. */
  213. async function convertCandidatesToCouncilMembers(
  214. store: DatabaseManager,
  215. candidates: Candidate[],
  216. blockNumber: number
  217. ): Promise<CouncilMember[]> {
  218. const councilMembers = await candidates.reduce(async (councilMembersPromise, candidate) => {
  219. const councilMembers = await councilMembersPromise
  220. // cast to any needed because member is not eagerly loaded into candidate object
  221. const member = new Membership({ id: (candidate as any).memberId.toString() })
  222. const councilMember = new CouncilMember({
  223. // id: candidate.id // TODO: are ids needed?
  224. stakingAccountId: candidate.stakingAccountId,
  225. rewardAccountId: candidate.rewardAccountId,
  226. member,
  227. stake: candidate.stake,
  228. lastPaymentBlock: new BN(blockNumber),
  229. unpaidReward: new BN(0),
  230. accumulatedReward: new BN(0),
  231. })
  232. return [...councilMembers, councilMember]
  233. }, Promise.resolve([] as CouncilMember[]))
  234. return councilMembers
  235. }
  236. /////////////////// Council events /////////////////////////////////////////////
  237. /*
  238. The event is emitted when a new round of elections begins (can be caused by multiple reasons) and candidates can announce
  239. their candidacies.
  240. */
  241. export async function council_AnnouncingPeriodStarted({ event, store }: EventContext & StoreContext): Promise<void> {
  242. // common event processing
  243. const [] = new Council.AnnouncingPeriodStartedEvent(event).params
  244. const announcingPeriodStartedEvent = new AnnouncingPeriodStartedEvent({
  245. ...genericEventFields(event),
  246. })
  247. await store.save<AnnouncingPeriodStartedEvent>(announcingPeriodStartedEvent)
  248. // specific event processing
  249. const stage = new CouncilStageAnnouncing()
  250. stage.candidatesCount = new BN(0)
  251. await updateCouncilStage(store, stage, event.blockNumber)
  252. }
  253. /*
  254. The event is emitted when a candidacy announcment period has ended, but not enough members announced.
  255. */
  256. export async function council_NotEnoughCandidates({ event, store }: EventContext & StoreContext): Promise<void> {
  257. // common event processing
  258. const [] = new Council.NotEnoughCandidatesEvent(event).params
  259. const notEnoughCandidatesEvent = new NotEnoughCandidatesEvent({
  260. ...genericEventFields(event),
  261. })
  262. await store.save<NotEnoughCandidatesEvent>(notEnoughCandidatesEvent)
  263. // specific event processing
  264. const stage = new CouncilStageAnnouncing()
  265. stage.candidatesCount = new BN(0)
  266. await updateCouncilStage(store, stage, event.blockNumber, ElectionProblem.NOT_ENOUGH_CANDIDATES)
  267. }
  268. /*
  269. The event is emitted when a new round of elections begins (can be caused by multiple reasons).
  270. */
  271. export async function council_VotingPeriodStarted({ event, store }: EventContext & StoreContext): Promise<void> {
  272. // common event processing
  273. const [numOfCandidates] = new Council.VotingPeriodStartedEvent(event).params
  274. const votingPeriodStartedEvent = new VotingPeriodStartedEvent({
  275. ...genericEventFields(event),
  276. numOfCandidates,
  277. })
  278. await store.save<VotingPeriodStartedEvent>(votingPeriodStartedEvent)
  279. // specific event processing
  280. // add stage update record
  281. const stage = new CouncilStageElection()
  282. stage.candidatesCount = numOfCandidates
  283. await updateCouncilStage(store, stage, event.blockNumber)
  284. }
  285. /*
  286. The event is emitted when a member announces candidacy to the council.
  287. */
  288. export async function council_NewCandidate({ event, store }: EventContext & StoreContext): Promise<void> {
  289. // common event processing - init
  290. const [memberId, stakingAccount, rewardAccount, balance] = new Council.NewCandidateEvent(event).params
  291. const member = await getMembership(store, memberId.toString())
  292. // specific event processing
  293. // increase candidate count in stage update record
  294. const lastStageUpdate = await getCurrentStageUpdate(store)
  295. if (!isCouncilStageAnnouncing(lastStageUpdate.stage)) {
  296. throw new Error(`Unexpected council stage "${lastStageUpdate.stage.isTypeOf}"`)
  297. }
  298. lastStageUpdate.stage.candidatesCount = new BN(lastStageUpdate.stage.candidatesCount).add(new BN(1))
  299. await store.save<CouncilStageUpdate>(lastStageUpdate)
  300. const electionRound = await getCurrentElectionRound(store)
  301. // prepare note metadata record (empty until explicitily set via different extrinsic)
  302. const noteMetadata = new CandidacyNoteMetadata({
  303. bulletPoints: [],
  304. })
  305. await store.save<CandidacyNoteMetadata>(noteMetadata)
  306. // save candidate record
  307. const candidate = new Candidate({
  308. stakingAccountId: stakingAccount.toString(),
  309. rewardAccountId: rewardAccount.toString(),
  310. member,
  311. electionRound,
  312. stake: balance,
  313. stakeLocked: true,
  314. candidacyWithdrawn: false,
  315. votePower: new BN(0),
  316. noteMetadata,
  317. })
  318. await store.save<Candidate>(candidate)
  319. // common event processing - save
  320. const newCandidateEvent = new NewCandidateEvent({
  321. ...genericEventFields(event),
  322. candidate,
  323. stakingAccount: stakingAccount.toString(),
  324. rewardAccount: rewardAccount.toString(),
  325. balance,
  326. })
  327. await store.save<NewCandidateEvent>(newCandidateEvent)
  328. }
  329. /*
  330. The event is emitted when the new council is elected. Sufficient members were elected and there is no other problem.
  331. */
  332. export async function council_NewCouncilElected({ event, store }: EventContext & StoreContext): Promise<void> {
  333. // common event processing - init
  334. const [memberIds] = new Council.NewCouncilElectedEvent(event).params
  335. const members = await store.getMany(Membership, { where: { id: memberIds.map((item) => item.toString()) } })
  336. // specific event processing
  337. // mark old council as resinged
  338. const oldElectedCouncil = await getCurrentElectedCouncil(store, true)
  339. if (oldElectedCouncil) {
  340. oldElectedCouncil.isResigned = true
  341. await store.save<ElectedCouncil>(oldElectedCouncil)
  342. }
  343. // get election round and its candidates
  344. const electionRound = await getCurrentElectionRound(store)
  345. const candidates = await store.getMany(Candidate, { where: { electionRoundId: electionRound.id } })
  346. // create new council record
  347. const electedCouncil = new ElectedCouncil({
  348. councilMembers: await convertCandidatesToCouncilMembers(store, candidates, event.blockNumber),
  349. updates: [],
  350. electedAtBlock: event.blockNumber,
  351. councilElections: oldElectedCouncil?.nextCouncilElections || [],
  352. nextCouncilElections: [],
  353. isResigned: false,
  354. })
  355. await store.save<ElectedCouncil>(electedCouncil)
  356. // save new council members
  357. await Promise.all(
  358. (electedCouncil.councilMembers || []).map(async (councilMember) => {
  359. councilMember.electedInCouncil = electedCouncil
  360. await store.save<CouncilMember>(councilMember)
  361. })
  362. )
  363. // end the last election round and start new one
  364. await startNextElectionRound(store, electedCouncil, electionRound)
  365. // unset `isCouncilMember` sign for old council's members
  366. const oldElectedMembers = await store.getMany(Membership, { where: { isCouncilMember: true } })
  367. await Promise.all(
  368. oldElectedMembers.map(async (member) => {
  369. member.isCouncilMember = false
  370. await store.save<Membership>(member)
  371. })
  372. )
  373. // set `isCouncilMember` sign for new council's members
  374. await Promise.all(
  375. (electedCouncil.councilMembers || []).map(async (councilMember) => {
  376. const member = councilMember.member
  377. member.isCouncilMember = true
  378. await store.save<Membership>(member)
  379. })
  380. )
  381. // common event processing - save
  382. const newCouncilElectedEvent = new NewCouncilElectedEvent({
  383. ...genericEventFields(event),
  384. electedCouncil,
  385. })
  386. await store.save<NewCouncilElectedEvent>(newCouncilElectedEvent)
  387. }
  388. /*
  389. The event is emitted when the new council couldn't be elected because not enough candidates received some votes.
  390. This can be vaguely translated as the public not having enough interest in the candidates.
  391. */
  392. export async function council_NewCouncilNotElected({ event, store }: EventContext & StoreContext): Promise<void> {
  393. // common event processing
  394. const [] = new Council.NewCouncilNotElectedEvent(event).params
  395. const newCouncilNotElectedEvent = new NewCouncilNotElectedEvent({
  396. ...genericEventFields(event),
  397. })
  398. await store.save<NewCouncilNotElectedEvent>(newCouncilNotElectedEvent)
  399. // specific event processing
  400. // restart elections
  401. const electedCouncil = (await getCurrentElectedCouncil(store))!
  402. await startNextElectionRound(store, electedCouncil)
  403. }
  404. /*
  405. The event is emitted when the member is releasing it's candidacy stake that is no longer needed.
  406. */
  407. export async function council_CandidacyStakeRelease({ event, store }: EventContext & StoreContext): Promise<void> {
  408. // common event processing
  409. const [memberId] = new Council.CandidacyStakeReleaseEvent(event).params
  410. const member = await getMembership(store, memberId.toString())
  411. const candidate = await getCandidate(store, memberId.toString()) // get last member's candidacy record
  412. const candidacyStakeReleaseEvent = new CandidacyStakeReleaseEvent({
  413. ...genericEventFields(event),
  414. candidate,
  415. })
  416. await store.save<CandidacyStakeReleaseEvent>(candidacyStakeReleaseEvent)
  417. // specific event processing
  418. // update candidate info about stake lock
  419. candidate.stakeLocked = false
  420. await store.save<Candidate>(candidate)
  421. }
  422. /*
  423. The event is emitted when the member is revoking its candidacy during a candidacy announcement stage.
  424. */
  425. export async function council_CandidacyWithdraw({ event, store }: EventContext & StoreContext): Promise<void> {
  426. // common event processing
  427. const [memberId] = new Council.CandidacyWithdrawEvent(event).params
  428. const member = await getMembership(store, memberId.toString())
  429. const candidacyWithdrawEvent = new CandidacyWithdrawEvent({
  430. ...genericEventFields(event),
  431. member,
  432. })
  433. await store.save<CandidacyWithdrawEvent>(candidacyWithdrawEvent)
  434. // specific event processing
  435. // mark candidacy as withdrawn
  436. const electionRound = await getCurrentElectionRound(store)
  437. const candidate = await getCandidate(store, memberId.toString(), electionRound)
  438. candidate.candidacyWithdrawn = true
  439. await store.save<Candidate>(candidate)
  440. }
  441. /*
  442. The event is emitted when the candidate changes its candidacy note.
  443. */
  444. export async function council_CandidacyNoteSet({ event, store }: EventContext & StoreContext): Promise<void> {
  445. // common event processing
  446. const [memberId, note] = new Council.CandidacyNoteSetEvent(event).params
  447. const member = await getMembership(store, memberId.toString())
  448. // load candidate recored
  449. const electionRound = await getCurrentElectionRound(store)
  450. const candidate = await getCandidate(store, memberId.toString(), electionRound)
  451. // unpack note's metadata and save it to db
  452. const metadata = deserializeMetadata(CouncilCandidacyNoteMetadata, note)
  453. const noteMetadata = candidate.noteMetadata
  454. // `XXX || (null as any)` construct clears metadata if requested (see https://github.com/Joystream/hydra/issues/435)
  455. noteMetadata.header = isSet(metadata?.header) ? metadata?.header || (null as any) : metadata?.header
  456. noteMetadata.bulletPoints = metadata?.bulletPoints || []
  457. noteMetadata.bannerImageUri = isSet(metadata?.bannerImageUri)
  458. ? metadata?.bannerImageUri || (null as any)
  459. : metadata?.bannerImageUri
  460. noteMetadata.description = isSet(metadata?.description)
  461. ? metadata?.description || (null as any)
  462. : metadata?.description
  463. await store.save<CandidacyNoteMetadata>(noteMetadata)
  464. const candidacyNoteSetEvent = new CandidacyNoteSetEvent({
  465. ...genericEventFields(event),
  466. member,
  467. header: metadata?.header || undefined,
  468. bulletPoints: metadata?.bulletPoints || [],
  469. bannerImageUri: metadata?.bannerImageUri || undefined,
  470. description: metadata?.description || undefined,
  471. })
  472. await store.save<CandidacyNoteSetEvent>(candidacyNoteSetEvent)
  473. // no specific event processing
  474. }
  475. /*
  476. The event is emitted when the council member receives its reward.
  477. */
  478. export async function council_RewardPayment({ event, store }: EventContext & StoreContext): Promise<void> {
  479. // common event processing
  480. const [memberId, rewardAccount, paidBalance, missingBalance] = new Council.RewardPaymentEvent(event).params
  481. const member = await getMembership(store, memberId.toString())
  482. const rewardPaymentEvent = new RewardPaymentEvent({
  483. ...genericEventFields(event),
  484. member,
  485. rewardAccount: rewardAccount.toString(),
  486. paidBalance,
  487. missingBalance,
  488. })
  489. await store.save<RewardPaymentEvent>(rewardPaymentEvent)
  490. // specific event processing
  491. // update (un)paid reward info
  492. const councilMember = await getCouncilMember(store, memberId.toString())
  493. councilMember.accumulatedReward = councilMember.accumulatedReward.add(paidBalance)
  494. councilMember.unpaidReward = missingBalance
  495. councilMember.lastPaymentBlock = new BN(event.blockNumber)
  496. await store.save<CouncilMember>(councilMember)
  497. }
  498. /*
  499. The event is emitted when a new budget balance is set.
  500. */
  501. export async function council_BudgetBalanceSet({ event, store }: EventContext & StoreContext): Promise<void> {
  502. // common event processing
  503. const [balance] = new Council.BudgetBalanceSetEvent(event).params
  504. const budgetBalanceSetEvent = new BudgetBalanceSetEvent({
  505. ...genericEventFields(event),
  506. balance,
  507. })
  508. await store.save<BudgetBalanceSetEvent>(budgetBalanceSetEvent)
  509. // no specific event processing
  510. }
  511. /*
  512. The event is emitted when a planned budget refill occurs.
  513. */
  514. export async function council_BudgetRefill({ event, store }: EventContext & StoreContext): Promise<void> {
  515. const [balance] = new Council.BudgetRefillEvent(event).params
  516. const budgetRefillEvent = new BudgetRefillEvent({
  517. ...genericEventFields(event),
  518. balance,
  519. })
  520. await store.save<BudgetRefillEvent>(budgetRefillEvent)
  521. // no specific event processing
  522. }
  523. /*
  524. The event is emitted when a new budget refill is planned.
  525. */
  526. export async function council_BudgetRefillPlanned({ event, store }: EventContext & StoreContext): Promise<void> {
  527. // common event processing
  528. const [nextRefillInBlock] = new Council.BudgetRefillPlannedEvent(event).params
  529. const budgetRefillPlannedEvent = new BudgetRefillPlannedEvent({
  530. ...genericEventFields(event),
  531. nextRefillInBlock: nextRefillInBlock.toNumber(),
  532. })
  533. await store.save<BudgetRefillPlannedEvent>(budgetRefillPlannedEvent)
  534. // no specific event processing
  535. }
  536. /*
  537. The event is emitted when a regular budget increment amount is updated.
  538. */
  539. export async function council_BudgetIncrementUpdated({ event, store }: EventContext & StoreContext): Promise<void> {
  540. // common event processing
  541. const [amount] = new Council.BudgetIncrementUpdatedEvent(event).params
  542. const budgetIncrementUpdatedEvent = new BudgetIncrementUpdatedEvent({
  543. ...genericEventFields(event),
  544. amount,
  545. })
  546. await store.save<BudgetIncrementUpdatedEvent>(budgetIncrementUpdatedEvent)
  547. // no specific event processing
  548. }
  549. /*
  550. The event is emitted when the reward amount for council members is updated.
  551. */
  552. export async function council_CouncilorRewardUpdated({ event, store }: EventContext & StoreContext): Promise<void> {
  553. // common event processing
  554. const [rewardAmount] = new Council.CouncilorRewardUpdatedEvent(event).params
  555. const councilorRewardUpdatedEvent = new CouncilorRewardUpdatedEvent({
  556. ...genericEventFields(event),
  557. rewardAmount,
  558. })
  559. await store.save<CouncilorRewardUpdatedEvent>(councilorRewardUpdatedEvent)
  560. // no specific event processing
  561. }
  562. /*
  563. The event is emitted when funds are transfered from the council budget to an account.
  564. */
  565. export async function council_RequestFunded({ event, store }: EventContext & StoreContext): Promise<void> {
  566. // common event processing
  567. const [account, amount] = new Council.RequestFundedEvent(event).params
  568. const requestFundedEvent = new RequestFundedEvent({
  569. ...genericEventFields(event),
  570. account: account.toString(),
  571. amount,
  572. })
  573. await store.save<RequestFundedEvent>(requestFundedEvent)
  574. // no specific event processing
  575. }
  576. /////////////////// Referendum events //////////////////////////////////////////
  577. /*
  578. The event is emitted when the voting stage of elections starts.
  579. */
  580. export async function referendum_ReferendumStarted({ event, store }: EventContext & StoreContext): Promise<void> {
  581. // common event processing
  582. const [winningTargetCount] = new Referendum.ReferendumStartedEvent(event).params
  583. const referendumStartedEvent = new ReferendumStartedEvent({
  584. ...genericEventFields(event),
  585. winningTargetCount,
  586. })
  587. await store.save<ReferendumStartedEvent>(referendumStartedEvent)
  588. // no specific event processing
  589. }
  590. /*
  591. The event is emitted when the voting stage of elections starts (in a fail-safe way).
  592. */
  593. export async function referendum_ReferendumStartedForcefully({
  594. event,
  595. store,
  596. }: EventContext & StoreContext): Promise<void> {
  597. // common event processing
  598. const [winningTargetCount] = new Referendum.ReferendumStartedForcefullyEvent(event).params
  599. const referendumStartedForcefullyEvent = new ReferendumStartedForcefullyEvent({
  600. ...genericEventFields(event),
  601. winningTargetCount,
  602. })
  603. await store.save<ReferendumStartedForcefullyEvent>(referendumStartedForcefullyEvent)
  604. // no specific event processing
  605. }
  606. /*
  607. The event is emitted when the vote revealing stage of elections starts.
  608. */
  609. export async function referendum_RevealingStageStarted({ event, store }: EventContext & StoreContext): Promise<void> {
  610. // common event processing
  611. const [] = new Referendum.RevealingStageStartedEvent(event).params
  612. const revealingStageStartedEvent = new RevealingStageStartedEvent({
  613. ...genericEventFields(event),
  614. })
  615. await store.save<RevealingStageStartedEvent>(revealingStageStartedEvent)
  616. // no specific event processing
  617. }
  618. /*
  619. The event is emitted when referendum finished and all revealed votes were counted.
  620. */
  621. export async function referendum_ReferendumFinished({ event, store }: EventContext & StoreContext): Promise<void> {
  622. // common event processing
  623. const [optionResultsRaw] = new Referendum.ReferendumFinishedEvent(event).params
  624. const members = await store.getMany(Membership, {
  625. where: { id: optionResultsRaw.map((item) => item.option_id.toString()) },
  626. })
  627. const referendumFinishedEvent = new ReferendumFinishedEvent({
  628. ...genericEventFields(event),
  629. optionResults: optionResultsRaw.map(
  630. (item, index) =>
  631. new ReferendumStageRevealingOptionResult({
  632. votePower: item.vote_power,
  633. option: members.find((tmpMember) => tmpMember.id.toString() == optionResultsRaw[index].option_id.toString()),
  634. })
  635. ),
  636. })
  637. await store.save<ReferendumFinishedEvent>(referendumFinishedEvent)
  638. // no specific event processing
  639. }
  640. /*
  641. The event is emitted when a vote is casted in the council election.
  642. */
  643. export async function referendum_VoteCast({ event, store }: EventContext & StoreContext): Promise<void> {
  644. // common event processing - init
  645. const [account, hash, stake] = new Referendum.VoteCastEvent(event).params
  646. const votePower = calculateVotePower(account.toString(), stake)
  647. // specific event processing
  648. const electionRound = await getCurrentElectionRound(store)
  649. const castVote = new CastVote({
  650. commitment: hash.toString(),
  651. electionRound,
  652. stake,
  653. stakeLocked: true,
  654. castBy: account.toString(),
  655. votePower: votePower,
  656. })
  657. await store.save<CastVote>(castVote)
  658. // common event processing - save
  659. const voteCastEvent = new VoteCastEvent({
  660. ...genericEventFields(event),
  661. castVote,
  662. })
  663. await store.save<VoteCastEvent>(voteCastEvent)
  664. }
  665. /*
  666. The event is emitted when a previously casted vote is revealed.
  667. */
  668. export async function referendum_VoteRevealed({ event, store }: EventContext & StoreContext): Promise<void> {
  669. // common event processing - init
  670. const [account, memberId, salt] = new Referendum.VoteRevealedEvent(event).params
  671. const member = await getMembership(store, memberId.toString())
  672. // specific event processing
  673. // read vote info
  674. const electionRound = await getCurrentElectionRound(store)
  675. const castVote = await getAccountCastVote(store, account.toString(), electionRound)
  676. // update cast vote's voteFor info
  677. castVote.voteFor = member
  678. await store.save<CastVote>(castVote)
  679. const candidate = await getCandidate(store, memberId.toString(), electionRound)
  680. candidate.votePower = candidate.votePower.add(castVote.votePower)
  681. await store.save<Candidate>(candidate)
  682. // common event processing - save
  683. const voteRevealedEvent = new VoteRevealedEvent({
  684. ...genericEventFields(event),
  685. castVote,
  686. })
  687. await store.save<VoteRevealedEvent>(voteRevealedEvent)
  688. }
  689. /*
  690. The event is emitted when a vote's stake is released.
  691. */
  692. export async function referendum_StakeReleased({ event, store }: EventContext & StoreContext): Promise<void> {
  693. // common event processing
  694. const [stakingAccount] = new Referendum.StakeReleasedEvent(event).params
  695. const stakeReleasedEvent = new StakeReleasedEvent({
  696. ...genericEventFields(event),
  697. stakingAccount: stakingAccount.toString(),
  698. })
  699. await store.save<StakeReleasedEvent>(stakeReleasedEvent)
  700. // specific event processing
  701. const electionRound = await getCurrentElectionRound(store)
  702. const castVote = await getAccountCastVote(store, stakingAccount.toString())
  703. castVote.stakeLocked = false
  704. await store.save<CastVote>(castVote)
  705. }