|
1 | 1 | import { Chain, SuiChain } from "../chains";
|
2 | 2 | import { DataSource } from "@pythnetwork/xc-admin-common";
|
| 3 | +import { WormholeContract } from "./wormhole"; |
3 | 4 | import { PriceFeedContract, PrivateKey, TxResult } from "../base";
|
4 | 5 | import { SuiPythClient } from "@pythnetwork/pyth-sui-js";
|
5 | 6 | import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui.js/utils";
|
6 | 7 | import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
|
7 | 8 | import { TransactionBlock } from "@mysten/sui.js/transactions";
|
| 9 | +import { uint8ArrayToBCS } from "@certusone/wormhole-sdk/lib/cjs/sui"; |
8 | 10 |
|
9 | 11 | type ObjectId = string;
|
10 | 12 |
|
@@ -405,3 +407,168 @@ export class SuiPriceFeedContract extends PriceFeedContract {
|
405 | 407 | return result.data.content.fields;
|
406 | 408 | }
|
407 | 409 | }
|
| 410 | + |
| 411 | +export class SuiWormholeContract extends WormholeContract { |
| 412 | + public static type = "SuiWormholeContract"; |
| 413 | + private client: SuiPythClient; |
| 414 | + |
| 415 | + getId(): string { |
| 416 | + return `${this.chain.getId()}_${this.address}`; |
| 417 | + } |
| 418 | + |
| 419 | + getType(): string { |
| 420 | + return SuiWormholeContract.type; |
| 421 | + } |
| 422 | + |
| 423 | + toJson() { |
| 424 | + return { |
| 425 | + chain: this.chain.getId(), |
| 426 | + address: this.address, |
| 427 | + type: SuiWormholeContract.type, |
| 428 | + }; |
| 429 | + } |
| 430 | + |
| 431 | + static fromJson( |
| 432 | + chain: Chain, |
| 433 | + parsed: { |
| 434 | + type: string; |
| 435 | + address: string; |
| 436 | + stateId: string; |
| 437 | + } |
| 438 | + ): SuiWormholeContract { |
| 439 | + if (parsed.type !== SuiWormholeContract.type) |
| 440 | + throw new Error("Invalid type"); |
| 441 | + if (!(chain instanceof SuiChain)) |
| 442 | + throw new Error(`Wrong chain type ${chain}`); |
| 443 | + return new SuiWormholeContract(chain, parsed.address, parsed.stateId); |
| 444 | + } |
| 445 | + |
| 446 | + constructor( |
| 447 | + public chain: SuiChain, |
| 448 | + public address: string, |
| 449 | + public stateId: string |
| 450 | + ) { |
| 451 | + super(); |
| 452 | + this.client = new SuiPythClient( |
| 453 | + this.chain.getProvider(), |
| 454 | + // HACK: |
| 455 | + // We're using the SuiPythClient to work with the Wormhole contract |
| 456 | + // so there is no Pyth contract here, passing empty string to type- |
| 457 | + // check. |
| 458 | + "", |
| 459 | + this.stateId |
| 460 | + ); |
| 461 | + } |
| 462 | + |
| 463 | + async getCurrentGuardianSetIndex(): Promise<number> { |
| 464 | + const data = await this.getStateFields(); |
| 465 | + return Number(data.guardian_set_index); |
| 466 | + } |
| 467 | + |
| 468 | + // There doesn't seem to be a way to get a value out of any function call |
| 469 | + // via a Sui transaction due to the linear nature of the language, this is |
| 470 | + // enforced at the TransactionBlock level by only allowing you to receive |
| 471 | + // receipts. |
| 472 | + async getChainId(): Promise<number> { |
| 473 | + return this.chain.getWormholeChainId(); |
| 474 | + } |
| 475 | + |
| 476 | + // NOTE: There's no way to getChain() on the main interface, should update |
| 477 | + // that interface. |
| 478 | + public getChain(): SuiChain { |
| 479 | + return this.chain; |
| 480 | + } |
| 481 | + |
| 482 | + async getGuardianSet(): Promise<string[]> { |
| 483 | + const data = await this.getStateFields(); |
| 484 | + const guardian_sets = data.guardian_sets; |
| 485 | + return guardian_sets; |
| 486 | + } |
| 487 | + |
| 488 | + async upgradeGuardianSets( |
| 489 | + senderPrivateKey: PrivateKey, |
| 490 | + vaa: Buffer |
| 491 | + ): Promise<TxResult> { |
| 492 | + const tx = new TransactionBlock(); |
| 493 | + const coreObjectId = this.stateId; |
| 494 | + const corePackageId = await this.client.getWormholePackageId(); |
| 495 | + const [verifiedVaa] = tx.moveCall({ |
| 496 | + target: `${corePackageId}::vaa::parse_and_verify`, |
| 497 | + arguments: [ |
| 498 | + tx.object(coreObjectId), |
| 499 | + tx.pure(uint8ArrayToBCS(vaa)), |
| 500 | + tx.object(SUI_CLOCK_OBJECT_ID), |
| 501 | + ], |
| 502 | + }); |
| 503 | + |
| 504 | + const [decreeTicket] = tx.moveCall({ |
| 505 | + target: `${corePackageId}::update_guardian_set::authorize_governance`, |
| 506 | + arguments: [tx.object(coreObjectId)], |
| 507 | + }); |
| 508 | + |
| 509 | + const [decreeReceipt] = tx.moveCall({ |
| 510 | + target: `${corePackageId}::governance_message::verify_vaa`, |
| 511 | + arguments: [tx.object(coreObjectId), verifiedVaa, decreeTicket], |
| 512 | + typeArguments: [ |
| 513 | + `${corePackageId}::update_guardian_set::GovernanceWitness`, |
| 514 | + ], |
| 515 | + }); |
| 516 | + |
| 517 | + tx.moveCall({ |
| 518 | + target: `${corePackageId}::update_guardian_set::update_guardian_set`, |
| 519 | + arguments: [ |
| 520 | + tx.object(coreObjectId), |
| 521 | + decreeReceipt, |
| 522 | + tx.object(SUI_CLOCK_OBJECT_ID), |
| 523 | + ], |
| 524 | + }); |
| 525 | + |
| 526 | + const keypair = Ed25519Keypair.fromSecretKey( |
| 527 | + Buffer.from(senderPrivateKey, "hex") |
| 528 | + ); |
| 529 | + const result = await this.executeTransaction(tx, keypair); |
| 530 | + return { id: result.digest, info: result }; |
| 531 | + } |
| 532 | + |
| 533 | + private async getStateFields(): Promise<any> { |
| 534 | + const provider = this.chain.getProvider(); |
| 535 | + const result = await provider.getObject({ |
| 536 | + id: this.stateId, |
| 537 | + options: { showContent: true }, |
| 538 | + }); |
| 539 | + if ( |
| 540 | + !result.data || |
| 541 | + !result.data.content || |
| 542 | + result.data.content.dataType !== "moveObject" |
| 543 | + ) |
| 544 | + throw new Error("Unable to fetch pyth state object"); |
| 545 | + return result.data.content.fields; |
| 546 | + } |
| 547 | + |
| 548 | + /** |
| 549 | + * Given a transaction block and a keypair, sign and execute it |
| 550 | + * Sets the gas budget to 2x the estimated gas cost |
| 551 | + * @param tx |
| 552 | + * @param keypair |
| 553 | + * @private |
| 554 | + */ |
| 555 | + private async executeTransaction( |
| 556 | + tx: TransactionBlock, |
| 557 | + keypair: Ed25519Keypair |
| 558 | + ) { |
| 559 | + const provider = this.chain.getProvider(); |
| 560 | + tx.setSender(keypair.toSuiAddress()); |
| 561 | + const dryRun = await provider.dryRunTransactionBlock({ |
| 562 | + transactionBlock: await tx.build({ client: provider }), |
| 563 | + }); |
| 564 | + tx.setGasBudget(BigInt(dryRun.input.gasData.budget.toString()) * BigInt(2)); |
| 565 | + return provider.signAndExecuteTransactionBlock({ |
| 566 | + signer: keypair, |
| 567 | + transactionBlock: tx, |
| 568 | + options: { |
| 569 | + showEffects: true, |
| 570 | + showEvents: true, |
| 571 | + }, |
| 572 | + }); |
| 573 | + } |
| 574 | +} |
0 commit comments