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 { 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}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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> {
|
||||||
|
|
Loading…
Reference in New Issue