common.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import { DefaultStore, EvmChain, PrivateKey } from "../src";
  2. import { existsSync, readFileSync, writeFileSync } from "fs";
  3. import { join } from "path";
  4. import Web3 from "web3";
  5. import { Contract } from "web3-eth-contract";
  6. import { InferredOptionType } from "yargs";
  7. interface DeployConfig {
  8. gasMultiplier: number;
  9. gasPriceMultiplier: number;
  10. jsonOutputDir: string;
  11. privateKey: PrivateKey;
  12. }
  13. // Deploys a contract if it was not deployed before.
  14. // It will check for the past deployments in file `cacheFile` against a key
  15. // If `cacheKey` is provided it will be used as the key, else it will use
  16. // a key - `${chain.getId()}-${artifactName}`
  17. export async function deployIfNotCached(
  18. cacheFile: string,
  19. chain: EvmChain,
  20. config: DeployConfig,
  21. artifactName: string,
  22. deployArgs: any[], // eslint-disable-line @typescript-eslint/no-explicit-any
  23. cacheKey?: string
  24. ): Promise<string> {
  25. const runIfNotCached = makeCacheFunction(cacheFile);
  26. const key = cacheKey ?? `${chain.getId()}-${artifactName}`;
  27. return runIfNotCached(key, async () => {
  28. const artifact = JSON.parse(
  29. readFileSync(join(config.jsonOutputDir, `${artifactName}.json`), "utf8")
  30. );
  31. console.log(`Deploying ${artifactName} on ${chain.getId()}...`);
  32. const addr = await chain.deploy(
  33. config.privateKey,
  34. artifact["abi"],
  35. artifact["bytecode"],
  36. deployArgs,
  37. config.gasMultiplier,
  38. config.gasPriceMultiplier
  39. );
  40. console.log(`✅ Deployed ${artifactName} on ${chain.getId()} at ${addr}`);
  41. return addr;
  42. });
  43. }
  44. export function getWeb3Contract(
  45. jsonOutputDir: string,
  46. artifactName: string,
  47. address: string
  48. ): Contract {
  49. const artifact = JSON.parse(
  50. readFileSync(join(jsonOutputDir, `${artifactName}.json`), "utf8")
  51. );
  52. const web3 = new Web3();
  53. return new web3.eth.Contract(artifact["abi"], address);
  54. }
  55. export const COMMON_DEPLOY_OPTIONS = {
  56. "std-output-dir": {
  57. type: "string",
  58. demandOption: true,
  59. desc: "Path to the standard JSON output of the contracts (build artifact) directory",
  60. },
  61. "private-key": {
  62. type: "string",
  63. demandOption: true,
  64. desc: "Private key to sign the transactions with",
  65. },
  66. chain: {
  67. type: "array",
  68. demandOption: true,
  69. desc: "Chain to upload the contract on. Can be one of the evm chains available in the store",
  70. },
  71. "deployment-type": {
  72. type: "string",
  73. demandOption: false,
  74. default: "stable",
  75. desc: "Deployment type to use. Can be 'stable' or 'beta'",
  76. },
  77. "gas-multiplier": {
  78. type: "number",
  79. demandOption: false,
  80. // Proxy (ERC1967) contract gas estimate is insufficient in many networks and thus we use 2 by default to make it work.
  81. default: 2,
  82. desc: "Gas multiplier to use for the deployment. This is useful when gas estimates are not accurate",
  83. },
  84. "gas-price-multiplier": {
  85. type: "number",
  86. demandOption: false,
  87. default: 1,
  88. desc: "Gas price multiplier to use for the deployment. This is useful when gas price estimates are not accurate",
  89. },
  90. "save-contract": {
  91. type: "boolean",
  92. demandOption: false,
  93. default: true,
  94. desc: "Save the contract to the store",
  95. },
  96. } as const;
  97. export const COMMON_UPGRADE_OPTIONS = {
  98. testnet: {
  99. type: "boolean",
  100. default: false,
  101. desc: "Upgrade testnet contracts instead of mainnet",
  102. },
  103. "all-chains": {
  104. type: "boolean",
  105. default: false,
  106. desc: "Upgrade the contract on all chains. Use with --testnet flag to upgrade all testnet contracts",
  107. },
  108. chain: {
  109. type: "array",
  110. string: true,
  111. desc: "Chains to upgrade the contract on",
  112. },
  113. "private-key": COMMON_DEPLOY_OPTIONS["private-key"],
  114. "ops-key-path": {
  115. type: "string",
  116. demandOption: true,
  117. desc: "Path to the private key of the proposer to use for the operations multisig governance proposal",
  118. },
  119. "std-output": {
  120. type: "string",
  121. demandOption: true,
  122. desc: "Path to the standard JSON output of the pyth contract (build artifact)",
  123. },
  124. } as const;
  125. export function makeCacheFunction(
  126. cacheFile: string
  127. ): (cacheKey: string, fn: () => Promise<string>) => Promise<string> {
  128. async function runIfNotCached(
  129. cacheKey: string,
  130. fn: () => Promise<string>
  131. ): Promise<string> {
  132. const cache = existsSync(cacheFile)
  133. ? JSON.parse(readFileSync(cacheFile, "utf8"))
  134. : {};
  135. if (cache[cacheKey]) {
  136. return cache[cacheKey];
  137. }
  138. const result = await fn();
  139. cache[cacheKey] = result;
  140. writeFileSync(cacheFile, JSON.stringify(cache, null, 2));
  141. return result;
  142. }
  143. return runIfNotCached;
  144. }
  145. export function getSelectedChains(argv: {
  146. chain: InferredOptionType<typeof COMMON_UPGRADE_OPTIONS["chain"]>;
  147. testnet: InferredOptionType<typeof COMMON_UPGRADE_OPTIONS["testnet"]>;
  148. allChains: InferredOptionType<typeof COMMON_UPGRADE_OPTIONS["all-chains"]>;
  149. }) {
  150. const selectedChains: EvmChain[] = [];
  151. if (argv.allChains && argv.chain)
  152. throw new Error("Cannot use both --all-chains and --chain");
  153. if (!argv.allChains && !argv.chain)
  154. throw new Error("Must use either --all-chains or --chain");
  155. for (const chain of Object.values(DefaultStore.chains)) {
  156. if (!(chain instanceof EvmChain)) continue;
  157. if (
  158. (argv.allChains && chain.isMainnet() !== argv.testnet) ||
  159. argv.chain?.includes(chain.getId())
  160. )
  161. selectedChains.push(chain);
  162. }
  163. if (argv.chain && selectedChains.length !== argv.chain.length)
  164. throw new Error(
  165. `Some chains were not found ${selectedChains
  166. .map((chain) => chain.getId())
  167. .toString()}`
  168. );
  169. for (const chain of selectedChains) {
  170. if (chain.isMainnet() != selectedChains[0].isMainnet())
  171. throw new Error("All chains must be either mainnet or testnet");
  172. }
  173. return selectedChains;
  174. }