Преглед на файлове

feat(contract_manager): add Movement deployment (#1586)

Reisen преди 1 година
родител
ревизия
2df53c9c82

+ 19 - 0
contract_manager/scripts/sync_wormhole_guardian_set.ts

@@ -4,6 +4,7 @@ import {
   CosmWasmPriceFeedContract,
   DefaultStore,
   EvmPriceFeedContract,
+  SuiWormholeContract,
   toPrivateKey,
 } from "../src";
 
@@ -27,6 +28,24 @@ async function main() {
   const privateKey = toPrivateKey(argv.privateKey);
   const chains = argv.chain;
 
+  for (const contract of Object.values(DefaultStore.wormhole_contracts)) {
+    if (contract instanceof SuiWormholeContract) {
+      if (chains && !chains.includes(contract.getChain().getId())) {
+        continue;
+      }
+
+      try {
+        let index = await contract.getCurrentGuardianSetIndex();
+        console.log("Guardian Index at Start:", index);
+        await contract.syncMainnetGuardianSets(privateKey);
+        index = await contract.getCurrentGuardianSetIndex();
+        console.log("Guardian Index at End:", index);
+      } catch (e) {
+        console.error(`Error updating Guardianset for ${contract.getId()}`, e);
+      }
+    }
+  }
+
   for (const contract of Object.values(DefaultStore.contracts)) {
     // We are currently only managing wormhole receiver contracts in EVM and
     // CosmWasm and Solana-based networks. The rest of the networks are

+ 167 - 0
contract_manager/src/contracts/sui.ts

@@ -1,10 +1,12 @@
 import { Chain, SuiChain } from "../chains";
 import { DataSource } from "@pythnetwork/xc-admin-common";
+import { WormholeContract } from "./wormhole";
 import { PriceFeedContract, PrivateKey, TxResult } from "../base";
 import { SuiPythClient } from "@pythnetwork/pyth-sui-js";
 import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui.js/utils";
 import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
 import { TransactionBlock } from "@mysten/sui.js/transactions";
+import { uint8ArrayToBCS } from "@certusone/wormhole-sdk/lib/cjs/sui";
 
 type ObjectId = string;
 
@@ -405,3 +407,168 @@ export class SuiPriceFeedContract extends PriceFeedContract {
     return result.data.content.fields;
   }
 }
+
+export class SuiWormholeContract extends WormholeContract {
+  public static type = "SuiWormholeContract";
+  private client: SuiPythClient;
+
+  getId(): string {
+    return `${this.chain.getId()}_${this.address}`;
+  }
+
+  getType(): string {
+    return SuiWormholeContract.type;
+  }
+
+  toJson() {
+    return {
+      chain: this.chain.getId(),
+      address: this.address,
+      type: SuiWormholeContract.type,
+    };
+  }
+
+  static fromJson(
+    chain: Chain,
+    parsed: {
+      type: string;
+      address: string;
+      stateId: string;
+    }
+  ): SuiWormholeContract {
+    if (parsed.type !== SuiWormholeContract.type)
+      throw new Error("Invalid type");
+    if (!(chain instanceof SuiChain))
+      throw new Error(`Wrong chain type ${chain}`);
+    return new SuiWormholeContract(chain, parsed.address, parsed.stateId);
+  }
+
+  constructor(
+    public chain: SuiChain,
+    public address: string,
+    public stateId: string
+  ) {
+    super();
+    this.client = new SuiPythClient(
+      this.chain.getProvider(),
+      // HACK:
+      // We're using the SuiPythClient to work with the Wormhole contract
+      // so there is no Pyth contract here, passing empty string to type-
+      // check.
+      "",
+      this.stateId
+    );
+  }
+
+  async getCurrentGuardianSetIndex(): Promise<number> {
+    const data = await this.getStateFields();
+    return Number(data.guardian_set_index);
+  }
+
+  // There doesn't seem to be a way to get a value out of any function call
+  // via a Sui transaction due to the linear nature of the language, this is
+  // enforced at the TransactionBlock level by only allowing you to receive
+  // receipts.
+  async getChainId(): Promise<number> {
+    return this.chain.getWormholeChainId();
+  }
+
+  // NOTE: There's no way to getChain() on the main interface, should update
+  // that interface.
+  public getChain(): SuiChain {
+    return this.chain;
+  }
+
+  async getGuardianSet(): Promise<string[]> {
+    const data = await this.getStateFields();
+    const guardian_sets = data.guardian_sets;
+    return guardian_sets;
+  }
+
+  async upgradeGuardianSets(
+    senderPrivateKey: PrivateKey,
+    vaa: Buffer
+  ): Promise<TxResult> {
+    const tx = new TransactionBlock();
+    const coreObjectId = this.stateId;
+    const corePackageId = await this.client.getWormholePackageId();
+    const [verifiedVaa] = tx.moveCall({
+      target: `${corePackageId}::vaa::parse_and_verify`,
+      arguments: [
+        tx.object(coreObjectId),
+        tx.pure(uint8ArrayToBCS(vaa)),
+        tx.object(SUI_CLOCK_OBJECT_ID),
+      ],
+    });
+
+    const [decreeTicket] = tx.moveCall({
+      target: `${corePackageId}::update_guardian_set::authorize_governance`,
+      arguments: [tx.object(coreObjectId)],
+    });
+
+    const [decreeReceipt] = tx.moveCall({
+      target: `${corePackageId}::governance_message::verify_vaa`,
+      arguments: [tx.object(coreObjectId), verifiedVaa, decreeTicket],
+      typeArguments: [
+        `${corePackageId}::update_guardian_set::GovernanceWitness`,
+      ],
+    });
+
+    tx.moveCall({
+      target: `${corePackageId}::update_guardian_set::update_guardian_set`,
+      arguments: [
+        tx.object(coreObjectId),
+        decreeReceipt,
+        tx.object(SUI_CLOCK_OBJECT_ID),
+      ],
+    });
+
+    const keypair = Ed25519Keypair.fromSecretKey(
+      Buffer.from(senderPrivateKey, "hex")
+    );
+    const result = await this.executeTransaction(tx, keypair);
+    return { id: result.digest, info: result };
+  }
+
+  private async getStateFields(): Promise<any> {
+    const provider = this.chain.getProvider();
+    const result = await provider.getObject({
+      id: this.stateId,
+      options: { showContent: true },
+    });
+    if (
+      !result.data ||
+      !result.data.content ||
+      result.data.content.dataType !== "moveObject"
+    )
+      throw new Error("Unable to fetch pyth state object");
+    return result.data.content.fields;
+  }
+
+  /**
+   * Given a transaction block and a keypair, sign and execute it
+   * Sets the gas budget to 2x the estimated gas cost
+   * @param tx
+   * @param keypair
+   * @private
+   */
+  private async executeTransaction(
+    tx: TransactionBlock,
+    keypair: Ed25519Keypair
+  ) {
+    const provider = this.chain.getProvider();
+    tx.setSender(keypair.toSuiAddress());
+    const dryRun = await provider.dryRunTransactionBlock({
+      transactionBlock: await tx.build({ client: provider }),
+    });
+    tx.setGasBudget(BigInt(dryRun.input.gasData.budget.toString()) * BigInt(2));
+    return provider.signAndExecuteTransactionBlock({
+      signer: keypair,
+      transactionBlock: tx,
+      options: {
+        showEffects: true,
+        showEvents: true,
+      },
+    });
+  }
+}

+ 2 - 0
contract_manager/src/store.ts

@@ -15,6 +15,7 @@ import {
   EvmPriceFeedContract,
   EvmWormholeContract,
   SuiPriceFeedContract,
+  SuiWormholeContract,
   WormholeContract,
 } from "./contracts";
 import { Token } from "./token";
@@ -122,6 +123,7 @@ export class Store {
       [CosmWasmPriceFeedContract.type]: CosmWasmPriceFeedContract,
       [CosmWasmWormholeContract.type]: CosmWasmWormholeContract,
       [SuiPriceFeedContract.type]: SuiPriceFeedContract,
+      [SuiWormholeContract.type]: SuiWormholeContract,
       [EvmPriceFeedContract.type]: EvmPriceFeedContract,
       [AptosPriceFeedContract.type]: AptosPriceFeedContract,
       [AptosWormholeContract.type]: AptosWormholeContract,

+ 5 - 0
contract_manager/store/chains/SuiChains.yaml

@@ -13,3 +13,8 @@
   mainnet: true
   rpcUrl: https://fullnode.mainnet.sui.io:443
   type: SuiChain
+- id: movement_devnet_m2
+  wormholeChainName: movement_devnet_m2
+  mainnet: false
+  rpcUrl: https://sui.devnet.m2.movementlabs.xyz:443
+  type: SuiChain

+ 4 - 0
contract_manager/store/contracts/SuiPriceFeedContracts.yaml

@@ -6,3 +6,7 @@
   stateId: "0x2d82612a354f0b7e52809fc2845642911c7190404620cec8688f68808f8800d8"
   wormholeStateId: "0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02"
   type: SuiPriceFeedContract
+- chain: movement_devnet_m2
+  stateId: "0xa2b4997fe170d5d7d622d5f43e54880ccdf69716df4ac4d215a69c35a0a1831f"
+  wormholeStateId: "0xcf185fbc1af3a437a600587e0b39e5fede163336ffbb7ff24dca9b6eb19d2656"
+  type: SuiPriceFeedContract

+ 4 - 0
contract_manager/store/contracts/SuiWormholeContracts.yaml

@@ -0,0 +1,4 @@
+- chain: movement_devnet_m2
+  type: SuiWormholeContract
+  address: "0x23a373b70e6e23a39e4846fa6896fa12beb08da061b3d4ec856bc8ead54f1e22"
+  stateId: "0xcf185fbc1af3a437a600587e0b39e5fede163336ffbb7ff24dca9b6eb19d2656"

+ 1 - 1
governance/xc_admin/packages/xc_admin_common/src/chains.ts

@@ -151,7 +151,7 @@ export const RECEIVER_CHAINS = {
   orange_testnet: 50073,
   polygon_amoy: 50074,
   starknet_sepolia: 50075,
-  // sui_l2: 50076,
+  movement_devnet_m2: 50076,
   taiko_mainnet: 50077,
   sei_evm_mainnet: 50078,
 };

+ 21 - 0
target_chains/sui/contracts/Move.movement_devnet_m2.toml

@@ -0,0 +1,21 @@
+[package]
+name = "Pyth"
+version = "0.0.2"
+
+[dependencies.Sui]
+git = "https://github.com/MystenLabs/sui.git"
+subdir = "crates/sui-framework/packages/sui-framework"
+rev = "041c5f2bae2fe52079e44b70514333532d69f4e6"
+
+[dependencies.Wormhole]
+git = "https://github.com/wormhole-foundation/wormhole.git"
+subdir = "sui/wormhole"
+rev = "sui-upgrade-testnet"
+
+[addresses]
+pyth = "0x0"
+wormhole = "0x23a373b70e6e23a39e4846fa6896fa12beb08da061b3d4ec856bc8ead54f1e22"
+
+[dev-addresses]
+pyth = "0x100"
+wormhole = "0x200"