generator.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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: "council/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 createDir = (dir: string) => {
  43. try {
  44. fs.statSync(dir);
  45. } catch (e) {
  46. fs.mkdirSync(dir, { recursive: true });
  47. }
  48. };
  49. const councilReport = async (
  50. startBlock: number,
  51. endBlock: number,
  52. config: Config
  53. ) => {
  54. const { repoDir, councilTemplate, providerUrl } = config;
  55. const api: ApiPromise = await connectApi(providerUrl);
  56. await api.isReady;
  57. // council report
  58. const startHash: Hash = await api.rpc.chain.getBlockHash(startBlock);
  59. const endHash: Hash = await api.rpc.chain.getBlockHash(endBlock);
  60. const blockRange = new BlockRange(startBlock, startHash, endBlock, endHash);
  61. const data = await generateReportData(api, blockRange);
  62. const report = await generateCouncilReport(data, councilTemplate);
  63. const term = data.councilTerm;
  64. let version = "giza";
  65. if (endBlock < 4191207) version = "sumer";
  66. if (startBlock < 717987) "antioch";
  67. const versionStr = version[0].toUpperCase() + version.slice(1);
  68. const dir = repoDir + `council/reports/` + version + `/`;
  69. const filename = `${versionStr}_Council${term}_Report.md`;
  70. createDir(dir);
  71. fs.writeFileSync(dir + filename, report);
  72. console.log(`-> Wrote ${dir + filename}`);
  73. api.disconnect();
  74. };
  75. const generateCouncilReport = async (
  76. data: ReportData,
  77. templateFile: string
  78. ) => {
  79. try {
  80. let fileData = await fs.readFileSync(templateFile, "utf8");
  81. let entries = Object.entries(data);
  82. for (let entry of entries) {
  83. let regex = new RegExp("{" + entry[0] + "}", "g");
  84. fileData = fileData.replace(regex, entry[1].toString());
  85. }
  86. return fileData;
  87. } catch (e) {
  88. console.error(e);
  89. return "";
  90. }
  91. };
  92. const tokenomicsReport = async (
  93. startBlock: number,
  94. endBlock: number,
  95. config: Config
  96. ): Promise<boolean> => {
  97. const { tokenomicsTemplate, repoDir, reportsDir } = config;
  98. let fileData = fs.readFileSync(tokenomicsTemplate, "utf8");
  99. let statsCollecttor = new StatisticsCollector();
  100. console.log(`-> Collecting stats from ${startBlock} to ${endBlock}`);
  101. const stats = await statsCollecttor.getStats(startBlock, endBlock, config);
  102. if (!stats.dateStart) return false;
  103. const round = stats.councilRound || 1;
  104. const fileName = `Council_Round${round}_${startBlock}-${endBlock}_Tokenomics_Report.md`;
  105. // antioch was updated to sumer at 717987
  106. const version =
  107. startBlock < 717987 ? "antioch-3" : endBlock > 4191207 ? "giza" : "sumer-4";
  108. const dir = `${repoDir}${reportsDir}/${version}`;
  109. createDir(dir);
  110. console.log(`-> Writing report to ${fileName}`);
  111. for (const entry of Object.entries(stats)) {
  112. if (entry[1] !== null) {
  113. let regex = new RegExp("{" + entry[0] + "}", "g");
  114. fileData = fileData.replace(regex, entry[1]?.toString());
  115. } else console.warn(`empty value:`, entry[0]);
  116. }
  117. fs.writeFileSync(`${dir}/${fileName}`, fileData);
  118. return true;
  119. };
  120. const updateReports = async (config: Config, round?: number) => {
  121. const { providerUrl } = config;
  122. console.debug(`Connecting to ${providerUrl}`);
  123. const api: ApiPromise = await connectApi(providerUrl);
  124. await api.isReady;
  125. console.log(`-> Fetching councils`);
  126. const head = await getHead(api);
  127. getCouncils(api, +head).then(async (councils: Round[]) => {
  128. api.disconnect();
  129. if (round === null || isNaN(round)) {
  130. console.log(`-> Updating reports`);
  131. await Promise.all(
  132. councils.map(({ start, end }) => createReports(start, end, config))
  133. );
  134. } else {
  135. const council = councils.find((c) => c.round === round);
  136. if (!council) return console.warn(`Round ${round} not found:`, councils);
  137. console.log(
  138. `-> Updating round ${round} (${council.start}-${council.end})`
  139. );
  140. await createReports(council.start, council.end, config);
  141. }
  142. process.exit();
  143. });
  144. };
  145. const createReports = (
  146. startBlock: number,
  147. endBlock: number,
  148. config: Config
  149. ) => {
  150. councilReport(startBlock, endBlock, config);
  151. return tokenomicsReport(startBlock, endBlock, config);
  152. };
  153. const main = async (config: Config) => {
  154. const args = process.argv.slice(2);
  155. if (args.length < 2) return updateReports(config, Number(args[0]));
  156. const startBlock = Number(args[0]);
  157. const endBlock = Number(args[1]);
  158. if (isNaN(startBlock) || isNaN(endBlock) || startBlock >= endBlock) {
  159. console.error("Invalid block range.");
  160. process.exit(1);
  161. }
  162. createReports(startBlock, endBlock, config);
  163. };
  164. main(CONFIG);