generator.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import fs from "fs";
  2. const exec = require("util").promisify(require("child_process").exec);
  3. import { ApiPromise } from "@polkadot/api";
  4. import { connectApi, getHead, getCouncils, getCouncilRound } from "./lib/api";
  5. import { StatisticsCollector } from "./tokenomics";
  6. import { generateReportData } from "./council";
  7. import { Config } from "./types/tokenomics";
  8. // types
  9. import { Round } from "./lib/types";
  10. import { types as joyTypes } from "@joystream/types";
  11. import { Hash, Moment } from "@polkadot/types/interfaces";
  12. import {
  13. BlockRange,
  14. CouncilMemberInfo,
  15. CouncilRoundInfo,
  16. ProposalFailedReason,
  17. ProposalInfo,
  18. ProposalStatus,
  19. ProposalType,
  20. ReportData,
  21. } from "./types/council";
  22. import { Seats } from "@joystream/types/council";
  23. import { MemberId, Membership } from "@joystream/types/members";
  24. import { StorageKey, u32, U32, Vec } from "@polkadot/types";
  25. import { Mint, MintId } from "@joystream/types/mint";
  26. import { ProposalDetailsOf, ProposalOf } from "@joystream/types/augment/types";
  27. const CONFIG: Config = {
  28. repoDir: __dirname + "/../../../../",
  29. reportsDir: "council/tokenomics",
  30. spendingCategoriesFile: "governance/spending_proposal_categories.csv",
  31. councilTemplate: __dirname + "/../templates/council.md",
  32. tokenomicsTemplate: __dirname + "/../templates/tokenomics.md",
  33. providerUrl: "ws://127.0.0.1:9944",
  34. proposalUrl: "https://testnet.joystream.org/#/proposals/",
  35. statusUrl: "https://status.joystream.org/status/",
  36. burnAddress: "5D5PhZQNJzcJXVBxwJxZcsutjKPqUPydrvpu6HeiBfMaeKQu",
  37. cacheDir: "cache",
  38. councilRoundOffset: 2,
  39. videoClassId: 10,
  40. channelClassId: 1,
  41. };
  42. const councilReport = async (
  43. startBlock: number,
  44. endBlock: number,
  45. config: Config
  46. ) => {
  47. const { repoDir, councilTemplate, providerUrl } = config;
  48. const api: ApiPromise = await connectApi(providerUrl);
  49. await api.isReady;
  50. // council report
  51. const startHash: Hash = await api.rpc.chain.getBlockHash(startBlock);
  52. const endHash: Hash = await api.rpc.chain.getBlockHash(endBlock);
  53. const blockRange = new BlockRange(startBlock, startHash, endBlock, endHash);
  54. const data = await generateReportData(api, blockRange);
  55. const report = await generateCouncilReport(data, councilTemplate);
  56. const term = data.councilTerm;
  57. const version = startBlock < 717987 ? "antioch" : "sumer";
  58. const versionStr = version[0].toUpperCase() + version.slice(1);
  59. const filename = `council/reports/${version}-reports/${versionStr}_Council${term}_Report.md`;
  60. fs.writeFileSync(repoDir + filename, report);
  61. console.log(`-> Wrote ${filename}`);
  62. api.disconnect();
  63. };
  64. const generateCouncilReport = async (
  65. data: ReportData,
  66. templateFile: string
  67. ) => {
  68. try {
  69. let fileData = await fs.readFileSync(templateFile, "utf8");
  70. let entries = Object.entries(data);
  71. for (let entry of entries) {
  72. let regex = new RegExp("{" + entry[0] + "}", "g");
  73. fileData = fileData.replace(regex, entry[1].toString());
  74. }
  75. return fileData;
  76. } catch (e) {
  77. console.error(e);
  78. return "";
  79. }
  80. };
  81. const tokenomicsReport = async (
  82. startBlock: number,
  83. endBlock: number,
  84. config: Config
  85. ): Promise<boolean> => {
  86. const { tokenomicsTemplate, repoDir, reportsDir } = config;
  87. let fileData = fs.readFileSync(tokenomicsTemplate, "utf8");
  88. let statsCollecttor = new StatisticsCollector();
  89. console.log(`-> Collecting stats from ${startBlock} to ${endBlock}`);
  90. const stats = await statsCollecttor.getStats(startBlock, endBlock, config);
  91. if (!stats.dateStart) return false;
  92. const round = stats.councilRound || 1;
  93. const fileName = `Council_Round${round}_${startBlock}-${endBlock}_Tokenomics_Report.md`;
  94. // antioch was updated to sumer at 717987
  95. const version = startBlock < 717987 ? "antioch-3" : "sumer-4";
  96. const dir = `${repoDir}${reportsDir}/${version}`;
  97. console.log(`-> Writing report to ${fileName}`);
  98. for (const entry of Object.entries(stats)) {
  99. const regex = new RegExp("{" + entry[0] + "}", "g");
  100. fileData = fileData.replace(regex, entry[1].toString());
  101. }
  102. fs.writeFileSync(`${dir}/${fileName}`, fileData);
  103. return true;
  104. };
  105. const updateReports = async (config: Config, round?: number) => {
  106. const { providerUrl } = config;
  107. console.debug(`Connecting to ${providerUrl}`);
  108. const api: ApiPromise = await connectApi(providerUrl);
  109. await api.isReady;
  110. console.log(`-> Fetching councils`);
  111. const head = await getHead(api);
  112. getCouncils(api, +head).then(async (councils: Round[]) => {
  113. api.disconnect();
  114. if (round === null || isNaN(round)) {
  115. console.log(`-> Updating reports`);
  116. await Promise.all(
  117. councils.map(({ start, end }) => createReports(start, end, config))
  118. );
  119. } else {
  120. const council = councils.find((c) => c.round === round);
  121. if (!council) return console.warn(`Round ${round} not found:`, councils);
  122. console.log(
  123. `-> Updating round ${round} (${council.start}-${council.end})`
  124. );
  125. await createReports(council.start, council.end, config);
  126. }
  127. process.exit();
  128. });
  129. };
  130. const createReports = (
  131. startBlock: number,
  132. endBlock: number,
  133. config: Config
  134. ) => {
  135. councilReport(startBlock, endBlock, config);
  136. return tokenomicsReport(startBlock, endBlock, config);
  137. };
  138. const main = async (config: Config) => {
  139. const args = process.argv.slice(2);
  140. if (args.length < 2) return updateReports(config, Number(args[0]));
  141. const startBlock = Number(args[0]);
  142. const endBlock = Number(args[1]);
  143. if (isNaN(startBlock) || isNaN(endBlock) || startBlock >= endBlock) {
  144. console.error("Invalid block range.");
  145. process.exit(1);
  146. }
  147. createReports(startBlock, endBlock, config);
  148. };
  149. main(CONFIG);