Răsfoiți Sursa

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
Dev Kalra 1 an în urmă
părinte
comite
34d94e3177

+ 67 - 0
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}`
+            );
+          }
+        }
+      }
     }
   }
 }

+ 25 - 9
contract_manager/scripts/upgrade_evm_executor_contracts.ts → 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_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);
     }

+ 50 - 13
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<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> {