Parcourir la source

[contract_manager] Add some nice-to-have features (#1650)

* gr

* some stuff

* comment

* gr

* cleanup
Jayant Krishnamurthy il y a 1 an
Parent
commit
1b8fee9022

+ 11 - 4
contract_manager/scripts/check_proposal.ts

@@ -140,7 +140,7 @@ 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}`
+          `Verifying EVMExecute on ${instruction.governanceAction.targetChainId}`
         );
         for (const chain of Object.values(DefaultStore.chains)) {
           if (
@@ -153,7 +153,8 @@ async function main() {
             const callAddress = instruction.governanceAction.callAddress;
             const calldata = instruction.governanceAction.calldata;
 
-            // currently executor is only being used by the entropy contract
+            // TODO: If we add additional EVM contracts using the executor, we need to
+            // add some logic here to identify what kind of contract is at the call address.
             const contract = new EvmEntropyContract(chain, callAddress);
             const owner = await contract.getOwner();
 
@@ -169,10 +170,13 @@ async function main() {
               continue;
             }
 
+            // TODO: This logic assumes we are calling upgradeTo on the contract at callAddress.
+            // In the future, this logic may need to be generalized to support calling other functions.
+            const invokedMethod = "upgradeTo(address)";
             const calldataHex = calldata.toString("hex");
             const web3 = new Web3();
             const methodSignature = web3.eth.abi
-              .encodeFunctionSignature("upgradeTo(address)")
+              .encodeFunctionSignature(invokedMethod)
               .replace("0x", "");
 
             let newImplementationAddress: string | undefined = undefined;
@@ -196,7 +200,10 @@ async function main() {
             );
             // this should be the same keccak256 of the deployedCode property generated by truffle
             console.log(
-              `${chain.getId()}  new implementation address:${newImplementationAddress} digest:${newImplementationCode}`
+              `${chain.getId()}  call ${invokedMethod} with arguments (${newImplementationAddress}) on ${contract.getType()} at address:${callAddress} from executor:${executorAddress}.`
+            );
+            console.log(
+              `${chain.getId()}    new implementation address:${newImplementationAddress} has code digest:${newImplementationCode}`
             );
           }
         }

+ 51 - 22
contract_manager/scripts/upgrade_evm_entropy_contracts.ts

@@ -2,6 +2,7 @@ import yargs from "yargs";
 import { hideBin } from "yargs/helpers";
 import { DefaultStore, loadHotWallet, toPrivateKey } from "../src";
 import { readFileSync } from "fs";
+import { PythCluster } from "@pythnetwork/client/lib/cluster";
 
 import {
   COMMON_UPGRADE_OPTIONS,
@@ -27,6 +28,18 @@ const parser = yargs(hideBin(process.argv))
     },
   });
 
+// Override these URLs to use a different RPC node for mainnet / testnet.
+// TODO: extract these RPCs to a config file (?)
+const RPCS = {
+  "mainnet-beta": "https://api.mainnet-beta.solana.com",
+  testnet: "https://api.testnet.solana.com",
+  devnet: "https://api.devnet.solana.com",
+} as Record<PythCluster, string>;
+
+function registry(cluster: PythCluster): string {
+  return RPCS[cluster];
+}
+
 async function main() {
   const argv = await parser.argv;
   const cacheFile =
@@ -45,40 +58,56 @@ async function main() {
 
   console.log("Using cache file", cacheFile);
 
+  // Try to deploy on every chain, then collect any failures at the end. This logic makes it simpler to
+  // identify deployment problems (e.g., not enough gas) on every chain where they occur.
   const payloads: Buffer[] = [];
+  const failures: string[] = [];
   for (const contract of Object.values(DefaultStore.entropy_contracts)) {
     if (selectedChains.includes(contract.chain)) {
       const artifact = JSON.parse(readFileSync(argv["std-output"], "utf8"));
       console.log("Deploying contract to", contract.chain.getId());
-      const address = await runIfNotCached(
-        `deploy-${contract.chain.getId()}`,
-        () => {
-          return contract.chain.deploy(
-            toPrivateKey(argv["private-key"]),
-            artifact["abi"],
-            artifact["bytecode"],
-            [],
-            2
-          );
-        }
-      );
-      console.log(
-        `Deployed contract at ${address} on ${contract.chain.getId()}`
-      );
-      const payload =
-        argv["contract-type"] === "executor"
-          ? await contract.generateUpgradeExecutorContractsPayload(address)
-          : await contract.generateUpgradeEntropyContractPayload(address);
+      try {
+        const address = await runIfNotCached(
+          `deploy-${contract.chain.getId()}`,
+          () => {
+            return contract.chain.deploy(
+              toPrivateKey(argv["private-key"]),
+              artifact["abi"],
+              artifact["bytecode"],
+              [],
+              2
+            );
+          }
+        );
+        console.log(
+          `Deployed contract at ${address} on ${contract.chain.getId()}`
+        );
+        const payload =
+          argv["contract-type"] === "executor"
+            ? await contract.generateUpgradeExecutorContractsPayload(address)
+            : await contract.generateUpgradeEntropyContractPayload(address);
 
-      console.log(payload.toString("hex"));
-      payloads.push(payload);
+        console.log(payload.toString("hex"));
+        payloads.push(payload);
+      } catch (e) {
+        console.log(`error deploying: ${e}`);
+        failures.push(contract.chain.getId());
+      }
     }
   }
 
+  if (failures.length > 0) {
+    throw new Error(
+      `Some chains could not be deployed: ${failures.join(
+        ", "
+      )}. Scroll up to see the errors from each chain.`
+    );
+  }
+
   console.log("Using vault at for proposal", vault.getId());
   const wallet = await loadHotWallet(argv["ops-key-path"]);
   console.log("Using wallet ", wallet.publicKey.toBase58());
-  await vault.connect(wallet);
+  vault.connect(wallet, registry);
   const proposal = await vault.proposeWormholeMessage(payloads);
   console.log("Proposal address", proposal.address.toBase58());
 }

+ 13 - 6
contract_manager/src/chains.ts

@@ -447,12 +447,19 @@ export class EvmChain extends Chain {
       );
     }
 
-    const deployedContract = await deployTx.send({
-      from: signer.address,
-      gas,
-      gasPrice: gasPrice.toString(),
-    });
-    return deployedContract.options.address;
+    try {
+      const deployedContract = await deployTx.send({
+        from: signer.address,
+        gas,
+        gasPrice: gasPrice.toString(),
+      });
+      return deployedContract.options.address;
+    } catch (e) {
+      // RPC errors often have useful information in the non-primary message field. Log the whole error
+      // to simplify identifying the problem.
+      console.log(`Error deploying contract: ${JSON.stringify(e)}`);
+      throw e;
+    }
   }
 
   async getAccountAddress(privateKey: PrivateKey): Promise<string> {

+ 8 - 0
contract_manager/src/governance.ts

@@ -58,6 +58,7 @@ export class SubmittedWormholeMessage {
    * Inspects the transaction logs to find the sequence number
    * @param signature signature of the transaction to inspect
    * @param cluster the cluster the transaction was submitted to
+   * @param registry registry of RPC nodes to use for each solana network. Defaults to the Solana public RPCs if not provided.
    */
   static async fromTransactionSignature(
     signature: string,
@@ -152,6 +153,11 @@ export class WormholeEmitter {
     return this.wallet.publicKey;
   }
 
+  /**
+   * Send a wormhole message containing payload through wormhole.
+   * @param payload the contents of the message
+   * @param registry registry of RPC nodes to use for each solana network. Defaults to the Solana public RPCs if not provided.
+   */
   async sendMessage(
     payload: Buffer,
     registry: SolanaRpcRegistry = getPythClusterApiUrl
@@ -299,6 +305,7 @@ export class Vault extends Storable {
    * Connects the vault to a wallet that can be used to submit proposals
    * The wallet should be a multisig signer of the vault
    * @param wallet
+   * @param registry registry of RPC nodes to use for each solana network. Defaults to the Solana public RPCs if not provided.
    */
   public connect(
     wallet: Wallet,
@@ -314,6 +321,7 @@ export class Vault extends Storable {
 
   /**
    * Gets the emitter address of the vault
+   * @param registry registry of RPC nodes to use for each solana network. Defaults to the Solana public RPCs if not provided.
    */
   public async getEmitter(registry: SolanaRpcRegistry = getPythClusterApiUrl) {
     const squad = SquadsMesh.endpoint(