upgrade_pyth.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import * as mock from "@certusone/wormhole-sdk/lib/cjs/mock";
  2. import dotenv from "dotenv";
  3. import {
  4. RawSigner,
  5. SUI_CLOCK_OBJECT_ID,
  6. TransactionBlock,
  7. fromB64,
  8. normalizeSuiObjectId,
  9. JsonRpcProvider,
  10. Ed25519Keypair,
  11. testnetConnection,
  12. Connection,
  13. } from "@mysten/sui.js";
  14. import { execSync } from "child_process";
  15. import { resolve } from "path";
  16. import * as fs from "fs";
  17. import { REGISTRY, NETWORK } from "../registry";
  18. import { modifySignTransaction } from "@certusone/wormhole-sdk/lib/cjs/solana";
  19. dotenv.config({ path: "~/.env" });
  20. // Network dependent settings.
  21. let network = NETWORK.TESTNET; // <= NOTE: Update this when changing network
  22. const walletPrivateKey = process.env.SUI_TESTNET_ALT_KEY_BASE_64; // <= NOTE: Update this when changing network
  23. const guardianPrivateKey = process.env.WH_TESTNET_GUARDIAN_PRIVATE_KEY;
  24. const registry = REGISTRY[network];
  25. const provider = new JsonRpcProvider(
  26. new Connection({ fullnode: registry["RPC_URL"] })
  27. );
  28. const PYTH_STATE_ID = registry["PYTH_STATE_ID"];
  29. const PYTH_PACKAGE_ID = registry["PYTH_PACKAGE_ID"];
  30. const WORMHOLE_STATE_ID = registry["WORMHOLE_STATE_ID"];
  31. const WORMHOLE_PACKAGE_ID = registry["WORMHOLE_PACKAGE_ID"];
  32. console.log("WORMHOLE_STATE_ID: ", WORMHOLE_STATE_ID);
  33. console.log("PYTH_STATE_ID: ", WORMHOLE_STATE_ID);
  34. const GOVERNANCE_EMITTER =
  35. //"0000000000000000000000000000000000000000000000000000000000000004";
  36. "63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385";
  37. // To upgrade Pyth, take the following steps.
  38. // 0. Make contract changes in the "contracts" folder. These updated contracts will be posted on chain as an
  39. // entirely new package. The old package will still be valid unless we "brick" its call-sites explicitly
  40. // (this is done for you via the version control logic built into the Pyth contracts).
  41. // 1. Make sure that in version_control.move, you create a new struct for the new version and update the
  42. // current_version() and previous_version() functions accordingly. The former should point to the new version,
  43. // and the latter should point to the old version.
  44. // 2. Update the Move.toml file so that it points to a wormhole dependency whose Move.toml file has a "published-at" field
  45. // specified at the top with the correct address.
  46. // 3. Execute this script!
  47. //
  48. async function main() {
  49. if (guardianPrivateKey === undefined) {
  50. throw new Error("TESTNET_GUARDIAN_PRIVATE_KEY unset in environment");
  51. }
  52. if (walletPrivateKey === undefined) {
  53. throw new Error("TESTNET_WALLET_PRIVATE_KEY unset in environment");
  54. }
  55. console.log("priv key: ", walletPrivateKey);
  56. const wallet = new RawSigner(
  57. Ed25519Keypair.fromSecretKey(
  58. network == "MAINNET"
  59. ? Buffer.from(walletPrivateKey, "hex")
  60. : Buffer.from(walletPrivateKey, "base64")
  61. ),
  62. provider
  63. );
  64. console.log("wallet address: ", wallet.getAddress());
  65. const pythContractsPath = resolve(`${__dirname}/../../contracts`);
  66. // Build for digest.
  67. const { modules, dependencies, digest } =
  68. buildForBytecodeAndDigest(pythContractsPath);
  69. console.log("dependencies", dependencies);
  70. console.log("digest", digest.toString("hex"));
  71. // ===========================================================================================
  72. // Construct VAA. We will use the signed VAA when we execute the upgrade.
  73. // For a mainnet contract upgrade, we would not construct AND sign the VAA here. Instead, all
  74. // the guardians would have to sign the upgrade VAA.
  75. const guardians = new mock.MockGuardians(0, [guardianPrivateKey]);
  76. const timestamp = 12345678;
  77. const governance = new mock.GovernanceEmitter(GOVERNANCE_EMITTER);
  78. const action = 0;
  79. const chain = 21;
  80. // construct VAA inner payload
  81. const magic = Buffer.alloc(4);
  82. magic.write("PTGM", 0); // magic
  83. console.log("magic buffer: ", magic);
  84. let inner_payload = Buffer.alloc(8); // 4 (magic) + 1 (module name) + 1 (action) + 2 (target chain) = 8
  85. inner_payload.write(magic.toString(), 0); // magic = "PTGM"
  86. inner_payload.writeUInt8(1, 4); // moduleName = 1
  87. inner_payload.writeUInt8(0, 5); // action = 0
  88. inner_payload.writeUInt16BE(21, 6); // target chain = 21
  89. inner_payload = Buffer.concat([inner_payload, digest]);
  90. console.log("digest: ", digest.toString("hex"));
  91. console.log("inner payload: ", inner_payload.toString("hex"));
  92. // create governance message
  93. let msg = governance.publishGovernanceMessage(
  94. timestamp,
  95. "",
  96. inner_payload,
  97. action,
  98. chain
  99. );
  100. msg.writeUInt8(0x1, 84 - 33 + 31); // here we insert an 0x1 in the right place to make the module name "0x00000000000000000000000000000001"
  101. console.log("governance msg: ", msg.toString("hex"));
  102. // sign governance message
  103. const signedVaa = guardians.addSignatures(msg, [0]);
  104. console.log("Upgrade VAA:", signedVaa.toString("hex"));
  105. // ===========================================================================================
  106. //Execute upgrade with signed governance VAA.
  107. const upgradeResults = await upgradePyth(
  108. wallet,
  109. PYTH_STATE_ID,
  110. WORMHOLE_STATE_ID,
  111. modules,
  112. dependencies,
  113. signedVaa
  114. );
  115. console.log("tx digest", upgradeResults.digest);
  116. console.log("tx effects", JSON.stringify(upgradeResults.effects!));
  117. console.log("tx events", JSON.stringify(upgradeResults.events!));
  118. const migrateResults = await migratePyth(
  119. wallet,
  120. PYTH_STATE_ID,
  121. WORMHOLE_STATE_ID,
  122. signedVaa
  123. );
  124. console.log("tx digest", migrateResults.digest);
  125. console.log("tx effects", JSON.stringify(migrateResults.effects!));
  126. console.log("tx events", JSON.stringify(migrateResults.events!));
  127. }
  128. main();
  129. function buildForBytecodeAndDigest(packagePath: string) {
  130. const buildOutput: {
  131. modules: string[];
  132. dependencies: string[];
  133. digest: number[];
  134. } = JSON.parse(
  135. execSync(
  136. `sui move build --dump-bytecode-as-base64 -p ${packagePath} 2> /dev/null`,
  137. { encoding: "utf-8" }
  138. )
  139. );
  140. return {
  141. modules: buildOutput.modules.map((m: string) => Array.from(fromB64(m))),
  142. dependencies: buildOutput.dependencies.map((d: string) =>
  143. normalizeSuiObjectId(d)
  144. ),
  145. digest: Buffer.from(buildOutput.digest),
  146. };
  147. }
  148. async function getPackageId(
  149. provider: JsonRpcProvider,
  150. stateId: string
  151. ): Promise<string> {
  152. const state = await provider
  153. .getObject({
  154. id: stateId,
  155. options: {
  156. showContent: true,
  157. },
  158. })
  159. .then((result) => {
  160. if (result.data?.content?.dataType == "moveObject") {
  161. return result.data.content.fields;
  162. }
  163. throw new Error("not move object");
  164. });
  165. if ("upgrade_cap" in state) {
  166. return state.upgrade_cap.fields.package;
  167. }
  168. throw new Error("upgrade_cap not found");
  169. }
  170. async function upgradePyth(
  171. signer: RawSigner,
  172. pythStateId: string,
  173. wormholeStateId: string,
  174. modules: number[][],
  175. dependencies: string[],
  176. signedVaa: Buffer
  177. ) {
  178. const pythPackage = await getPackageId(signer.provider, pythStateId);
  179. const wormholePackage = await getPackageId(signer.provider, wormholeStateId);
  180. console.log("pythPackage: ", pythPackage);
  181. console.log("wormholePackage: ", wormholePackage);
  182. const tx = new TransactionBlock();
  183. const [verifiedVaa] = tx.moveCall({
  184. target: `${wormholePackage}::vaa::parse_and_verify`,
  185. arguments: [
  186. tx.object(wormholeStateId),
  187. tx.pure(Array.from(signedVaa)),
  188. tx.object(SUI_CLOCK_OBJECT_ID),
  189. ],
  190. });
  191. const [decreeTicket] = tx.moveCall({
  192. target: `${pythPackage}::contract_upgrade::authorize_governance`,
  193. arguments: [tx.object(pythStateId)],
  194. });
  195. const [decreeReceipt] = tx.moveCall({
  196. target: `${wormholePackage}::governance_message::verify_vaa`,
  197. arguments: [tx.object(wormholeStateId), verifiedVaa, decreeTicket],
  198. typeArguments: [`${pythPackage}::governance_witness::GovernanceWitness`],
  199. });
  200. // Authorize upgrade.
  201. const [upgradeTicket] = tx.moveCall({
  202. target: `${pythPackage}::contract_upgrade::authorize_upgrade`,
  203. arguments: [tx.object(pythStateId), decreeReceipt],
  204. });
  205. // Build and generate modules and dependencies for upgrade.
  206. const [upgradeReceipt] = tx.upgrade({
  207. modules,
  208. dependencies,
  209. packageId: pythPackage,
  210. ticket: upgradeTicket,
  211. });
  212. // Commit upgrade.
  213. tx.moveCall({
  214. target: `${pythPackage}::contract_upgrade::commit_upgrade`,
  215. arguments: [tx.object(pythStateId), upgradeReceipt],
  216. });
  217. tx.setGasBudget(2_000_000_000n);
  218. return signer.signAndExecuteTransactionBlock({
  219. transactionBlock: tx,
  220. options: {
  221. showEffects: true,
  222. showEvents: true,
  223. },
  224. });
  225. }
  226. async function migratePyth(
  227. signer: RawSigner,
  228. pythStateId: string,
  229. wormholeStateId: string,
  230. signedUpgradeVaa: Buffer
  231. ) {
  232. const pythPackage = await getPackageId(signer.provider, pythStateId);
  233. const wormholePackage = await getPackageId(signer.provider, wormholeStateId);
  234. const tx = new TransactionBlock();
  235. const [verifiedVaa] = tx.moveCall({
  236. target: `${wormholePackage}::vaa::parse_and_verify`,
  237. arguments: [
  238. tx.object(wormholeStateId),
  239. tx.pure(Array.from(signedUpgradeVaa)),
  240. tx.object(SUI_CLOCK_OBJECT_ID),
  241. ],
  242. });
  243. const [decreeTicket] = tx.moveCall({
  244. target: `${pythPackage}::contract_upgrade::authorize_governance`,
  245. arguments: [tx.object(pythStateId)],
  246. });
  247. const [decreeReceipt] = tx.moveCall({
  248. target: `${wormholePackage}::governance_message::verify_vaa`,
  249. arguments: [tx.object(wormholeStateId), verifiedVaa, decreeTicket],
  250. typeArguments: [`${pythPackage}::governance_witness::GovernanceWitness`],
  251. });
  252. tx.moveCall({
  253. target: `${pythPackage}::migrate::migrate`,
  254. arguments: [tx.object(pythStateId), decreeReceipt],
  255. });
  256. tx.setGasBudget(2_000_000_000n);
  257. return signer.signAndExecuteTransactionBlock({
  258. transactionBlock: tx,
  259. options: {
  260. showEffects: true,
  261. showEvents: true,
  262. },
  263. });
  264. }