diff --git a/contract_manager/scripts/generate_upgrade_ton_contract_proposal.ts b/contract_manager/scripts/generate_upgrade_ton_contract_proposal.ts new file mode 100644 index 0000000000..5fa35f3151 --- /dev/null +++ b/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 --contract-address
--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); +}); diff --git a/contract_manager/scripts/upgrade_ton_contract.ts b/contract_manager/scripts/upgrade_ton_contract.ts index 5fa35f3151..1f222ecc5e 100644 --- a/contract_manager/scripts/upgrade_ton_contract.ts +++ b/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 --contract-address
--ops-key-path " + "Usage: $0 --network --contract --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) => { diff --git a/contract_manager/src/contracts/ton.ts b/contract_manager/src/contracts/ton.ts index 4ef19687b1..345f5e102c 100644 --- a/contract_manager/src/contracts/ton.ts +++ b/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 { + 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(), diff --git a/target_chains/ton/sdk/js/package.json b/target_chains/ton/sdk/js/package.json index bcf9dd682e..afcf8c1f0c 100644 --- a/target_chains/ton/sdk/js/package.json +++ b/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": { diff --git a/target_chains/ton/sdk/js/src/index.ts b/target_chains/ton/sdk/js/src/index.ts index b4517bac05..b42b42cbc1 100644 --- a/target_chains/ton/sdk/js/src/index.ts +++ b/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) },