deploy_evm_entropy_contracts.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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 {
  6. DeploymentType,
  7. EvmEntropyContract,
  8. EvmPriceFeedContract,
  9. getDefaultDeploymentConfig,
  10. PrivateKey,
  11. toDeploymentType,
  12. toPrivateKey,
  13. WormholeEvmContract,
  14. } from "../src";
  15. import {
  16. COMMON_DEPLOY_OPTIONS,
  17. deployIfNotCached,
  18. getWeb3Contract,
  19. } from "./common";
  20. import Web3 from "web3";
  21. type DeploymentConfig = {
  22. type: DeploymentType;
  23. gasMultiplier: number;
  24. gasPriceMultiplier: number;
  25. privateKey: PrivateKey;
  26. jsonOutputDir: string;
  27. wormholeAddr: string;
  28. saveContract: boolean;
  29. };
  30. const CACHE_FILE = ".cache-deploy-evm-entropy-contracts";
  31. const ENTROPY_DEFAULT_PROVIDER = {
  32. mainnet: "0x52DeaA1c84233F7bb8C8A45baeDE41091c616506",
  33. testnet: "0x6CC14824Ea2918f5De5C2f75A9Da968ad4BD6344",
  34. };
  35. const parser = yargs(hideBin(process.argv))
  36. .scriptName("deploy_evm_entropy_contracts.ts")
  37. .usage(
  38. "Usage: $0 --std-output-dir <path/to/std-output-dir/> --private-key <private-key> --chain <chain> --wormhole-addr <wormhole-addr>"
  39. )
  40. .options({
  41. ...COMMON_DEPLOY_OPTIONS,
  42. chain: {
  43. type: "string",
  44. demandOption: true,
  45. desc: "Chain to upload the contract on. Can be one of the evm chains available in the store",
  46. },
  47. });
  48. async function deployExecutorContracts(
  49. chain: EvmChain,
  50. config: DeploymentConfig
  51. ): Promise<string> {
  52. const executorImplAddr = await deployIfNotCached(
  53. CACHE_FILE,
  54. chain,
  55. config,
  56. "ExecutorUpgradable",
  57. []
  58. );
  59. // Craft the init data for the proxy contract
  60. const { governanceDataSource } = getDefaultDeploymentConfig(config.type);
  61. const executorImplContract = getWeb3Contract(
  62. config.jsonOutputDir,
  63. "ExecutorUpgradable",
  64. executorImplAddr
  65. );
  66. const executorInitData = executorImplContract.methods
  67. .initialize(
  68. config.wormholeAddr,
  69. 0, // lastExecutedSequence,
  70. chain.getWormholeChainId(),
  71. governanceDataSource.emitterChain,
  72. `0x${governanceDataSource.emitterAddress}`
  73. )
  74. .encodeABI();
  75. return await deployIfNotCached(CACHE_FILE, chain, config, "ERC1967Proxy", [
  76. executorImplAddr,
  77. executorInitData,
  78. ]);
  79. }
  80. async function deployEntropyContracts(
  81. chain: EvmChain,
  82. config: DeploymentConfig,
  83. executorAddr: string
  84. ): Promise<string> {
  85. const entropyImplAddr = await deployIfNotCached(
  86. CACHE_FILE,
  87. chain,
  88. config,
  89. "EntropyUpgradable",
  90. []
  91. );
  92. const entropyImplContract = getWeb3Contract(
  93. config.jsonOutputDir,
  94. "EntropyUpgradable",
  95. entropyImplAddr
  96. );
  97. const entropyInitData = entropyImplContract.methods
  98. .initialize(
  99. executorAddr, // owner
  100. executorAddr, // admin
  101. 1, // pythFeeInWei
  102. chain.isMainnet()
  103. ? ENTROPY_DEFAULT_PROVIDER.mainnet
  104. : ENTROPY_DEFAULT_PROVIDER.testnet,
  105. true // prefillRequestStorage
  106. )
  107. .encodeABI();
  108. return await deployIfNotCached(
  109. CACHE_FILE,
  110. chain,
  111. config,
  112. "ERC1967Proxy",
  113. [entropyImplAddr, entropyInitData],
  114. // NOTE: we are deploying a ERC1967Proxy when deploying executor
  115. // we need to provide a different cache key. As the `artifactname`
  116. // is same in both case which means the cache key will be same
  117. `${chain.getId()}-ERC1967Proxy-ENTROPY`
  118. );
  119. }
  120. async function topupProviderIfNecessary(
  121. chain: EvmChain,
  122. deploymentConfig: DeploymentConfig
  123. ) {
  124. const provider = chain.isMainnet()
  125. ? ENTROPY_DEFAULT_PROVIDER.mainnet
  126. : ENTROPY_DEFAULT_PROVIDER.testnet;
  127. const web3 = new Web3(chain.getRpcUrl());
  128. const balance = Number(
  129. web3.utils.fromWei(await web3.eth.getBalance(provider), "ether")
  130. );
  131. const MIN_BALANCE = 0.01;
  132. console.log(`Provider balance: ${balance} ETH`);
  133. if (balance < MIN_BALANCE) {
  134. console.log(
  135. `Balance is less than ${MIN_BALANCE}. Topping up the provider address...`
  136. );
  137. const signer = web3.eth.accounts.privateKeyToAccount(
  138. deploymentConfig.privateKey
  139. );
  140. web3.eth.accounts.wallet.add(signer);
  141. const tx = await web3.eth.sendTransaction({
  142. from: signer.address,
  143. to: provider,
  144. gas: 30000,
  145. value: web3.utils.toWei(`${MIN_BALANCE}`, "ether"),
  146. });
  147. console.log("Topped up the provider address. Tx: ", tx.transactionHash);
  148. }
  149. }
  150. async function findWormholeAddress(
  151. chain: EvmChain
  152. ): Promise<string | undefined> {
  153. for (const contract of Object.values(DefaultStore.contracts)) {
  154. if (
  155. contract instanceof EvmPriceFeedContract &&
  156. contract.getChain().getId() === chain.getId()
  157. ) {
  158. return (await contract.getWormholeContract()).address;
  159. }
  160. }
  161. }
  162. async function main() {
  163. const argv = await parser.argv;
  164. const chainName = argv.chain;
  165. const chain = DefaultStore.chains[chainName];
  166. if (!chain) {
  167. throw new Error(`Chain ${chainName} not found`);
  168. } else if (!(chain instanceof EvmChain)) {
  169. throw new Error(`Chain ${chainName} is not an EVM chain`);
  170. }
  171. const wormholeAddr = await findWormholeAddress(chain);
  172. if (!wormholeAddr) {
  173. // TODO: deploy wormhole if necessary and maintain a wormhole store
  174. throw new Error(`Wormhole contract not found for chain ${chain.getId()}`);
  175. }
  176. const deploymentConfig: DeploymentConfig = {
  177. type: toDeploymentType(argv.deploymentType),
  178. gasMultiplier: argv.gasMultiplier,
  179. gasPriceMultiplier: argv.gasPriceMultiplier,
  180. privateKey: toPrivateKey(argv.privateKey),
  181. jsonOutputDir: argv.stdOutputDir,
  182. saveContract: argv.saveContract,
  183. wormholeAddr,
  184. };
  185. const wormholeContract = new WormholeEvmContract(
  186. chain,
  187. deploymentConfig.wormholeAddr
  188. );
  189. const wormholeChainId = await wormholeContract.getChainId();
  190. if (chain.getWormholeChainId() != wormholeChainId) {
  191. throw new Error(
  192. `Wormhole chain id mismatch. Expected ${chain.getWormholeChainId()} but got ${wormholeChainId}`
  193. );
  194. }
  195. await topupProviderIfNecessary(chain, deploymentConfig);
  196. console.log(
  197. `Deployment config: ${JSON.stringify(deploymentConfig, null, 2)}\n`
  198. );
  199. console.log(`Deploying entropy contracts on ${chain.getId()}...`);
  200. const executorAddr = await deployExecutorContracts(chain, deploymentConfig);
  201. const entropyAddr = await deployEntropyContracts(
  202. chain,
  203. deploymentConfig,
  204. executorAddr
  205. );
  206. if (deploymentConfig.saveContract) {
  207. console.log("Saving the contract in the store...");
  208. const contract = new EvmEntropyContract(chain, entropyAddr);
  209. DefaultStore.entropy_contracts[contract.getId()] = contract;
  210. DefaultStore.saveAllContracts();
  211. }
  212. console.log(
  213. `✅ Deployed entropy contracts on ${chain.getId()} at ${entropyAddr}\n\n`
  214. );
  215. }
  216. main();