council.ts 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  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 = new BN(numOfCandidates.toString()) // toString() is needed to duplicate BN
  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. votesRecieved: [],
  318. })
  319. await store.save<Candidate>(candidate)
  320. // common event processing - save
  321. const newCandidateEvent = new NewCandidateEvent({
  322. ...genericEventFields(event),
  323. candidate,
  324. stakingAccount: stakingAccount.toString(),
  325. rewardAccount: rewardAccount.toString(),
  326. balance,
  327. })
  328. await store.save<NewCandidateEvent>(newCandidateEvent)
  329. }
  330. /*
  331. The event is emitted when the new council is elected. Sufficient members were elected and there is no other problem.
  332. */
  333. export async function council_NewCouncilElected({ event, store }: EventContext & StoreContext): Promise<void> {
  334. // common event processing - init
  335. const [memberIds] = new Council.NewCouncilElectedEvent(event).params
  336. const members = await store.getMany(Membership, { where: { id: memberIds.map((item) => item.toString()) } })
  337. // specific event processing
  338. // mark old council as resinged
  339. const oldElectedCouncil = await getCurrentElectedCouncil(store, true)
  340. if (oldElectedCouncil) {
  341. oldElectedCouncil.isResigned = true
  342. oldElectedCouncil.endedAtBlock = event.blockNumber
  343. await store.save<ElectedCouncil>(oldElectedCouncil)
  344. }
  345. // get election round and its candidates
  346. const electionRound = await getCurrentElectionRound(store)
  347. const candidates = await store.getMany(Candidate, { where: { electionRoundId: electionRound.id } })
  348. // create new council record
  349. const electedCouncil = new ElectedCouncil({
  350. councilMembers: await convertCandidatesToCouncilMembers(store, candidates, event.blockNumber),
  351. updates: [],
  352. electedAtBlock: event.blockNumber,
  353. councilElections: oldElectedCouncil?.nextCouncilElections || [],
  354. nextCouncilElections: [],
  355. isResigned: false,
  356. })
  357. await store.save<ElectedCouncil>(electedCouncil)
  358. // save new council members
  359. await Promise.all(
  360. (electedCouncil.councilMembers || []).map(async (councilMember) => {
  361. councilMember.electedInCouncil = electedCouncil
  362. await store.save<CouncilMember>(councilMember)
  363. })
  364. )
  365. // end the last election round and start new one
  366. await startNextElectionRound(store, electedCouncil, electionRound)
  367. // unset `isCouncilMember` sign for old council's members
  368. const oldElectedMembers = await store.getMany(Membership, { where: { isCouncilMember: true } })
  369. await Promise.all(
  370. oldElectedMembers.map(async (member) => {
  371. member.isCouncilMember = false
  372. await store.save<Membership>(member)
  373. })
  374. )
  375. // set `isCouncilMember` sign for new council's members
  376. await Promise.all(
  377. (electedCouncil.councilMembers || []).map(async (councilMember) => {
  378. const member = councilMember.member
  379. member.isCouncilMember = true
  380. await store.save<Membership>(member)
  381. })
  382. )
  383. // common event processing - save
  384. const newCouncilElectedEvent = new NewCouncilElectedEvent({
  385. ...genericEventFields(event),
  386. electedCouncil,
  387. })
  388. await store.save<NewCouncilElectedEvent>(newCouncilElectedEvent)
  389. }
  390. /*
  391. The event is emitted when the new council couldn't be elected because not enough candidates received some votes.
  392. This can be vaguely translated as the public not having enough interest in the candidates.
  393. */
  394. export async function council_NewCouncilNotElected({ event, store }: EventContext & StoreContext): Promise<void> {
  395. // common event processing
  396. const [] = new Council.NewCouncilNotElectedEvent(event).params
  397. const newCouncilNotElectedEvent = new NewCouncilNotElectedEvent({
  398. ...genericEventFields(event),
  399. })
  400. await store.save<NewCouncilNotElectedEvent>(newCouncilNotElectedEvent)
  401. // specific event processing
  402. // restart elections
  403. const electedCouncil = (await getCurrentElectedCouncil(store))!
  404. await startNextElectionRound(store, electedCouncil)
  405. }
  406. /*
  407. The event is emitted when the member is releasing it's candidacy stake that is no longer needed.
  408. */
  409. export async function council_CandidacyStakeRelease({ event, store }: EventContext & StoreContext): Promise<void> {
  410. // common event processing
  411. const [memberId] = new Council.CandidacyStakeReleaseEvent(event).params
  412. const candidate = await getCandidate(store, memberId.toString()) // get last member's candidacy record
  413. const candidacyStakeReleaseEvent = new CandidacyStakeReleaseEvent({
  414. ...genericEventFields(event),
  415. candidate,
  416. })
  417. await store.save<CandidacyStakeReleaseEvent>(candidacyStakeReleaseEvent)
  418. // specific event processing
  419. // update candidate info about stake lock
  420. candidate.stakeLocked = false
  421. await store.save<Candidate>(candidate)
  422. }
  423. /*
  424. The event is emitted when the member is revoking its candidacy during a candidacy announcement stage.
  425. */
  426. export async function council_CandidacyWithdraw({ event, store }: EventContext & StoreContext): Promise<void> {
  427. // common event processing
  428. const [memberId] = new Council.CandidacyWithdrawEvent(event).params
  429. const candidate = await getCandidate(store, memberId.toString())
  430. const candidacyWithdrawEvent = new CandidacyWithdrawEvent({
  431. ...genericEventFields(event),
  432. candidate,
  433. })
  434. await store.save<CandidacyWithdrawEvent>(candidacyWithdrawEvent)
  435. // specific event processing
  436. // mark candidacy as withdrawn
  437. const electionRound = await getCurrentElectionRound(store)
  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. // load candidate recored
  448. const electionRound = await getCurrentElectionRound(store)
  449. const candidate = await getCandidate(store, memberId.toString(), electionRound)
  450. // unpack note's metadata and save it to db
  451. const metadata = deserializeMetadata(CouncilCandidacyNoteMetadata, note)
  452. const noteMetadata = candidate.noteMetadata
  453. // `XXX || (null as any)` construct clears metadata if requested (see https://github.com/Joystream/hydra/issues/435)
  454. noteMetadata.header = isSet(metadata?.header) ? metadata?.header || (null as any) : metadata?.header
  455. noteMetadata.bulletPoints = metadata?.bulletPoints || []
  456. noteMetadata.bannerImageUri = isSet(metadata?.bannerImageUri)
  457. ? metadata?.bannerImageUri || (null as any)
  458. : metadata?.bannerImageUri
  459. noteMetadata.description = isSet(metadata?.description)
  460. ? metadata?.description || (null as any)
  461. : metadata?.description
  462. await store.save<CandidacyNoteMetadata>(noteMetadata)
  463. // save metadata set by this event
  464. const noteMetadataSnapshot = new CandidacyNoteMetadata({
  465. header: metadata?.header || undefined,
  466. bulletPoints: metadata?.bulletPoints || [],
  467. bannerImageUri: metadata?.bannerImageUri || undefined,
  468. description: metadata?.description || undefined,
  469. })
  470. await store.save<CandidacyNoteMetadata>(noteMetadataSnapshot)
  471. const candidacyNoteSetEvent = new CandidacyNoteSetEvent({
  472. ...genericEventFields(event),
  473. candidate,
  474. noteMetadata: noteMetadataSnapshot,
  475. })
  476. await store.save<CandidacyNoteSetEvent>(candidacyNoteSetEvent)
  477. // no specific event processing
  478. }
  479. /*
  480. The event is emitted when the council member receives its reward.
  481. */
  482. export async function council_RewardPayment({ event, store }: EventContext & StoreContext): Promise<void> {
  483. // common event processing
  484. const [memberId, rewardAccount, paidBalance, missingBalance] = new Council.RewardPaymentEvent(event).params
  485. const councilMember = await getCouncilMember(store, memberId.toString())
  486. const rewardPaymentEvent = new RewardPaymentEvent({
  487. ...genericEventFields(event),
  488. councilMember,
  489. rewardAccount: rewardAccount.toString(),
  490. paidBalance,
  491. missingBalance,
  492. })
  493. await store.save<RewardPaymentEvent>(rewardPaymentEvent)
  494. // specific event processing
  495. // update (un)paid reward info
  496. councilMember.accumulatedReward = councilMember.accumulatedReward.add(paidBalance)
  497. councilMember.unpaidReward = missingBalance
  498. councilMember.lastPaymentBlock = new BN(event.blockNumber)
  499. await store.save<CouncilMember>(councilMember)
  500. }
  501. /*
  502. The event is emitted when a new budget balance is set.
  503. */
  504. export async function council_BudgetBalanceSet({ event, store }: EventContext & StoreContext): Promise<void> {
  505. // common event processing
  506. const [balance] = new Council.BudgetBalanceSetEvent(event).params
  507. const budgetBalanceSetEvent = new BudgetBalanceSetEvent({
  508. ...genericEventFields(event),
  509. balance,
  510. })
  511. await store.save<BudgetBalanceSetEvent>(budgetBalanceSetEvent)
  512. // no specific event processing
  513. }
  514. /*
  515. The event is emitted when a planned budget refill occurs.
  516. */
  517. export async function council_BudgetRefill({ event, store }: EventContext & StoreContext): Promise<void> {
  518. const [balance] = new Council.BudgetRefillEvent(event).params
  519. const budgetRefillEvent = new BudgetRefillEvent({
  520. ...genericEventFields(event),
  521. balance,
  522. })
  523. await store.save<BudgetRefillEvent>(budgetRefillEvent)
  524. // no specific event processing
  525. }
  526. /*
  527. The event is emitted when a new budget refill is planned.
  528. */
  529. export async function council_BudgetRefillPlanned({ event, store }: EventContext & StoreContext): Promise<void> {
  530. // common event processing
  531. const [nextRefillInBlock] = new Council.BudgetRefillPlannedEvent(event).params
  532. const budgetRefillPlannedEvent = new BudgetRefillPlannedEvent({
  533. ...genericEventFields(event),
  534. nextRefillInBlock: nextRefillInBlock.toNumber(),
  535. })
  536. await store.save<BudgetRefillPlannedEvent>(budgetRefillPlannedEvent)
  537. // no specific event processing
  538. }
  539. /*
  540. The event is emitted when a regular budget increment amount is updated.
  541. */
  542. export async function council_BudgetIncrementUpdated({ event, store }: EventContext & StoreContext): Promise<void> {
  543. // common event processing
  544. const [amount] = new Council.BudgetIncrementUpdatedEvent(event).params
  545. const budgetIncrementUpdatedEvent = new BudgetIncrementUpdatedEvent({
  546. ...genericEventFields(event),
  547. amount,
  548. })
  549. await store.save<BudgetIncrementUpdatedEvent>(budgetIncrementUpdatedEvent)
  550. // no specific event processing
  551. }
  552. /*
  553. The event is emitted when the reward amount for council members is updated.
  554. */
  555. export async function council_CouncilorRewardUpdated({ event, store }: EventContext & StoreContext): Promise<void> {
  556. // common event processing
  557. const [rewardAmount] = new Council.CouncilorRewardUpdatedEvent(event).params
  558. const councilorRewardUpdatedEvent = new CouncilorRewardUpdatedEvent({
  559. ...genericEventFields(event),
  560. rewardAmount,
  561. })
  562. await store.save<CouncilorRewardUpdatedEvent>(councilorRewardUpdatedEvent)
  563. // no specific event processing
  564. }
  565. /*
  566. The event is emitted when funds are transfered from the council budget to an account.
  567. */
  568. export async function council_RequestFunded({ event, store }: EventContext & StoreContext): Promise<void> {
  569. // common event processing
  570. const [account, amount] = new Council.RequestFundedEvent(event).params
  571. const requestFundedEvent = new RequestFundedEvent({
  572. ...genericEventFields(event),
  573. account: account.toString(),
  574. amount,
  575. })
  576. await store.save<RequestFundedEvent>(requestFundedEvent)
  577. // no specific event processing
  578. }
  579. /////////////////// Referendum events //////////////////////////////////////////
  580. /*
  581. The event is emitted when the voting stage of elections starts.
  582. */
  583. export async function referendum_ReferendumStarted({ event, store }: EventContext & StoreContext): Promise<void> {
  584. // common event processing
  585. const [winningTargetCount] = new Referendum.ReferendumStartedEvent(event).params
  586. const referendumStartedEvent = new ReferendumStartedEvent({
  587. ...genericEventFields(event),
  588. winningTargetCount,
  589. })
  590. await store.save<ReferendumStartedEvent>(referendumStartedEvent)
  591. // no specific event processing
  592. }
  593. /*
  594. The event is emitted when the voting stage of elections starts (in a fail-safe way).
  595. */
  596. export async function referendum_ReferendumStartedForcefully({
  597. event,
  598. store,
  599. }: EventContext & StoreContext): Promise<void> {
  600. // common event processing
  601. const [winningTargetCount] = new Referendum.ReferendumStartedForcefullyEvent(event).params
  602. const referendumStartedForcefullyEvent = new ReferendumStartedForcefullyEvent({
  603. ...genericEventFields(event),
  604. winningTargetCount,
  605. })
  606. await store.save<ReferendumStartedForcefullyEvent>(referendumStartedForcefullyEvent)
  607. // no specific event processing
  608. }
  609. /*
  610. The event is emitted when the vote revealing stage of elections starts.
  611. */
  612. export async function referendum_RevealingStageStarted({ event, store }: EventContext & StoreContext): Promise<void> {
  613. // common event processing
  614. const [] = new Referendum.RevealingStageStartedEvent(event).params
  615. const revealingStageStartedEvent = new RevealingStageStartedEvent({
  616. ...genericEventFields(event),
  617. })
  618. await store.save<RevealingStageStartedEvent>(revealingStageStartedEvent)
  619. // no specific event processing
  620. }
  621. /*
  622. The event is emitted when referendum finished and all revealed votes were counted.
  623. */
  624. export async function referendum_ReferendumFinished({ event, store }: EventContext & StoreContext): Promise<void> {
  625. // common event processing
  626. const [optionResultsRaw] = new Referendum.ReferendumFinishedEvent(event).params
  627. const members = await store.getMany(Membership, {
  628. where: { id: optionResultsRaw.map((item) => item.option_id.toString()) },
  629. })
  630. const referendumFinishedEvent = new ReferendumFinishedEvent({
  631. ...genericEventFields(event),
  632. optionResults: optionResultsRaw.map(
  633. (item, index) =>
  634. new ReferendumStageRevealingOptionResult({
  635. votePower: item.vote_power,
  636. option: members.find((tmpMember) => tmpMember.id.toString() == optionResultsRaw[index].option_id.toString()),
  637. })
  638. ),
  639. })
  640. await store.save<ReferendumFinishedEvent>(referendumFinishedEvent)
  641. // no specific event processing
  642. }
  643. /*
  644. The event is emitted when a vote is casted in the council election.
  645. */
  646. export async function referendum_VoteCast({ event, store }: EventContext & StoreContext): Promise<void> {
  647. // common event processing - init
  648. const [account, hash, stake] = new Referendum.VoteCastEvent(event).params
  649. const votePower = calculateVotePower(account.toString(), stake)
  650. // specific event processing
  651. const electionRound = await getCurrentElectionRound(store)
  652. const castVote = new CastVote({
  653. commitment: hash.toString(),
  654. electionRound,
  655. stake,
  656. stakeLocked: true,
  657. castBy: account.toString(),
  658. votePower: votePower,
  659. })
  660. await store.save<CastVote>(castVote)
  661. // common event processing - save
  662. const voteCastEvent = new VoteCastEvent({
  663. ...genericEventFields(event),
  664. castVote,
  665. })
  666. await store.save<VoteCastEvent>(voteCastEvent)
  667. }
  668. /*
  669. The event is emitted when a previously casted vote is revealed.
  670. */
  671. export async function referendum_VoteRevealed({ event, store }: EventContext & StoreContext): Promise<void> {
  672. // common event processing - init
  673. const [account, memberId, salt] = new Referendum.VoteRevealedEvent(event).params
  674. const member = await getMembership(store, memberId.toString())
  675. // specific event processing
  676. // read vote info
  677. const electionRound = await getCurrentElectionRound(store)
  678. const candidate = await getCandidate(store, memberId.toString(), electionRound)
  679. const castVote = await getAccountCastVote(store, account.toString(), electionRound)
  680. // update cast vote's voteFor info
  681. castVote.voteFor = candidate
  682. await store.save<CastVote>(castVote)
  683. candidate.votePower = candidate.votePower.add(castVote.votePower)
  684. await store.save<Candidate>(candidate)
  685. // common event processing - save
  686. const voteRevealedEvent = new VoteRevealedEvent({
  687. ...genericEventFields(event),
  688. castVote,
  689. })
  690. await store.save<VoteRevealedEvent>(voteRevealedEvent)
  691. }
  692. /*
  693. The event is emitted when a vote's stake is released.
  694. */
  695. export async function referendum_StakeReleased({ event, store }: EventContext & StoreContext): Promise<void> {
  696. // common event processing
  697. const [stakingAccount] = new Referendum.StakeReleasedEvent(event).params
  698. const stakeReleasedEvent = new StakeReleasedEvent({
  699. ...genericEventFields(event),
  700. stakingAccount: stakingAccount.toString(),
  701. })
  702. await store.save<StakeReleasedEvent>(stakeReleasedEvent)
  703. // specific event processing
  704. const electionRound = await getCurrentElectionRound(store)
  705. const castVote = await getAccountCastVote(store, stakingAccount.toString())
  706. castVote.stakeLocked = false
  707. await store.save<CastVote>(castVote)
  708. }