Skip to content

Commit 34d94e3

Browse files
author
Dev Kalra
authored
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
1 parent 3c5a913 commit 34d94e3

File tree

3 files changed

+142
-22
lines changed

3 files changed

+142
-22
lines changed

contract_manager/scripts/check_proposal.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { createHash } from "crypto";
55
import { DefaultStore } from "../src/store";
66
import {
77
CosmosUpgradeContract,
8+
EvmExecute,
89
EvmSetWormholeAddress,
910
EvmUpgradeContract,
1011
getProposalInstructions,
@@ -19,7 +20,9 @@ import {
1920
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
2021
import { AccountMeta, Keypair, PublicKey } from "@solana/web3.js";
2122
import {
23+
EvmEntropyContract,
2224
EvmPriceFeedContract,
25+
getCodeDigestWithoutAddress,
2326
WormholeEvmContract,
2427
} from "../src/contracts/evm";
2528
import Web3 from "web3";
@@ -134,6 +137,70 @@ async function main() {
134137
}
135138
}
136139
}
140+
if (instruction.governanceAction instanceof EvmExecute) {
141+
// Note: it only checks for upgrade entropy contracts right now
142+
console.log(
143+
`Verifying EVMExecute Contract on ${instruction.governanceAction.targetChainId}`
144+
);
145+
for (const chain of Object.values(DefaultStore.chains)) {
146+
if (
147+
chain instanceof EvmChain &&
148+
chain.wormholeChainName ===
149+
instruction.governanceAction.targetChainId
150+
) {
151+
const executorAddress =
152+
instruction.governanceAction.executorAddress;
153+
const callAddress = instruction.governanceAction.callAddress;
154+
const calldata = instruction.governanceAction.calldata;
155+
156+
// currently executor is only being used by the entropy contract
157+
const contract = new EvmEntropyContract(chain, callAddress);
158+
const owner = await contract.getOwner();
159+
160+
if (
161+
executorAddress.toUpperCase() !==
162+
owner.replace("0x", "").toUpperCase()
163+
) {
164+
console.log(
165+
`Executor Address: ${executorAddress.toUpperCase()} is not equal to Owner Address: ${owner
166+
.replace("0x", "")
167+
.toUpperCase()}`
168+
);
169+
continue;
170+
}
171+
172+
const calldataHex = calldata.toString("hex");
173+
const web3 = new Web3();
174+
const methodSignature = web3.eth.abi
175+
.encodeFunctionSignature("upgradeTo(address)")
176+
.replace("0x", "");
177+
178+
let newImplementationAddress: string | undefined = undefined;
179+
if (calldataHex.startsWith(methodSignature)) {
180+
newImplementationAddress = web3.eth.abi.decodeParameter(
181+
"address",
182+
calldataHex.replace(methodSignature, "")
183+
) as unknown as string;
184+
}
185+
186+
if (newImplementationAddress === undefined) {
187+
console.log(
188+
`We couldn't parse the instruction for ${chain.getId()}`
189+
);
190+
continue;
191+
}
192+
193+
const newImplementationCode = await getCodeDigestWithoutAddress(
194+
chain.getRpcUrl(),
195+
newImplementationAddress
196+
);
197+
// this should be the same keccak256 of the deployedCode property generated by truffle
198+
console.log(
199+
`${chain.getId()} new implementation address:${newImplementationAddress} digest:${newImplementationCode}`
200+
);
201+
}
202+
}
203+
}
137204
}
138205
}
139206
}

contract_manager/scripts/upgrade_evm_executor_contracts.ts renamed to contract_manager/scripts/upgrade_evm_entropy_contracts.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,41 @@ import {
99
makeCacheFunction,
1010
} from "./common";
1111

12-
const CACHE_FILE = ".cache-upgrade-evm-executor-contract";
13-
const runIfNotCached = makeCacheFunction(CACHE_FILE);
12+
const EXECUTOR_CACHE_FILE = ".cache-upgrade-evm-executor-contract";
13+
const ENTROPY_CACHE_FILE = ".cache-upgrade-evm-entropy-contract";
1414

1515
const parser = yargs(hideBin(process.argv))
1616
.usage(
17-
"Deploys a new ExecutorUpgradeable contract to a set of chains where Entropy is deployed and creates a governance proposal for it.\n" +
18-
`Uses a cache file (${CACHE_FILE}) to avoid deploying contracts twice\n` +
17+
"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" +
18+
`Uses a cache file to avoid deploying contracts twice\n` +
1919
"Usage: $0 --chain <chain_1> --chain <chain_2> --private-key <private_key> --ops-key-path <ops_key_path> --std-output <std_output>"
2020
)
21-
.options(COMMON_UPGRADE_OPTIONS);
21+
.options({
22+
...COMMON_UPGRADE_OPTIONS,
23+
"contract-type": {
24+
type: "string",
25+
choices: ["executor", "entropy"],
26+
demandOption: true,
27+
},
28+
});
2229

2330
async function main() {
2431
const argv = await parser.argv;
32+
const cacheFile =
33+
argv["contract-type"] === "executor"
34+
? EXECUTOR_CACHE_FILE
35+
: ENTROPY_CACHE_FILE;
36+
37+
const runIfNotCached = makeCacheFunction(cacheFile);
38+
2539
const selectedChains = getSelectedChains(argv);
2640

2741
const vault =
2842
DefaultStore.vaults[
2943
"mainnet-beta_FVQyHcooAtThJ83XFrNnv74BcinbRH3bRmfFamAHBfuj"
3044
];
3145

32-
console.log("Using cache file", CACHE_FILE);
46+
console.log("Using cache file", cacheFile);
3347

3448
const payloads: Buffer[] = [];
3549
for (const contract of Object.values(DefaultStore.entropy_contracts)) {
@@ -51,9 +65,11 @@ async function main() {
5165
console.log(
5266
`Deployed contract at ${address} on ${contract.chain.getId()}`
5367
);
54-
const payload = await contract.generateUpgradeExecutorContractsPayload(
55-
address
56-
);
68+
const payload =
69+
argv["contract-type"] === "executor"
70+
? await contract.generateUpgradeExecutorContractsPayload(address)
71+
: await contract.generateUpgradeEntropyContractPayload(address);
72+
5773
console.log(payload.toString("hex"));
5874
payloads.push(payload);
5975
}

contract_manager/src/contracts/evm.ts

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ const EXTENDED_ENTROPY_ABI = [
6262
stateMutability: "pure",
6363
type: "function",
6464
},
65+
{
66+
inputs: [
67+
{
68+
internalType: "address",
69+
name: "newImplementation",
70+
type: "address",
71+
},
72+
],
73+
name: "upgradeTo",
74+
outputs: [],
75+
stateMutability: "nonpayable",
76+
type: "function",
77+
},
6578
...EntropyAbi,
6679
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
6780
const EXTENDED_PYTH_ABI = [
@@ -354,6 +367,29 @@ const EXECUTOR_ABI = [
354367
type: "function",
355368
},
356369
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
370+
371+
/**
372+
* Returns the keccak256 digest of the contract bytecode at the given address after replacing
373+
* any occurrences of the contract addr in the bytecode with 0.The bytecode stores the deployment
374+
* address as an immutable variable. This behavior is inherited from OpenZeppelin's implementation
375+
* of UUPSUpgradeable contract. You can read more about verification with immutable variables here:
376+
* https://docs.sourcify.dev/docs/immutables/
377+
* This function can be used to verify that the contract code is the same on all chains and matches
378+
* with the deployedCode property generated by truffle builds
379+
*/
380+
export async function getCodeDigestWithoutAddress(
381+
rpcUrl: string,
382+
address: string
383+
): Promise<string> {
384+
const web3 = new Web3(rpcUrl);
385+
const code = await web3.eth.getCode(address);
386+
const strippedCode = code.replaceAll(
387+
address.toLowerCase().replace("0x", ""),
388+
"0000000000000000000000000000000000000000"
389+
);
390+
return Web3.utils.keccak256(strippedCode);
391+
}
392+
357393
export class WormholeEvmContract extends WormholeContract {
358394
constructor(public chain: EvmChain, public address: string) {
359395
super();
@@ -480,6 +516,18 @@ export class EvmEntropyContract extends Storable {
480516
return this.generateExecutorPayload(newOwner, this.address, data);
481517
}
482518

519+
async generateUpgradeEntropyContractPayload(
520+
newImplementation: string
521+
): Promise<Buffer> {
522+
const contract = this.getContract();
523+
const data = contract.methods.upgradeTo(newImplementation).encodeABI();
524+
return this.generateExecutorPayload(
525+
await this.getOwner(),
526+
this.address,
527+
data
528+
);
529+
}
530+
483531
// Generates a payload to upgrade the executor contract, the owner of entropy contracts
484532
async generateUpgradeExecutorContractsPayload(
485533
newImplementation: string
@@ -708,21 +756,10 @@ export class EvmPriceFeedContract extends PriceFeedContract {
708756
}
709757

710758
/**
711-
* Returns the keccak256 digest of the contract bytecode after replacing any occurrences of the contract addr in
712-
* the bytecode with 0.The bytecode stores the deployment address as an immutable variable.
713-
* This behavior is inherited from OpenZeppelin's implementation of UUPSUpgradeable contract.
714-
* You can read more about verification with immutable variables here:
715-
* https://docs.sourcify.dev/docs/immutables/
716-
* This function can be used to verify that the contract code is the same on all chains and matches
717-
* with the deployedCode property generated by truffle builds
759+
* Returns the keccak256 digest of the contract bytecode
718760
*/
719761
async getCodeDigestWithoutAddress(): Promise<string> {
720-
const code = await this.getCode();
721-
const strippedCode = code.replaceAll(
722-
this.address.toLowerCase().replace("0x", ""),
723-
"0000000000000000000000000000000000000000"
724-
);
725-
return Web3.utils.keccak256(strippedCode);
762+
return getCodeDigestWithoutAddress(this.chain.getRpcUrl(), this.address);
726763
}
727764

728765
async getTotalFee(): Promise<TokenQty> {

0 commit comments

Comments
 (0)