| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- import * as mock from "@certusone/wormhole-sdk/lib/cjs/mock";
- import dotenv from "dotenv";
- import {
- RawSigner,
- SUI_CLOCK_OBJECT_ID,
- TransactionBlock,
- fromB64,
- normalizeSuiObjectId,
- JsonRpcProvider,
- Ed25519Keypair,
- testnetConnection,
- Connection,
- } from "@mysten/sui.js";
- import { execSync } from "child_process";
- import { resolve } from "path";
- import * as fs from "fs";
- import { REGISTRY, NETWORK } from "../registry";
- import { modifySignTransaction } from "@certusone/wormhole-sdk/lib/cjs/solana";
- dotenv.config({ path: "~/.env" });
- // Network dependent settings.
- let network = NETWORK.TESTNET; // <= NOTE: Update this when changing network
- const walletPrivateKey = process.env.SUI_TESTNET_ALT_KEY_BASE_64; // <= NOTE: Update this when changing network
- const guardianPrivateKey = process.env.WH_TESTNET_GUARDIAN_PRIVATE_KEY;
- const registry = REGISTRY[network];
- const provider = new JsonRpcProvider(
- new Connection({ fullnode: registry["RPC_URL"] })
- );
- const PYTH_STATE_ID = registry["PYTH_STATE_ID"];
- const PYTH_PACKAGE_ID = registry["PYTH_PACKAGE_ID"];
- const WORMHOLE_STATE_ID = registry["WORMHOLE_STATE_ID"];
- const WORMHOLE_PACKAGE_ID = registry["WORMHOLE_PACKAGE_ID"];
- console.log("WORMHOLE_STATE_ID: ", WORMHOLE_STATE_ID);
- console.log("PYTH_STATE_ID: ", WORMHOLE_STATE_ID);
- const GOVERNANCE_EMITTER =
- //"0000000000000000000000000000000000000000000000000000000000000004";
- "63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385";
- // To upgrade Pyth, take the following steps.
- // 0. Make contract changes in the "contracts" folder. These updated contracts will be posted on chain as an
- // entirely new package. The old package will still be valid unless we "brick" its call-sites explicitly
- // (this is done for you via the version control logic built into the Pyth contracts).
- // 1. Make sure that in version_control.move, you create a new struct for the new version and update the
- // current_version() and previous_version() functions accordingly. The former should point to the new version,
- // and the latter should point to the old version.
- // 2. Update the Move.toml file so that it points to a wormhole dependency whose Move.toml file has a "published-at" field
- // specified at the top with the correct address.
- // 3. Execute this script!
- //
- async function main() {
- if (guardianPrivateKey === undefined) {
- throw new Error("TESTNET_GUARDIAN_PRIVATE_KEY unset in environment");
- }
- if (walletPrivateKey === undefined) {
- throw new Error("TESTNET_WALLET_PRIVATE_KEY unset in environment");
- }
- console.log("priv key: ", walletPrivateKey);
- const wallet = new RawSigner(
- Ed25519Keypair.fromSecretKey(
- network == "MAINNET"
- ? Buffer.from(walletPrivateKey, "hex")
- : Buffer.from(walletPrivateKey, "base64")
- ),
- provider
- );
- console.log("wallet address: ", wallet.getAddress());
- const pythContractsPath = resolve(`${__dirname}/../../contracts`);
- // Build for digest.
- const { modules, dependencies, digest } =
- buildForBytecodeAndDigest(pythContractsPath);
- console.log("dependencies", dependencies);
- console.log("digest", digest.toString("hex"));
- // ===========================================================================================
- // Construct VAA. We will use the signed VAA when we execute the upgrade.
- // For a mainnet contract upgrade, we would not construct AND sign the VAA here. Instead, all
- // the guardians would have to sign the upgrade VAA.
- const guardians = new mock.MockGuardians(0, [guardianPrivateKey]);
- const timestamp = 12345678;
- const governance = new mock.GovernanceEmitter(GOVERNANCE_EMITTER);
- const action = 0;
- const chain = 21;
- // construct VAA inner payload
- const magic = Buffer.alloc(4);
- magic.write("PTGM", 0); // magic
- console.log("magic buffer: ", magic);
- let inner_payload = Buffer.alloc(8); // 4 (magic) + 1 (module name) + 1 (action) + 2 (target chain) = 8
- inner_payload.write(magic.toString(), 0); // magic = "PTGM"
- inner_payload.writeUInt8(1, 4); // moduleName = 1
- inner_payload.writeUInt8(0, 5); // action = 0
- inner_payload.writeUInt16BE(21, 6); // target chain = 21
- inner_payload = Buffer.concat([inner_payload, digest]);
- console.log("digest: ", digest.toString("hex"));
- console.log("inner payload: ", inner_payload.toString("hex"));
- // create governance message
- let msg = governance.publishGovernanceMessage(
- timestamp,
- "",
- inner_payload,
- action,
- chain
- );
- msg.writeUInt8(0x1, 84 - 33 + 31); // here we insert an 0x1 in the right place to make the module name "0x00000000000000000000000000000001"
- console.log("governance msg: ", msg.toString("hex"));
- // sign governance message
- const signedVaa = guardians.addSignatures(msg, [0]);
- console.log("Upgrade VAA:", signedVaa.toString("hex"));
- // ===========================================================================================
- //Execute upgrade with signed governance VAA.
- const upgradeResults = await upgradePyth(
- wallet,
- PYTH_STATE_ID,
- WORMHOLE_STATE_ID,
- modules,
- dependencies,
- signedVaa
- );
- console.log("tx digest", upgradeResults.digest);
- console.log("tx effects", JSON.stringify(upgradeResults.effects!));
- console.log("tx events", JSON.stringify(upgradeResults.events!));
- const migrateResults = await migratePyth(
- wallet,
- PYTH_STATE_ID,
- WORMHOLE_STATE_ID,
- signedVaa
- );
- console.log("tx digest", migrateResults.digest);
- console.log("tx effects", JSON.stringify(migrateResults.effects!));
- console.log("tx events", JSON.stringify(migrateResults.events!));
- }
- main();
- function buildForBytecodeAndDigest(packagePath: string) {
- const buildOutput: {
- modules: string[];
- dependencies: string[];
- digest: number[];
- } = JSON.parse(
- execSync(
- `sui move build --dump-bytecode-as-base64 -p ${packagePath} 2> /dev/null`,
- { encoding: "utf-8" }
- )
- );
- return {
- modules: buildOutput.modules.map((m: string) => Array.from(fromB64(m))),
- dependencies: buildOutput.dependencies.map((d: string) =>
- normalizeSuiObjectId(d)
- ),
- digest: Buffer.from(buildOutput.digest),
- };
- }
- async function getPackageId(
- provider: JsonRpcProvider,
- stateId: string
- ): Promise<string> {
- const state = await provider
- .getObject({
- id: stateId,
- options: {
- showContent: true,
- },
- })
- .then((result) => {
- if (result.data?.content?.dataType == "moveObject") {
- return result.data.content.fields;
- }
- throw new Error("not move object");
- });
- if ("upgrade_cap" in state) {
- return state.upgrade_cap.fields.package;
- }
- throw new Error("upgrade_cap not found");
- }
- async function upgradePyth(
- signer: RawSigner,
- pythStateId: string,
- wormholeStateId: string,
- modules: number[][],
- dependencies: string[],
- signedVaa: Buffer
- ) {
- const pythPackage = await getPackageId(signer.provider, pythStateId);
- const wormholePackage = await getPackageId(signer.provider, wormholeStateId);
- console.log("pythPackage: ", pythPackage);
- console.log("wormholePackage: ", wormholePackage);
- const tx = new TransactionBlock();
- const [verifiedVaa] = tx.moveCall({
- target: `${wormholePackage}::vaa::parse_and_verify`,
- arguments: [
- tx.object(wormholeStateId),
- tx.pure(Array.from(signedVaa)),
- tx.object(SUI_CLOCK_OBJECT_ID),
- ],
- });
- const [decreeTicket] = tx.moveCall({
- target: `${pythPackage}::contract_upgrade::authorize_governance`,
- arguments: [tx.object(pythStateId)],
- });
- const [decreeReceipt] = tx.moveCall({
- target: `${wormholePackage}::governance_message::verify_vaa`,
- arguments: [tx.object(wormholeStateId), verifiedVaa, decreeTicket],
- typeArguments: [`${pythPackage}::governance_witness::GovernanceWitness`],
- });
- // Authorize upgrade.
- const [upgradeTicket] = tx.moveCall({
- target: `${pythPackage}::contract_upgrade::authorize_upgrade`,
- arguments: [tx.object(pythStateId), decreeReceipt],
- });
- // Build and generate modules and dependencies for upgrade.
- const [upgradeReceipt] = tx.upgrade({
- modules,
- dependencies,
- packageId: pythPackage,
- ticket: upgradeTicket,
- });
- // Commit upgrade.
- tx.moveCall({
- target: `${pythPackage}::contract_upgrade::commit_upgrade`,
- arguments: [tx.object(pythStateId), upgradeReceipt],
- });
- tx.setGasBudget(2_000_000_000n);
- return signer.signAndExecuteTransactionBlock({
- transactionBlock: tx,
- options: {
- showEffects: true,
- showEvents: true,
- },
- });
- }
- async function migratePyth(
- signer: RawSigner,
- pythStateId: string,
- wormholeStateId: string,
- signedUpgradeVaa: Buffer
- ) {
- const pythPackage = await getPackageId(signer.provider, pythStateId);
- const wormholePackage = await getPackageId(signer.provider, wormholeStateId);
- const tx = new TransactionBlock();
- const [verifiedVaa] = tx.moveCall({
- target: `${wormholePackage}::vaa::parse_and_verify`,
- arguments: [
- tx.object(wormholeStateId),
- tx.pure(Array.from(signedUpgradeVaa)),
- tx.object(SUI_CLOCK_OBJECT_ID),
- ],
- });
- const [decreeTicket] = tx.moveCall({
- target: `${pythPackage}::contract_upgrade::authorize_governance`,
- arguments: [tx.object(pythStateId)],
- });
- const [decreeReceipt] = tx.moveCall({
- target: `${wormholePackage}::governance_message::verify_vaa`,
- arguments: [tx.object(wormholeStateId), verifiedVaa, decreeTicket],
- typeArguments: [`${pythPackage}::governance_witness::GovernanceWitness`],
- });
- tx.moveCall({
- target: `${pythPackage}::migrate::migrate`,
- arguments: [tx.object(pythStateId), decreeReceipt],
- });
- tx.setGasBudget(2_000_000_000n);
- return signer.signAndExecuteTransactionBlock({
- transactionBlock: tx,
- options: {
- showEffects: true,
- showEvents: true,
- },
- });
- }
|