deploy_evm_pricefeed_contracts.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. import yargs from "yargs";
  2. import { hideBin } from "yargs/helpers";
  3. import { EvmChain } from "../src/chains";
  4. import { DefaultStore } from "../src/store";
  5. import { existsSync, readFileSync, writeFileSync } from "fs";
  6. import {
  7. DeploymentType,
  8. EvmPriceFeedContract,
  9. getDefaultDeploymentConfig,
  10. PrivateKey,
  11. toDeploymentType,
  12. toPrivateKey,
  13. WormholeEvmContract,
  14. } from "../src";
  15. import { join } from "path";
  16. import Web3 from "web3";
  17. import { Contract } from "web3-eth-contract";
  18. type DeploymentConfig = {
  19. type: DeploymentType;
  20. validTimePeriodSeconds: number;
  21. singleUpdateFeeInWei: number;
  22. gasMultiplier: number;
  23. gasPriceMultiplier: number;
  24. privateKey: PrivateKey;
  25. jsonOutputDir: string;
  26. saveContract: boolean;
  27. };
  28. const CACHE_FILE = ".cache-deploy-evm";
  29. const parser = yargs(hideBin(process.argv))
  30. .scriptName("deploy_evm_pricefeed_contracts.ts")
  31. .usage(
  32. "Usage: $0 --std-output-dir <path/to/std-output-dir/> --private-key <private-key> --chain <chain0> --chain <chain1>"
  33. )
  34. .options({
  35. "std-output-dir": {
  36. type: "string",
  37. demandOption: true,
  38. desc: "Path to the standard JSON output of the contracts (build artifact) directory",
  39. },
  40. "private-key": {
  41. type: "string",
  42. demandOption: true,
  43. desc: "Private key to use for the deployment",
  44. },
  45. chain: {
  46. type: "array",
  47. demandOption: true,
  48. desc: "Chain to upload the contract on. Can be one of the evm chains available in the store",
  49. },
  50. "deployment-type": {
  51. type: "string",
  52. demandOption: false,
  53. default: "stable",
  54. desc: "Deployment type to use. Can be 'stable' or 'beta'",
  55. },
  56. "valid-time-period-seconds": {
  57. type: "number",
  58. demandOption: false,
  59. default: 60,
  60. desc: "Valid time period in seconds for the price feed staleness",
  61. },
  62. "single-update-fee-in-wei": {
  63. type: "number",
  64. demandOption: false,
  65. default: 1,
  66. desc: "Single update fee in wei for the price feed",
  67. },
  68. "gas-multiplier": {
  69. type: "number",
  70. demandOption: false,
  71. // Pyth Proxy (ERC1967) gas estimate is insufficient in many networks and thus we use 2 by default to make it work.
  72. default: 2,
  73. desc: "Gas multiplier to use for the deployment. This is useful when gas estimates are not accurate",
  74. },
  75. "gas-price-multiplier": {
  76. type: "number",
  77. demandOption: false,
  78. default: 1,
  79. desc: "Gas price multiplier to use for the deployment. This is useful when gas price estimates are not accurate",
  80. },
  81. "save-contract": {
  82. type: "boolean",
  83. demandOption: false,
  84. default: true,
  85. desc: "Save the contract to the store",
  86. },
  87. });
  88. async function deployIfNotCached(
  89. chain: EvmChain,
  90. config: DeploymentConfig,
  91. artifactName: string,
  92. deployArgs: any[] // eslint-disable-line @typescript-eslint/no-explicit-any
  93. ): Promise<string> {
  94. const cache = existsSync(CACHE_FILE)
  95. ? JSON.parse(readFileSync(CACHE_FILE, "utf8"))
  96. : {};
  97. const cacheKey = `${chain.getId()}-${artifactName}`;
  98. if (cache[cacheKey]) {
  99. const address = cache[cacheKey];
  100. console.log(
  101. `Using cached deployment of ${artifactName} on ${chain.getId()} at ${address}`
  102. );
  103. return address;
  104. }
  105. const artifact = JSON.parse(
  106. readFileSync(join(config.jsonOutputDir, `${artifactName}.json`), "utf8")
  107. );
  108. console.log(`Deploying ${artifactName} on ${chain.getId()}...`);
  109. const addr = await chain.deploy(
  110. config.privateKey,
  111. artifact["abi"],
  112. artifact["bytecode"],
  113. deployArgs,
  114. config.gasMultiplier,
  115. config.gasPriceMultiplier
  116. );
  117. console.log(`✅ Deployed ${artifactName} on ${chain.getId()} at ${addr}`);
  118. cache[cacheKey] = addr;
  119. writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
  120. return addr;
  121. }
  122. function getWeb3Contract(
  123. config: DeploymentConfig,
  124. artifactName: string,
  125. address: string
  126. ): Contract {
  127. const artifact = JSON.parse(
  128. readFileSync(join(config.jsonOutputDir, `${artifactName}.json`), "utf8")
  129. );
  130. const web3 = new Web3();
  131. return new web3.eth.Contract(artifact["abi"], address);
  132. }
  133. async function deployWormholeReceiverContracts(
  134. chain: EvmChain,
  135. config: DeploymentConfig
  136. ): Promise<string> {
  137. const receiverSetupAddr = await deployIfNotCached(
  138. chain,
  139. config,
  140. "ReceiverSetup",
  141. []
  142. );
  143. const receiverImplAddr = await deployIfNotCached(
  144. chain,
  145. config,
  146. "ReceiverImplementation",
  147. []
  148. );
  149. // Craft the init data for the proxy contract
  150. const setupContract = getWeb3Contract(
  151. config,
  152. "ReceiverSetup",
  153. receiverSetupAddr
  154. );
  155. const { wormholeConfig } = getDefaultDeploymentConfig(config.type);
  156. const initData = setupContract.methods
  157. .setup(
  158. receiverImplAddr,
  159. wormholeConfig.initialGuardianSet.map((addr: string) => "0x" + addr),
  160. chain.getWormholeChainId(),
  161. wormholeConfig.governanceChainId,
  162. "0x" + wormholeConfig.governanceContract
  163. )
  164. .encodeABI();
  165. const wormholeReceiverAddr = await deployIfNotCached(
  166. chain,
  167. config,
  168. "WormholeReceiver",
  169. [receiverSetupAddr, initData]
  170. );
  171. const wormholeEvmContract = new WormholeEvmContract(
  172. chain,
  173. wormholeReceiverAddr
  174. );
  175. if (config.type === "stable") {
  176. console.log(`Syncing mainnet guardian sets for ${chain.getId()}...`);
  177. // TODO: Add a way to pass gas configs to this
  178. await wormholeEvmContract.syncMainnetGuardianSets(config.privateKey);
  179. console.log(`✅ Synced mainnet guardian sets for ${chain.getId()}`);
  180. }
  181. return wormholeReceiverAddr;
  182. }
  183. async function deployPriceFeedContracts(
  184. chain: EvmChain,
  185. config: DeploymentConfig,
  186. wormholeAddr: string
  187. ): Promise<string> {
  188. const pythImplAddr = await deployIfNotCached(
  189. chain,
  190. config,
  191. "PythUpgradable",
  192. []
  193. );
  194. // Craft the init data for the proxy contract
  195. const { dataSources, governanceDataSource } = getDefaultDeploymentConfig(
  196. config.type
  197. );
  198. const pythImplContract = getWeb3Contract(
  199. config,
  200. "PythUpgradable",
  201. pythImplAddr
  202. );
  203. const pythInitData = pythImplContract.methods
  204. .initialize(
  205. wormholeAddr,
  206. dataSources.map((ds) => ds.emitterChain),
  207. dataSources.map((ds) => "0x" + ds.emitterAddress),
  208. governanceDataSource.emitterChain,
  209. "0x" + governanceDataSource.emitterAddress,
  210. 0, // governanceInitialSequence
  211. config.validTimePeriodSeconds,
  212. config.singleUpdateFeeInWei
  213. )
  214. .encodeABI();
  215. return await deployIfNotCached(chain, config, "ERC1967Proxy", [
  216. pythImplAddr,
  217. pythInitData,
  218. ]);
  219. }
  220. async function main() {
  221. const argv = await parser.argv;
  222. const deploymentConfig: DeploymentConfig = {
  223. type: toDeploymentType(argv.deploymentType),
  224. validTimePeriodSeconds: argv.validTimePeriodSeconds,
  225. singleUpdateFeeInWei: argv.singleUpdateFeeInWei,
  226. gasMultiplier: argv.gasMultiplier,
  227. gasPriceMultiplier: argv.gasPriceMultiplier,
  228. privateKey: toPrivateKey(argv.privateKey),
  229. jsonOutputDir: argv.stdOutputDir,
  230. saveContract: argv.saveContract,
  231. };
  232. console.log(
  233. `Deployment config: ${JSON.stringify(deploymentConfig, null, 2)}\n`
  234. );
  235. const chainNames = argv.chain;
  236. for (const chainName of chainNames) {
  237. const chain = DefaultStore.chains[chainName];
  238. if (!chain) {
  239. throw new Error(`Chain ${chain} not found`);
  240. } else if (!(chain instanceof EvmChain)) {
  241. throw new Error(`Chain ${chain} is not an EVM chain`);
  242. }
  243. console.log(`Deploying price feed contracts on ${chain.getId()}...`);
  244. const wormholeAddr = await deployWormholeReceiverContracts(
  245. chain,
  246. deploymentConfig
  247. );
  248. const priceFeedAddr = await deployPriceFeedContracts(
  249. chain,
  250. deploymentConfig,
  251. wormholeAddr
  252. );
  253. if (deploymentConfig.saveContract) {
  254. console.log("Saving the contract in the store...");
  255. const contract = new EvmPriceFeedContract(chain, priceFeedAddr);
  256. DefaultStore.contracts[contract.getId()] = contract;
  257. DefaultStore.saveAllContracts();
  258. }
  259. console.log(
  260. `✅ Deployed price feed contracts on ${chain.getId()} at ${priceFeedAddr}\n\n`
  261. );
  262. }
  263. }
  264. main();