import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import { DefaultStore, EvmChain, loadHotWallet, toPrivateKey } from "../src"; import { existsSync, readFileSync, writeFileSync } from "fs"; const CACHE_FILE = ".cache-upgrade-evm"; const parser = yargs(hideBin(process.argv)) .usage( "Deploys a new PythUpgradable contract to a set of chains and creates a governance proposal for it.\n" + `Uses a cache file (${CACHE_FILE}) to avoid deploying contracts twice\n` + "Usage: $0 --chain --chain --private-key --ops-key-path --std-output " ) .options({ testnet: { type: "boolean", default: false, desc: "Upgrade testnet contracts instead of mainnet", }, "all-chains": { type: "boolean", default: false, desc: "Upgrade the contract on all chains. Use with --testnet flag to upgrade all testnet contracts", }, chain: { type: "array", string: true, desc: "Chains to upgrade the contract on", }, "private-key": { type: "string", demandOption: true, desc: "Private key to use for the deployment", }, "ops-key-path": { type: "string", demandOption: true, desc: "Path to the private key of the proposer to use for the operations multisig governance proposal", }, "std-output": { type: "string", demandOption: true, desc: "Path to the standard JSON output of the pyth contract (build artifact)", }, }); async function run_if_not_cached( cache_key: string, fn: () => Promise ): Promise { const cache = existsSync(CACHE_FILE) ? JSON.parse(readFileSync(CACHE_FILE, "utf8")) : {}; if (cache[cache_key]) { return cache[cache_key]; } const result = await fn(); cache[cache_key] = result; writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2)); return result; } async function main() { const argv = await parser.argv; const selectedChains: EvmChain[] = []; if (argv.allChains && argv.chain) throw new Error("Cannot use both --all-chains and --chain"); if (!argv.allChains && !argv.chain) throw new Error("Must use either --all-chains or --chain"); for (const chain of Object.values(DefaultStore.chains)) { if (!(chain instanceof EvmChain)) continue; if ( (argv.allChains && chain.isMainnet() !== argv.testnet) || argv.chain?.includes(chain.getId()) ) selectedChains.push(chain); } if (argv.chain && selectedChains.length !== argv.chain.length) throw new Error( `Some chains were not found ${selectedChains .map((chain) => chain.getId()) .toString()}` ); for (const chain of selectedChains) { if (chain.isMainnet() != selectedChains[0].isMainnet()) throw new Error("All chains must be either mainnet or testnet"); } const vault = DefaultStore.vaults[ "mainnet-beta_FVQyHcooAtThJ83XFrNnv74BcinbRH3bRmfFamAHBfuj" ]; console.log("Using cache file", CACHE_FILE); console.log( "Upgrading on chains", selectedChains.map((c) => c.getId()) ); const payloads: Buffer[] = []; for (const chain of selectedChains) { const artifact = JSON.parse(readFileSync(argv["std-output"], "utf8")); console.log("Deploying contract to", chain.getId()); const address = await run_if_not_cached(`deploy-${chain.getId()}`, () => { return chain.deploy( toPrivateKey(argv["private-key"]), artifact["abi"], artifact["bytecode"], [] ); }); console.log(`Deployed contract at ${address} on ${chain.getId()}`); payloads.push( chain.generateGovernanceUpgradePayload(address.replace("0x", "")) ); } console.log("Using vault at for proposal", vault.getId()); const wallet = await loadHotWallet(argv["ops-key-path"]); console.log("Using wallet ", wallet.publicKey.toBase58()); await vault.connect(wallet); const proposal = await vault.proposeWormholeMessage(payloads); console.log("Proposal address", proposal.address.toBase58()); } main();