Parcourir la source

[contract_manager] support for executor (#1268)

* [contract_manager] support for executor

* refactor

* clean up

* fix ci

* rename method
Dev Kalra il y a 1 an
Parent
commit
c330e40277
2 fichiers modifiés avec 184 ajouts et 36 suppressions
  1. 113 15
      contract_manager/src/contracts/evm.ts
  2. 71 21
      contract_manager/src/executor.ts

+ 113 - 15
contract_manager/src/contracts/evm.ts

@@ -183,21 +183,6 @@ const EXTENDED_PYTH_ABI = [
   },
   ...PythInterfaceAbi,
 ] as any; // eslint-disable-line  @typescript-eslint/no-explicit-any
-const EXECUTOR_ABI = [
-  {
-    inputs: [
-      {
-        internalType: "address",
-        name: "newImplementation",
-        type: "address",
-      },
-    ],
-    name: "upgradeTo",
-    outputs: [],
-    stateMutability: "nonpayable",
-    type: "function",
-  },
-] as any; // eslint-disable-line  @typescript-eslint/no-explicit-any
 const WORMHOLE_ABI = [
   {
     inputs: [],
@@ -283,6 +268,66 @@ const WORMHOLE_ABI = [
     type: "function",
   },
 ] as any; // eslint-disable-line  @typescript-eslint/no-explicit-any
+const EXECUTOR_ABI = [
+  {
+    inputs: [
+      {
+        internalType: "bytes",
+        name: "encodedVm",
+        type: "bytes",
+      },
+    ],
+    name: "execute",
+    outputs: [
+      {
+        internalType: "bytes",
+        name: "response",
+        type: "bytes",
+      },
+    ],
+    stateMutability: "nonpayable",
+    type: "function",
+  },
+  {
+    inputs: [],
+    name: "getOwnerChainId",
+    outputs: [
+      {
+        internalType: "uint64",
+        name: "",
+        type: "uint64",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+  {
+    inputs: [],
+    name: "getOwnerEmitterAddress",
+    outputs: [
+      {
+        internalType: "bytes32",
+        name: "",
+        type: "bytes32",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+  {
+    inputs: [],
+    name: "getLastExecutedSequence",
+    outputs: [
+      {
+        internalType: "uint64",
+        name: "",
+        type: "uint64",
+      },
+    ],
+    stateMutability: "view",
+    type: "function",
+  },
+] as any; // eslint-disable-line  @typescript-eslint/no-explicit-any
 export class WormholeEvmContract extends WormholeContract {
   constructor(public chain: EvmChain, public address: string) {
     super();
@@ -454,6 +499,59 @@ export class EvmEntropyContract extends Storable {
   }
 }
 
+export class EvmExecutorContract {
+  constructor(public chain: EvmChain, public address: string) {}
+
+  getId(): string {
+    return `${this.chain.getId()}_${this.address}`;
+  }
+
+  getContract() {
+    const web3 = new Web3(this.chain.getRpcUrl());
+    return new web3.eth.Contract(EXECUTOR_ABI, this.address);
+  }
+
+  async getLastExecutedGovernanceSequence() {
+    return await this.getContract().methods.getLastExecutedSequence().call();
+  }
+
+  async getGovernanceDataSource(): Promise<DataSource> {
+    const executorContract = this.getContract();
+    const ownerEmitterAddress = await executorContract.methods
+      .getOwnerEmitterAddress()
+      .call();
+    const ownerEmitterChainid = await executorContract.methods
+      .getOwnerChainId()
+      .call();
+    return {
+      emitterChain: Number(ownerEmitterChainid),
+      emitterAddress: ownerEmitterAddress.replace("0x", ""),
+    };
+  }
+
+  async executeGovernanceInstruction(
+    senderPrivateKey: PrivateKey,
+    vaa: Buffer
+  ) {
+    const web3 = new Web3(this.chain.getRpcUrl());
+    const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
+    const executorContract = new web3.eth.Contract(EXECUTOR_ABI, this.address);
+    const transactionObject = executorContract.methods.execute(
+      "0x" + vaa.toString("hex")
+    );
+    const gasEstimate = await transactionObject.estimateGas({
+      from: address,
+      gas: 100000000,
+    });
+    const result = await transactionObject.send({
+      from: address,
+      gas: gasEstimate * GAS_ESTIMATE_MULTIPLIER,
+      gasPrice: await this.chain.getGasPrice(),
+    });
+    return { id: result.transactionHash, info: result };
+  }
+}
+
 export class EvmPriceFeedContract extends PriceFeedContract {
   static type = "EvmPriceFeedContract";
 

+ 71 - 21
contract_manager/src/executor.ts

@@ -1,7 +1,53 @@
 import { parseVaa } from "@certusone/wormhole-sdk";
-import { decodeGovernancePayload } from "xc_admin_common";
+import {
+  DataSource,
+  EvmExecute,
+  decodeGovernancePayload,
+} from "xc_admin_common";
 import { DefaultStore } from "./store";
-import { PrivateKey } from "./base";
+import { PrivateKey, TxResult } from "./base";
+import { EvmExecutorContract } from "./contracts";
+import { EvmChain } from "./chains";
+
+// TODO: A better place for this would be `base.ts`. That will require
+// significant refactor. Todo in separate PR.
+interface GovernanceContract {
+  getId(): string;
+  getGovernanceDataSource(): Promise<DataSource>;
+  getLastExecutedGovernanceSequence(): Promise<number>;
+  executeGovernanceInstruction(
+    senderPrivateKey: PrivateKey,
+    vaa: Buffer
+  ): Promise<TxResult>;
+}
+
+async function executeForGovernanceContract(
+  contract: GovernanceContract,
+  vaa: Buffer,
+  senderPrivateKey: PrivateKey
+) {
+  const parsedVaa = parseVaa(vaa);
+  const governanceSource = await contract.getGovernanceDataSource();
+  if (
+    governanceSource.emitterAddress ===
+      parsedVaa.emitterAddress.toString("hex") &&
+    governanceSource.emitterChain === parsedVaa.emitterChain
+  ) {
+    const lastExecutedSequence =
+      await contract.getLastExecutedGovernanceSequence();
+    if (lastExecutedSequence >= parsedVaa.sequence) {
+      console.log(
+        `Skipping on contract ${contract.getId()} as it was already executed`
+      );
+      return;
+    }
+    const { id } = await contract.executeGovernanceInstruction(
+      senderPrivateKey,
+      vaa
+    );
+    console.log(`Executed on contract ${contract.getId()} with txHash: ${id}`);
+  }
+}
 
 /**
  * A general executor that tries to find any contract that can execute a given VAA and executes it
@@ -12,28 +58,32 @@ export async function executeVaa(senderPrivateKey: PrivateKey, vaa: Buffer) {
   const parsedVaa = parseVaa(vaa);
   const action = decodeGovernancePayload(parsedVaa.payload);
   if (!action) return; //TODO: handle other actions
-  for (const contract of Object.values(DefaultStore.contracts)) {
-    if (
-      action.targetChainId === "unset" ||
-      contract.getChain().wormholeChainName === action.targetChainId
-    ) {
-      const governanceSource = await contract.getGovernanceDataSource();
+
+  if (action instanceof EvmExecute) {
+    for (const chain of Object.values(DefaultStore.chains)) {
       if (
-        governanceSource.emitterAddress ===
-          parsedVaa.emitterAddress.toString("hex") &&
-        governanceSource.emitterChain === parsedVaa.emitterChain
+        chain instanceof EvmChain &&
+        chain.wormholeChainName === action.targetChainId
       ) {
-        const lastExecutedSequence =
-          await contract.getLastExecutedGovernanceSequence();
-        if (lastExecutedSequence >= parsedVaa.sequence) {
-          console.log(
-            `Skipping on contract ${contract.getId()} as it was already executed`
-          );
-          continue;
-        }
-        await contract.executeGovernanceInstruction(senderPrivateKey, vaa);
-        console.log(`Executed on contract ${contract.getId()}`);
+        const executorContract = new EvmExecutorContract(
+          chain,
+          action.executorAddress
+        );
+
+        await executeForGovernanceContract(
+          executorContract,
+          vaa,
+          senderPrivateKey
+        );
       }
     }
+  } else {
+    for (const contract of Object.values(DefaultStore.contracts)) {
+      if (
+        action.targetChainId === "unset" ||
+        contract.getChain().wormholeChainName === action.targetChainId
+      )
+        await executeForGovernanceContract(contract, vaa, senderPrivateKey);
+    }
   }
 }