rewards.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import { ApiPromise } from "@polkadot/api";
  2. // types
  3. import { Bounty, CacheEvent, WorkerReward, SpendingProposal } from "./types";
  4. import { AccountId, Balance } from "@polkadot/types/interfaces";
  5. import { Hash } from "@polkadot/types/interfaces";
  6. import { Membership } from "@joystream/types/members";
  7. import { Mint, MintId } from "@joystream/types/mint";
  8. import {
  9. Proposal,
  10. ProposalId,
  11. SpendingParams,
  12. } from "@joystream/types/proposals";
  13. import { ProposalDetails, ProposalOf } from "@joystream/types/augment/types";
  14. import { Stake } from "@joystream/types/stake";
  15. import {
  16. RewardRelationship,
  17. RewardRelationshipId,
  18. } from "@joystream/types/recurring-rewards";
  19. // lib
  20. import { getPercent, getTotalMinted } from "./";
  21. import {
  22. getBlock,
  23. getBlockHash,
  24. getMint,
  25. getNextWorker,
  26. getMember,
  27. getWorker,
  28. getWorkerReward,
  29. getProposalInfo,
  30. getProposalDetails,
  31. getStake,
  32. getValidators,
  33. getValidatorCount,
  34. } from "./api";
  35. import { WorkerOf } from "@joystream/types/augment-codec/all";
  36. import { ProposalDetailsOf } from "@joystream/types/augment/types";
  37. export const filterMethods = {
  38. getBurnedTokens: ({ section, method }: CacheEvent) =>
  39. section === "balances" && method === "Transfer",
  40. newValidatorsRewards: ({ section, method }: CacheEvent) =>
  41. section === "staking" && method === "Reward",
  42. finalizedSpendingProposals: ({ section, method }: CacheEvent) =>
  43. section === "proposalsEngine" && method === "ProposalStatusUpdated",
  44. sudoSetBalance: ({ section, method }: CacheEvent) =>
  45. section === "balances" && method === "BalanceSet",
  46. proposalStatusUpdated: ({ section, method }: CacheEvent) =>
  47. section === "proposalsEngine" && method === "ProposalStatusUpdated",
  48. };
  49. export const getWorkerRewards = async (
  50. api: ApiPromise,
  51. group: string,
  52. hash: Hash
  53. ): Promise<WorkerReward[]> => {
  54. let workers = Array<WorkerReward>();
  55. const nextWorkerId = await getNextWorker(api, group, hash);
  56. for (let id = 0; id < nextWorkerId; ++id) {
  57. const worker: WorkerOf = await getWorker(api, group, hash, id);
  58. const account = worker.role_account_id as AccountId;
  59. const memberId = worker.member_id;
  60. const member: Membership = await getMember(api, memberId, hash);
  61. const handle = member ? String(member.handle) : account.toString();
  62. const status = worker.is_active ? `active` : `inactive`;
  63. let stake: Stake;
  64. let reward: RewardRelationship;
  65. if (worker.role_stake_profile.isSome) {
  66. const roleStakeProfile = worker.role_stake_profile.unwrap();
  67. stake = await getStake(api, roleStakeProfile.stake_id, hash);
  68. }
  69. if (worker.reward_relationship.isSome) {
  70. // TODO changing salaries are not reflected
  71. const rewardId: RewardRelationshipId =
  72. worker.reward_relationship.unwrap();
  73. reward = await getWorkerReward(api, hash, rewardId);
  74. }
  75. workers.push({ id, stake, reward, status, handle, account, memberId });
  76. }
  77. return workers;
  78. };
  79. export const getWorkerRow = (
  80. worker: WorkerReward,
  81. earnedStart: number
  82. ): string => {
  83. const mtjoy = (mtjoy: number): string => (mtjoy / 1000000).toFixed(1);
  84. const { id, memberId, account, handle, status, reward } = worker;
  85. const earnedEnd = Number(reward.total_reward_received.toBigInt());
  86. if (!earnedEnd) return ``;
  87. const totalEarned = mtjoy(earnedEnd);
  88. const earnedTerm = mtjoy(earnedEnd - earnedStart);
  89. const amount = Number(reward.amount_per_payout.toBigInt());
  90. const rewardPerBlock = (amount / Number(reward.payout_interval)).toFixed();
  91. const url = `https://pioneer.joystreamstats.live/#/members/${handle}`; // TODO
  92. return `| ${id} | [@${handle}](${url}) | ${status} | ${rewardPerBlock} | ${earnedTerm} | ${totalEarned} |\n`;
  93. };
  94. export const getBurnedTokens = (
  95. burnAddress: string,
  96. blocks: [number, CacheEvent[]][]
  97. ): number => {
  98. let tokensBurned = 0;
  99. blocks.forEach(([key, transfers]) =>
  100. transfers.forEach((transfer) => {
  101. let receiver = transfer.data[1] as AccountId;
  102. let amount = transfer.data[2] as Balance;
  103. if (receiver.toString() === burnAddress) tokensBurned += Number(amount);
  104. })
  105. );
  106. return tokensBurned;
  107. };
  108. export const getFinalizedSpendingProposals = async (
  109. api: ApiPromise,
  110. blocks: [number, CacheEvent[]][]
  111. ): Promise<SpendingProposal[]> => {
  112. const selectedEvents: CacheEvent[] = [];
  113. blocks.map(([key, proposalEvents]) =>
  114. proposalEvents.map((proposalEvent) => selectedEvents.push(proposalEvent))
  115. );
  116. let spendingProposals: SpendingProposal[] = [];
  117. for (const proposalEvent of selectedEvents) {
  118. let statusUpdateData = proposalEvent.data[1] as any;
  119. const finalizedAt = statusUpdateData.finalized.finalizedAt;
  120. if (!(statusUpdateData.finalized && finalizedAt)) continue;
  121. const proposalId = proposalEvent.data[0] as ProposalId;
  122. const proposalInfo: ProposalOf = await getProposalInfo(api, proposalId);
  123. if (!proposalInfo) continue;
  124. try {
  125. const finalizedData = proposalInfo.status.asFinalized;
  126. const details: ProposalDetailsOf = await getProposalDetails(
  127. api,
  128. proposalId
  129. );
  130. if (finalizedData.proposalStatus.isApproved && details.isSpending) {
  131. let approvedData = finalizedData.proposalStatus.asApproved;
  132. if (!approvedData.isExecuted) continue;
  133. if (
  134. !spendingProposals.some((proposal) => proposal.id === +proposalId)
  135. ) {
  136. const title = proposalInfo.title.toString();
  137. const amount = +details.asSpending[0];
  138. spendingProposals.push({ id: +proposalId, title, amount });
  139. }
  140. }
  141. } catch (e) {
  142. console.error(`Failed to fetch proposal info: ${e.message}`);
  143. continue;
  144. }
  145. }
  146. return spendingProposals;
  147. };
  148. export const getValidatorsRewards = (
  149. blocks: [number, CacheEvent[]][]
  150. ): number => {
  151. let newValidatorRewards = 0;
  152. blocks.forEach(([key, validatorRewards]) =>
  153. validatorRewards.forEach(
  154. (reward: CacheEvent) => (newValidatorRewards += Number(reward.data[1]))
  155. )
  156. );
  157. return newValidatorRewards;
  158. };
  159. export const getActiveValidators = async (
  160. api: ApiPromise,
  161. hash: Hash,
  162. searchPreviousBlocks: boolean = false
  163. ): Promise<AccountId[]> => {
  164. const block = await getBlock(api, hash);
  165. let currentBlockNr = block.block.header.number.toNumber();
  166. let activeValidators: AccountId[];
  167. do {
  168. const hash: Hash = await getBlockHash(api, currentBlockNr);
  169. const validators: AccountId[] = await getValidators(api, hash);
  170. if (validators.length) {
  171. let max = await getValidatorCount(api, hash);
  172. activeValidators = validators.slice(0, max);
  173. }
  174. if (searchPreviousBlocks) --currentBlockNr;
  175. else ++currentBlockNr;
  176. } while (!activeValidators);
  177. return activeValidators;
  178. };