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:
parent
3c5a913a80
commit
34d94e3177
|
@ -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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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_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() {
|
||||
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);
|
||||
}
|
|
@ -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<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 {
|
||||
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<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
|
||||
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<string> {
|
||||
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<TokenQty> {
|
||||
|
|
Loading…
Reference in New Issue