diff --git a/contract_manager/scripts/check_proposal.ts b/contract_manager/scripts/check_proposal.ts index 3dd2cb49..21921b3e 100644 --- a/contract_manager/scripts/check_proposal.ts +++ b/contract_manager/scripts/check_proposal.ts @@ -5,6 +5,7 @@ import { createHash } from "crypto"; import { DefaultStore } from "../src/store"; import { CosmosUpgradeContract, + EvmExecute, EvmSetWormholeAddress, EvmUpgradeContract, getProposalInstructions, @@ -19,7 +20,9 @@ import { import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; import { AccountMeta, Keypair, PublicKey } from "@solana/web3.js"; import { + EvmEntropyContract, EvmPriceFeedContract, + getCodeDigestWithoutAddress, WormholeEvmContract, } from "../src/contracts/evm"; import Web3 from "web3"; @@ -134,6 +137,70 @@ async function main() { } } } + if (instruction.governanceAction instanceof EvmExecute) { + // Note: it only checks for upgrade entropy contracts right now + console.log( + `Verifying EVMExecute Contract on ${instruction.governanceAction.targetChainId}` + ); + for (const chain of Object.values(DefaultStore.chains)) { + if ( + chain instanceof EvmChain && + chain.wormholeChainName === + instruction.governanceAction.targetChainId + ) { + const executorAddress = + instruction.governanceAction.executorAddress; + const callAddress = instruction.governanceAction.callAddress; + const calldata = instruction.governanceAction.calldata; + + // currently executor is only being used by the entropy contract + const contract = new EvmEntropyContract(chain, callAddress); + const owner = await contract.getOwner(); + + if ( + executorAddress.toUpperCase() !== + owner.replace("0x", "").toUpperCase() + ) { + console.log( + `Executor Address: ${executorAddress.toUpperCase()} is not equal to Owner Address: ${owner + .replace("0x", "") + .toUpperCase()}` + ); + continue; + } + + const calldataHex = calldata.toString("hex"); + const web3 = new Web3(); + const methodSignature = web3.eth.abi + .encodeFunctionSignature("upgradeTo(address)") + .replace("0x", ""); + + let newImplementationAddress: string | undefined = undefined; + if (calldataHex.startsWith(methodSignature)) { + newImplementationAddress = web3.eth.abi.decodeParameter( + "address", + calldataHex.replace(methodSignature, "") + ) as unknown as string; + } + + if (newImplementationAddress === undefined) { + console.log( + `We couldn't parse the instruction for ${chain.getId()}` + ); + continue; + } + + const newImplementationCode = await getCodeDigestWithoutAddress( + chain.getRpcUrl(), + newImplementationAddress + ); + // this should be the same keccak256 of the deployedCode property generated by truffle + console.log( + `${chain.getId()} new implementation address:${newImplementationAddress} digest:${newImplementationCode}` + ); + } + } + } } } } diff --git a/contract_manager/scripts/upgrade_evm_executor_contracts.ts b/contract_manager/scripts/upgrade_evm_entropy_contracts.ts similarity index 65% rename from contract_manager/scripts/upgrade_evm_executor_contracts.ts rename to contract_manager/scripts/upgrade_evm_entropy_contracts.ts index c9622a1f..c6a167ba 100644 --- a/contract_manager/scripts/upgrade_evm_executor_contracts.ts +++ b/contract_manager/scripts/upgrade_evm_entropy_contracts.ts @@ -9,19 +9,33 @@ import { makeCacheFunction, } from "./common"; -const CACHE_FILE = ".cache-upgrade-evm-executor-contract"; -const runIfNotCached = makeCacheFunction(CACHE_FILE); +const EXECUTOR_CACHE_FILE = ".cache-upgrade-evm-executor-contract"; +const ENTROPY_CACHE_FILE = ".cache-upgrade-evm-entropy-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` + + "Deploys a new Upgradeable contract for Executor or Entropy to a set of chains where Entropy is deployed and creates a governance proposal for it.\n" + + `Uses a cache file to avoid deploying contracts twice\n` + "Usage: $0 --chain --chain --private-key --ops-key-path --std-output " ) - .options(COMMON_UPGRADE_OPTIONS); + .options({ + ...COMMON_UPGRADE_OPTIONS, + "contract-type": { + type: "string", + choices: ["executor", "entropy"], + demandOption: true, + }, + }); async function main() { const argv = await parser.argv; + const cacheFile = + argv["contract-type"] === "executor" + ? EXECUTOR_CACHE_FILE + : ENTROPY_CACHE_FILE; + + const runIfNotCached = makeCacheFunction(cacheFile); + const selectedChains = getSelectedChains(argv); const vault = @@ -29,7 +43,7 @@ async function main() { "mainnet-beta_FVQyHcooAtThJ83XFrNnv74BcinbRH3bRmfFamAHBfuj" ]; - console.log("Using cache file", CACHE_FILE); + console.log("Using cache file", cacheFile); const payloads: Buffer[] = []; for (const contract of Object.values(DefaultStore.entropy_contracts)) { @@ -51,9 +65,11 @@ async function main() { console.log( `Deployed contract at ${address} on ${contract.chain.getId()}` ); - const payload = await contract.generateUpgradeExecutorContractsPayload( - address - ); + const payload = + argv["contract-type"] === "executor" + ? await contract.generateUpgradeExecutorContractsPayload(address) + : await contract.generateUpgradeEntropyContractPayload(address); + console.log(payload.toString("hex")); payloads.push(payload); } diff --git a/contract_manager/src/contracts/evm.ts b/contract_manager/src/contracts/evm.ts index cb8361ae..9e441e37 100644 --- a/contract_manager/src/contracts/evm.ts +++ b/contract_manager/src/contracts/evm.ts @@ -62,6 +62,19 @@ const EXTENDED_ENTROPY_ABI = [ stateMutability: "pure", type: "function", }, + { + inputs: [ + { + internalType: "address", + name: "newImplementation", + type: "address", + }, + ], + name: "upgradeTo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, ...EntropyAbi, ] as any; // eslint-disable-line @typescript-eslint/no-explicit-any const EXTENDED_PYTH_ABI = [ @@ -354,6 +367,29 @@ const EXECUTOR_ABI = [ type: "function", }, ] as any; // eslint-disable-line @typescript-eslint/no-explicit-any + +/** + * Returns the keccak256 digest of the contract bytecode at the given address after replacing + * any occurrences of the contract addr in the bytecode with 0.The bytecode stores the deployment + * address as an immutable variable. This behavior is inherited from OpenZeppelin's implementation + * of UUPSUpgradeable contract. You can read more about verification with immutable variables here: + * https://docs.sourcify.dev/docs/immutables/ + * This function can be used to verify that the contract code is the same on all chains and matches + * with the deployedCode property generated by truffle builds + */ +export async function getCodeDigestWithoutAddress( + rpcUrl: string, + address: string +): Promise { + const web3 = new Web3(rpcUrl); + const code = await web3.eth.getCode(address); + const strippedCode = code.replaceAll( + address.toLowerCase().replace("0x", ""), + "0000000000000000000000000000000000000000" + ); + return Web3.utils.keccak256(strippedCode); +} + export class WormholeEvmContract extends WormholeContract { constructor(public chain: EvmChain, public address: string) { super(); @@ -480,6 +516,18 @@ export class EvmEntropyContract extends Storable { return this.generateExecutorPayload(newOwner, this.address, data); } + async generateUpgradeEntropyContractPayload( + newImplementation: string + ): Promise { + const contract = this.getContract(); + const data = contract.methods.upgradeTo(newImplementation).encodeABI(); + return this.generateExecutorPayload( + await this.getOwner(), + this.address, + data + ); + } + // Generates a payload to upgrade the executor contract, the owner of entropy contracts async generateUpgradeExecutorContractsPayload( newImplementation: string @@ -708,21 +756,10 @@ export class EvmPriceFeedContract extends PriceFeedContract { } /** - * Returns the keccak256 digest of the contract bytecode after replacing any occurrences of the contract addr in - * the bytecode with 0.The bytecode stores the deployment address as an immutable variable. - * This behavior is inherited from OpenZeppelin's implementation of UUPSUpgradeable contract. - * You can read more about verification with immutable variables here: - * https://docs.sourcify.dev/docs/immutables/ - * This function can be used to verify that the contract code is the same on all chains and matches - * with the deployedCode property generated by truffle builds + * Returns the keccak256 digest of the contract bytecode */ async getCodeDigestWithoutAddress(): Promise { - const code = await this.getCode(); - const strippedCode = code.replaceAll( - this.address.toLowerCase().replace("0x", ""), - "0000000000000000000000000000000000000000" - ); - return Web3.utils.keccak256(strippedCode); + return getCodeDigestWithoutAddress(this.chain.getRpcUrl(), this.address); } async getTotalFee(): Promise {