Parcourir la source

add upgrade script to contract_manager (#2147)

Daniel Chew il y a 1 an
Parent
commit
4556454db4

+ 88 - 0
contract_manager/scripts/generate_upgrade_ton_contract_proposal.ts

@@ -0,0 +1,88 @@
+import yargs from "yargs";
+import { hideBin } from "yargs/helpers";
+import { DefaultStore, loadHotWallet } from "../src";
+import { TonChain } from "../src/chains";
+import { CHAINS, toChainName } from "@pythnetwork/xc-admin-common";
+import fs from "fs";
+import path from "path";
+
+const parser = yargs(hideBin(process.argv))
+  .usage(
+    "Upgrades the Pyth contract on TON and creates a governance proposal for it.\n" +
+      "Usage: $0 --network <mainnet|testnet> --contract-address <address> --ops-key-path <ops_key_path>"
+  )
+  .options({
+    network: {
+      type: "string",
+      choices: ["mainnet", "testnet"],
+      description: "Network to deploy to",
+      demandOption: true,
+    },
+    "contract-address": {
+      type: "string",
+      description: "Address of the contract to upgrade",
+      demandOption: true,
+    },
+    "ops-key-path": {
+      type: "string",
+      description: "Path to operations key file",
+      demandOption: true,
+    },
+  });
+
+async function main() {
+  const argv = await parser.argv;
+  const isMainnet = argv.network === "mainnet";
+
+  // Get chain ID and name from CHAINS mapping
+  const chainId = isMainnet ? CHAINS.ton_mainnet : CHAINS.ton_testnet;
+  const wormholeChainName = toChainName(chainId);
+
+  // Get the TON chain instance with appropriate RPC URL based on network
+  const chain = new TonChain(
+    chainId.toString(),
+    isMainnet,
+    wormholeChainName,
+    undefined,
+    isMainnet
+      ? "https://toncenter.com/api/v2/jsonRPC"
+      : "https://testnet.toncenter.com/api/v2/jsonRPC"
+  );
+
+  const vault =
+    DefaultStore.vaults[
+      "mainnet-beta_FVQyHcooAtThJ83XFrNnv74BcinbRH3bRmfFamAHBfuj"
+    ];
+
+  console.log(
+    `Upgrading contract on TON ${argv.network} (Chain ID: ${chainId}, Wormhole Chain Name: ${wormholeChainName})`
+  );
+
+  // Read the compiled contract from the build directory
+  // NOTE: Remember to rebuild contract_manager before running this script because it will also build the ton contract
+  const compiledPath = path.resolve(
+    __dirname,
+    "../../target_chains/ton/contracts/build/Main.compiled.json"
+  );
+  const compiled = JSON.parse(fs.readFileSync(compiledPath, "utf8"));
+  const newCodeHash = compiled.hash;
+  console.log("New code hash:", newCodeHash);
+
+  // Generate governance payload for the upgrade
+  const payload = chain.generateGovernanceUpgradePayload(newCodeHash);
+  console.log("Generated governance payload");
+  console.log("Payload:", payload);
+
+  // Create and submit governance proposal
+  console.log("Using vault for proposal:", vault.getId());
+  const keypair = await loadHotWallet(argv["ops-key-path"] as string);
+  console.log("Using wallet:", keypair.publicKey.toBase58());
+  vault.connect(keypair);
+  const proposal = await vault.proposeWormholeMessage([payload]);
+  console.log("Proposal address:", proposal.address.toBase58());
+}
+
+main().catch((error) => {
+  console.error("Error during upgrade:", error);
+  process.exit(1);
+});

+ 17 - 46
contract_manager/scripts/upgrade_ton_contract.ts

@@ -1,15 +1,14 @@
 import yargs from "yargs";
 import { hideBin } from "yargs/helpers";
-import { DefaultStore, loadHotWallet } from "../src";
-import { TonChain } from "../src/chains";
-import { CHAINS, toChainName } from "@pythnetwork/xc-admin-common";
+import { DefaultStore, TonPriceFeedContract, toPrivateKey } from "../src";
 import fs from "fs";
 import path from "path";
+import { Cell } from "@ton/ton";
 
 const parser = yargs(hideBin(process.argv))
   .usage(
     "Upgrades the Pyth contract on TON and creates a governance proposal for it.\n" +
-      "Usage: $0 --network <mainnet|testnet> --contract-address <address> --ops-key-path <ops_key_path>"
+      "Usage: $0 --network <mainnet|testnet> --contract <contract_name> --private-key <private_key>"
   )
   .options({
     network: {
@@ -18,45 +17,24 @@ const parser = yargs(hideBin(process.argv))
       description: "Network to deploy to",
       demandOption: true,
     },
-    "contract-address": {
+    contract: {
       type: "string",
-      description: "Address of the contract to upgrade",
+      description: "Contract name",
       demandOption: true,
     },
-    "ops-key-path": {
+    "private-key": {
       type: "string",
-      description: "Path to operations key file",
+      description: "Private key of the sender",
       demandOption: true,
     },
   });
 
 async function main() {
   const argv = await parser.argv;
-  const isMainnet = argv.network === "mainnet";
 
-  // Get chain ID and name from CHAINS mapping
-  const chainId = isMainnet ? CHAINS.ton_mainnet : CHAINS.ton_testnet;
-  const wormholeChainName = toChainName(chainId);
-
-  // Get the TON chain instance with appropriate RPC URL based on network
-  const chain = new TonChain(
-    chainId.toString(),
-    isMainnet,
-    wormholeChainName,
-    undefined,
-    isMainnet
-      ? "https://toncenter.com/api/v2/jsonRPC"
-      : "https://testnet.toncenter.com/api/v2/jsonRPC"
-  );
-
-  const vault =
-    DefaultStore.vaults[
-      "mainnet-beta_FVQyHcooAtThJ83XFrNnv74BcinbRH3bRmfFamAHBfuj"
-    ];
-
-  console.log(
-    `Upgrading contract on TON ${argv.network} (Chain ID: ${chainId}, Wormhole Chain Name: ${wormholeChainName})`
-  );
+  const contract = DefaultStore.contracts[
+    argv.contract
+  ] as TonPriceFeedContract;
 
   // Read the compiled contract from the build directory
   // NOTE: Remember to rebuild contract_manager before running this script because it will also build the ton contract
@@ -65,21 +43,14 @@ async function main() {
     "../../target_chains/ton/contracts/build/Main.compiled.json"
   );
   const compiled = JSON.parse(fs.readFileSync(compiledPath, "utf8"));
-  const newCodeHash = compiled.hash;
-  console.log("New code hash:", newCodeHash);
-
-  // Generate governance payload for the upgrade
-  const payload = chain.generateGovernanceUpgradePayload(newCodeHash);
-  console.log("Generated governance payload");
-  console.log("Payload:", payload);
+  const newCode = Cell.fromHex(compiled.hex);
+  console.log(newCode);
 
-  // Create and submit governance proposal
-  console.log("Using vault for proposal:", vault.getId());
-  const keypair = await loadHotWallet(argv["ops-key-path"] as string);
-  console.log("Using wallet:", keypair.publicKey.toBase58());
-  vault.connect(keypair);
-  const proposal = await vault.proposeWormholeMessage([payload]);
-  console.log("Proposal address:", proposal.address.toBase58());
+  const tx = await contract.upgradeContract(
+    toPrivateKey(argv["private-key"]),
+    newCode
+  );
+  console.log("Upgrade transaction:", tx);
 }
 
 main().catch((error) => {

+ 25 - 1
contract_manager/src/contracts/ton.ts

@@ -3,7 +3,7 @@ import { WormholeContract } from "./wormhole";
 import { PriceFeed, PriceFeedContract, PrivateKey, TxResult } from "../base";
 import { TokenQty } from "../token";
 import { DataSource } from "@pythnetwork/xc-admin-common";
-import { Address, OpenedContract } from "@ton/ton";
+import { Address, Cell, OpenedContract } from "@ton/ton";
 import {
   calculateUpdatePriceFeedsFee,
   PythContract,
@@ -279,6 +279,30 @@ export class TonPriceFeedContract extends PriceFeedContract {
     };
   }
 
+  async upgradeContract(
+    senderPrivateKey: PrivateKey,
+    newCode: Cell
+  ): Promise<TxResult> {
+    const client = await this.chain.getClient();
+    const contract = await this.getContract();
+    const wallet = await this.chain.getWallet(senderPrivateKey);
+    const sender = await this.chain.getSender(senderPrivateKey);
+    await contract.sendUpgradeContract(sender, newCode);
+
+    const txDetails = await client.getTransactions(wallet.address, {
+      limit: 1,
+    });
+    const txHash = Buffer.from(txDetails[0].hash()).toString("hex");
+    const txInfo = JSON.stringify(txDetails[0].description, (_, value) =>
+      typeof value === "bigint" ? value.toString() : value
+    );
+
+    return {
+      id: txHash,
+      info: txInfo,
+    };
+  }
+
   toJson() {
     return {
       chain: this.chain.getId(),

+ 1 - 1
target_chains/ton/sdk/js/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@pythnetwork/pyth-ton-js",
-  "version": "0.1.1",
+  "version": "0.1.2",
   "description": "Pyth Network TON Utilities",
   "homepage": "https://pyth.network",
   "author": {

+ 17 - 0
target_chains/ton/sdk/js/src/index.ts

@@ -93,6 +93,23 @@ export class PythContract implements Contract {
     });
   }
 
+  async sendUpgradeContract(
+    provider: ContractProvider,
+    via: Sender,
+    newCode: Cell
+  ) {
+    const messageBody = beginCell()
+      .storeUint(4, 32) // OP_UPGRADE_CONTRACT
+      .storeRef(newCode)
+      .endCell();
+
+    await provider.internal(via, {
+      value: toNano("0.1"),
+      sendMode: SendMode.PAY_GAS_SEPARATELY,
+      body: messageBody,
+    });
+  }
+
   async getPriceUnsafe(provider: ContractProvider, priceFeedId: string) {
     const result = await provider.get("get_price_unsafe", [
       { type: "int", value: BigInt(priceFeedId) },