deploy_evm_pricefeed_contracts.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. /* eslint-disable @typescript-eslint/no-unsafe-call */
  2. /* eslint-disable @typescript-eslint/no-floating-promises */
  3. /* eslint-disable @typescript-eslint/no-unsafe-member-access */
  4. /* eslint-disable @typescript-eslint/no-unsafe-assignment */
  5. /* eslint-disable unicorn/prefer-top-level-await */
  6. /* eslint-disable @typescript-eslint/restrict-template-expressions */
  7. /* eslint-disable no-console */
  8. import { HermesClient } from "@pythnetwork/hermes-client";
  9. import yargs from "yargs";
  10. import { hideBin } from "yargs/helpers";
  11. import type { BaseDeployConfig } from "./common";
  12. import {
  13. COMMON_DEPLOY_OPTIONS,
  14. deployIfNotCached,
  15. getWeb3Contract,
  16. getOrDeployWormholeContract,
  17. } from "./common";
  18. import type { DeploymentType } from "../src/core/base";
  19. import {
  20. getDefaultDeploymentConfig,
  21. toDeploymentType,
  22. toPrivateKey,
  23. } from "../src/core/base";
  24. import { EvmChain } from "../src/core/chains";
  25. import { EvmPriceFeedContract } from "../src/core/contracts";
  26. import { DefaultStore } from "../src/node/utils/store";
  27. type DeploymentConfig = {
  28. type: DeploymentType;
  29. validTimePeriodSeconds: number;
  30. singleUpdateFeeInWei: number;
  31. saveContract: boolean;
  32. } & BaseDeployConfig;
  33. const CACHE_FILE = ".cache-deploy-evm";
  34. const parser = yargs(hideBin(process.argv))
  35. .scriptName("deploy_evm_pricefeed_contracts.ts")
  36. .usage(
  37. "Usage: $0 --std-output-dir <path/to/std-output-dir/> --private-key <private-key> --chain <chain0> --chain <chain1>",
  38. )
  39. .options({
  40. ...COMMON_DEPLOY_OPTIONS,
  41. "valid-time-period-seconds": {
  42. type: "number",
  43. demandOption: false,
  44. default: 60,
  45. desc: "Valid time period in seconds for the price feed staleness",
  46. },
  47. "single-update-fee-in-wei": {
  48. type: "number",
  49. demandOption: false,
  50. default: 1,
  51. desc: "Single update fee in wei for the price feed",
  52. },
  53. "single-update-fee-in-usd": {
  54. type: "number",
  55. demandOption: false,
  56. desc: "Single update fee in USD for the price feed. (This overrides the single-update-fee-in-wei option) ",
  57. },
  58. "native-token-price-feed-id": {
  59. type: "string",
  60. demandOption: false,
  61. desc: "Pyth Price Feed ID to fetch the current price of the native token (This will be used to calculate the single-update-fee-in-usd)",
  62. },
  63. "native-token-decimals": {
  64. type: "number",
  65. demandOption: false,
  66. desc: "Number of decimals of the native token",
  67. },
  68. });
  69. async function deployPriceFeedContracts(
  70. chain: EvmChain,
  71. config: DeploymentConfig,
  72. wormholeAddr: string,
  73. ): Promise<string> {
  74. const pythImplAddr = await deployIfNotCached(
  75. CACHE_FILE,
  76. chain,
  77. config,
  78. "PythUpgradable",
  79. [],
  80. );
  81. // Craft the init data for the proxy contract
  82. const { dataSources, governanceDataSource } = getDefaultDeploymentConfig(
  83. config.type,
  84. );
  85. const pythImplContract = getWeb3Contract(
  86. config.jsonOutputDir,
  87. "PythUpgradable",
  88. pythImplAddr,
  89. );
  90. const pythInitData = pythImplContract.methods
  91. .initialize(
  92. wormholeAddr,
  93. dataSources.map((ds) => ds.emitterChain),
  94. dataSources.map((ds) => "0x" + ds.emitterAddress),
  95. governanceDataSource.emitterChain,
  96. "0x" + governanceDataSource.emitterAddress,
  97. 0, // governanceInitialSequence
  98. config.validTimePeriodSeconds,
  99. config.singleUpdateFeeInWei,
  100. )
  101. .encodeABI();
  102. return await deployIfNotCached(CACHE_FILE, chain, config, "ERC1967Proxy", [
  103. pythImplAddr,
  104. pythInitData,
  105. ]);
  106. }
  107. async function main() {
  108. const argv = await parser.argv;
  109. let singleUpdateFeeInWei = argv.singleUpdateFeeInWei;
  110. const singleUpdateFeeInUsd = argv["single-update-fee-in-usd"];
  111. const nativeTokenPriceFeedId = argv["native-token-price-feed-id"];
  112. const nativeTokenDecimals = argv["native-token-decimals"];
  113. if (
  114. singleUpdateFeeInUsd &&
  115. (nativeTokenPriceFeedId == undefined || nativeTokenDecimals == undefined)
  116. ) {
  117. throw new Error(
  118. "native-token-price-feed-id and native-token-decimals are required when single-update-fee-in-usd is provided",
  119. );
  120. }
  121. if (nativeTokenPriceFeedId && singleUpdateFeeInUsd && nativeTokenDecimals) {
  122. const hermesClient = new HermesClient("https://hermes.pyth.network");
  123. const priceObject = await hermesClient.getLatestPriceUpdates(
  124. [nativeTokenPriceFeedId],
  125. {
  126. parsed: true,
  127. },
  128. );
  129. const price = priceObject.parsed?.[0]?.price;
  130. if (price == undefined) {
  131. throw new Error("Failed to get price of the native token");
  132. }
  133. const priceInUsd = Number(price.price);
  134. const exponent = price.expo;
  135. singleUpdateFeeInWei = Math.round(
  136. Math.pow(10, nativeTokenDecimals) *
  137. (singleUpdateFeeInUsd / (priceInUsd * Math.pow(10, exponent))),
  138. );
  139. console.log(`Single update fee in wei: ${singleUpdateFeeInWei}`);
  140. }
  141. const deploymentConfig: DeploymentConfig = {
  142. type: toDeploymentType(argv.deploymentType),
  143. validTimePeriodSeconds: argv.validTimePeriodSeconds,
  144. singleUpdateFeeInWei: singleUpdateFeeInWei,
  145. gasMultiplier: argv.gasMultiplier,
  146. gasPriceMultiplier: argv.gasPriceMultiplier,
  147. privateKey: toPrivateKey(argv.privateKey),
  148. jsonOutputDir: argv.stdOutputDir,
  149. saveContract: argv.saveContract,
  150. };
  151. const maskedDeploymentConfig = {
  152. ...deploymentConfig,
  153. privateKey: deploymentConfig.privateKey ? `<REDACTED>` : undefined,
  154. };
  155. console.log(
  156. `Deployment config: ${JSON.stringify(maskedDeploymentConfig, undefined, 2)}\n`,
  157. );
  158. const chainNames = argv.chain;
  159. for (const chainName of chainNames) {
  160. const chain = DefaultStore.getChainOrThrow(chainName, EvmChain);
  161. console.log(`Deploying price feed contracts on ${chain.getId()}...`);
  162. const wormholeContract = await getOrDeployWormholeContract(
  163. chain,
  164. deploymentConfig,
  165. CACHE_FILE,
  166. );
  167. const priceFeedAddr = await deployPriceFeedContracts(
  168. chain,
  169. deploymentConfig,
  170. wormholeContract.address,
  171. );
  172. if (deploymentConfig.saveContract) {
  173. console.log("Saving the contract in the store...");
  174. const contract = new EvmPriceFeedContract(chain, priceFeedAddr);
  175. DefaultStore.contracts[contract.getId()] = contract;
  176. DefaultStore.saveAllContracts();
  177. }
  178. console.log(
  179. `✅ Deployed price feed contracts on ${chain.getId()} at ${priceFeedAddr}\n\n`,
  180. );
  181. }
  182. }
  183. main();