feat(contract_manager): implement upgrade evm entropy contracts script (#1417)

* implement upgrade evm entropy contracts script

* check proposal for entropy contract upgrades

* refactor scripts

* minor changes in check proposal

* fix comments

* correct comment

* log something and continue

* log only if the owner and executor address doesn't match

* use web3 for abi encoding

* remove unused

* extract code digest code

* feedback implement
This commit is contained in:
Dev Kalra 2024-04-11 15:11:55 +05:30 committed by GitHub
parent 3c5a913a80
commit 34d94e3177
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 142 additions and 22 deletions

View File

@ -5,6 +5,7 @@ import { createHash } from "crypto";
import { DefaultStore } from "../src/store"; import { DefaultStore } from "../src/store";
import { import {
CosmosUpgradeContract, CosmosUpgradeContract,
EvmExecute,
EvmSetWormholeAddress, EvmSetWormholeAddress,
EvmUpgradeContract, EvmUpgradeContract,
getProposalInstructions, getProposalInstructions,
@ -19,7 +20,9 @@ import {
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
import { AccountMeta, Keypair, PublicKey } from "@solana/web3.js"; import { AccountMeta, Keypair, PublicKey } from "@solana/web3.js";
import { import {
EvmEntropyContract,
EvmPriceFeedContract, EvmPriceFeedContract,
getCodeDigestWithoutAddress,
WormholeEvmContract, WormholeEvmContract,
} from "../src/contracts/evm"; } from "../src/contracts/evm";
import Web3 from "web3"; 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}`
);
}
}
}
} }
} }
} }

View File

@ -9,19 +9,33 @@ import {
makeCacheFunction, makeCacheFunction,
} from "./common"; } from "./common";
const CACHE_FILE = ".cache-upgrade-evm-executor-contract"; const EXECUTOR_CACHE_FILE = ".cache-upgrade-evm-executor-contract";
const runIfNotCached = makeCacheFunction(CACHE_FILE); const ENTROPY_CACHE_FILE = ".cache-upgrade-evm-entropy-contract";
const parser = yargs(hideBin(process.argv)) const parser = yargs(hideBin(process.argv))
.usage( .usage(
"Deploys a new ExecutorUpgradeable contract to a set of chains where Entropy is deployed and creates a governance proposal for it.\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 (${CACHE_FILE}) to avoid deploying contracts twice\n` + `Uses a 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>" "Usage: $0 --chain <chain_1> --chain <chain_2> --private-key <private_key> --ops-key-path <ops_key_path> --std-output <std_output>"
) )
.options(COMMON_UPGRADE_OPTIONS); .options({
...COMMON_UPGRADE_OPTIONS,
"contract-type": {
type: "string",
choices: ["executor", "entropy"],
demandOption: true,
},
});
async function main() { async function main() {
const argv = await parser.argv; 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 selectedChains = getSelectedChains(argv);
const vault = const vault =
@ -29,7 +43,7 @@ async function main() {
"mainnet-beta_FVQyHcooAtThJ83XFrNnv74BcinbRH3bRmfFamAHBfuj" "mainnet-beta_FVQyHcooAtThJ83XFrNnv74BcinbRH3bRmfFamAHBfuj"
]; ];
console.log("Using cache file", CACHE_FILE); console.log("Using cache file", cacheFile);
const payloads: Buffer[] = []; const payloads: Buffer[] = [];
for (const contract of Object.values(DefaultStore.entropy_contracts)) { for (const contract of Object.values(DefaultStore.entropy_contracts)) {
@ -51,9 +65,11 @@ async function main() {
console.log( console.log(
`Deployed contract at ${address} on ${contract.chain.getId()}` `Deployed contract at ${address} on ${contract.chain.getId()}`
); );
const payload = await contract.generateUpgradeExecutorContractsPayload( const payload =
address argv["contract-type"] === "executor"
); ? await contract.generateUpgradeExecutorContractsPayload(address)
: await contract.generateUpgradeEntropyContractPayload(address);
console.log(payload.toString("hex")); console.log(payload.toString("hex"));
payloads.push(payload); payloads.push(payload);
} }

View File

@ -62,6 +62,19 @@ const EXTENDED_ENTROPY_ABI = [
stateMutability: "pure", stateMutability: "pure",
type: "function", type: "function",
}, },
{
inputs: [
{
internalType: "address",
name: "newImplementation",
type: "address",
},
],
name: "upgradeTo",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
...EntropyAbi, ...EntropyAbi,
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any ] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
const EXTENDED_PYTH_ABI = [ const EXTENDED_PYTH_ABI = [
@ -354,6 +367,29 @@ const EXECUTOR_ABI = [
type: "function", type: "function",
}, },
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any ] 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<string> {
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 { export class WormholeEvmContract extends WormholeContract {
constructor(public chain: EvmChain, public address: string) { constructor(public chain: EvmChain, public address: string) {
super(); super();
@ -480,6 +516,18 @@ export class EvmEntropyContract extends Storable {
return this.generateExecutorPayload(newOwner, this.address, data); return this.generateExecutorPayload(newOwner, this.address, data);
} }
async generateUpgradeEntropyContractPayload(
newImplementation: string
): Promise<Buffer> {
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 // Generates a payload to upgrade the executor contract, the owner of entropy contracts
async generateUpgradeExecutorContractsPayload( async generateUpgradeExecutorContractsPayload(
newImplementation: string 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 * Returns the keccak256 digest of the contract bytecode
* 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
*/ */
async getCodeDigestWithoutAddress(): Promise<string> { async getCodeDigestWithoutAddress(): Promise<string> {
const code = await this.getCode(); return getCodeDigestWithoutAddress(this.chain.getRpcUrl(), this.address);
const strippedCode = code.replaceAll(
this.address.toLowerCase().replace("0x", ""),
"0000000000000000000000000000000000000000"
);
return Web3.utils.keccak256(strippedCode);
} }
async getTotalFee(): Promise<TokenQty> { async getTotalFee(): Promise<TokenQty> {