[contract_manager] upgrade evm executor contracts (#1264)

This commit is contained in:
Dev Kalra 2024-01-31 18:58:52 +05:30 committed by GitHub
parent 2952eb0dc2
commit 4edef3a4ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 168 additions and 5 deletions

View File

@ -0,0 +1,133 @@
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-executor-contract";
const parser = yargs(hideBin(process.argv))
.usage(
"Deploys a new ExecutorUpgradeable contract to a set of chains where Entropy is deployed and creates a governance proposal for it.\n" +
`Uses a cache file (${CACHE_FILE}) to avoid deploying contracts twice\n` +
"Usage: $0 --chain <chain_1> --chain <chain_2> --private-key <private_key> --ops-key-path <ops_key_path> --std-output <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 runIfNotCached(
cacheKey: string,
fn: () => Promise<string>
): Promise<string> {
const cache = existsSync(CACHE_FILE)
? JSON.parse(readFileSync(CACHE_FILE, "utf8"))
: {};
if (cache[cacheKey]) {
return cache[cacheKey];
}
const result = await fn();
cache[cacheKey] = 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);
const payloads: Buffer[] = [];
for (const contract of Object.values(DefaultStore.entropy_contracts)) {
if (selectedChains.includes(contract.chain)) {
const artifact = JSON.parse(readFileSync(argv["std-output"], "utf8"));
console.log("Deploying contract to", contract.chain.getId());
const address = await runIfNotCached(
`deploy-${contract.chain.getId()}`,
() => {
return contract.chain.deploy(
toPrivateKey(argv["private-key"]),
artifact["abi"],
artifact["bytecode"],
[],
2
);
}
);
console.log(
`Deployed contract at ${address} on ${contract.chain.getId()}`
);
const payload = await contract.generateUpgradeExecutorContractsPayload(
address
);
console.log(payload.toString("hex"));
payloads.push(payload);
}
}
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();

View File

@ -183,7 +183,21 @@ const EXTENDED_PYTH_ABI = [
},
...PythInterfaceAbi,
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
const EXECUTOR_ABI = [
{
inputs: [
{
internalType: "address",
name: "newImplementation",
type: "address",
},
],
name: "upgradeTo",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
const WORMHOLE_ABI = [
{
inputs: [],
@ -366,11 +380,15 @@ export class EvmEntropyContract extends Storable {
// Generate a payload for the given executor address and calldata.
// `executor` and `calldata` should be hex strings.
generateExecutorPayload(executor: string, calldata: string) {
generateExecutorPayload(
executor: string,
callAddress: string,
calldata: string
) {
return new EvmExecute(
this.chain.wormholeChainName,
executor.replace("0x", ""),
this.address.replace("0x", ""),
callAddress.replace("0x", ""),
0n,
Buffer.from(calldata.replace("0x", ""), "hex")
).encode();
@ -380,14 +398,26 @@ export class EvmEntropyContract extends Storable {
generateAcceptAdminPayload(newAdmin: string): Buffer {
const contract = this.getContract();
const data = contract.methods.acceptAdmin().encodeABI();
return this.generateExecutorPayload(newAdmin, data);
return this.generateExecutorPayload(newAdmin, this.address, data);
}
// Generates a payload for newOwner to call acceptOwnership on the entropy contracts
generateAcceptOwnershipPayload(newOwner: string): Buffer {
const contract = this.getContract();
const data = contract.methods.acceptOwnership().encodeABI();
return this.generateExecutorPayload(newOwner, data);
return this.generateExecutorPayload(newOwner, this.address, data);
}
// Generates a payload to upgrade the executor contract, the owner of entropy contracts
async generateUpgradeExecutorContractsPayload(
newImplementation: string
): Promise<Buffer> {
// Executor contract is the owner of entropy contract
const executorAddr = await this.getOwner();
const web3 = new Web3(this.chain.getRpcUrl());
const executor = new web3.eth.Contract(EXECUTOR_ABI, executorAddr);
const data = executor.methods.upgradeTo(newImplementation).encodeABI();
return this.generateExecutorPayload(executorAddr, executorAddr, data);
}
getOwner(): string {