|
1 | 1 | import * as anchor from "@coral-xyz/anchor"; |
2 | 2 | import { BN, web3 } from "@coral-xyz/anchor"; |
| 3 | +import { getApproveCheckedInstruction } from "@solana-program/token"; |
| 4 | +import { |
| 5 | + AccountRole, |
| 6 | + address, |
| 7 | + appendTransactionMessageInstruction, |
| 8 | + createKeyPairFromBytes, |
| 9 | + createSignerFromKeyPair, |
| 10 | + getProgramDerivedAddress, |
| 11 | + IAccountMeta, |
| 12 | + pipe, |
| 13 | +} from "@solana/kit"; |
3 | 14 | import { |
4 | 15 | ASSOCIATED_TOKEN_PROGRAM_ID, |
5 | | - TOKEN_PROGRAM_ID, |
6 | | - TOKEN_2022_PROGRAM_ID, |
7 | 16 | createAccount, |
8 | | - createMint, |
9 | | - getOrCreateAssociatedTokenAccount, |
10 | | - mintTo, |
11 | | - getAccount, |
12 | | - getAssociatedTokenAddressSync, |
13 | 17 | createApproveCheckedInstruction, |
14 | | - createReallocateInstruction, |
15 | 18 | createEnableCpiGuardInstruction, |
| 19 | + createMint, |
| 20 | + createReallocateInstruction, |
16 | 21 | ExtensionType, |
| 22 | + getAccount, |
| 23 | + getAssociatedTokenAddressSync, |
| 24 | + getOrCreateAssociatedTokenAccount, |
| 25 | + mintTo, |
| 26 | + TOKEN_2022_PROGRAM_ID, |
| 27 | + TOKEN_PROGRAM_ID, |
17 | 28 | } from "@solana/spl-token"; |
18 | | -import { PublicKey, Keypair, TransactionInstruction, sendAndConfirmTransaction, Transaction } from "@solana/web3.js"; |
| 29 | +import { Keypair, PublicKey, sendAndConfirmTransaction, Transaction, TransactionInstruction } from "@solana/web3.js"; |
| 30 | +import { SvmSpokeClient } from "../../src/svm"; |
| 31 | +import { FillRelayAsyncInput } from "../../src/svm/clients/SvmSpoke"; |
19 | 32 | import { |
20 | | - readEventsUntilFound, |
21 | 33 | calculateRelayHashUint8Array, |
22 | | - sendTransactionWithLookupTable, |
23 | 34 | hashNonEmptyMessage, |
24 | 35 | intToU8Array32, |
| 36 | + readEventsUntilFound, |
| 37 | + sendTransactionWithLookupTable, |
25 | 38 | } from "../../src/svm/web3-v1"; |
26 | | -import { common } from "./SvmSpoke.common"; |
27 | | -import { testAcrossPlusMessage } from "./utils"; |
28 | 39 | import { FillDataValues, RelayData } from "../../src/types/svm"; |
| 40 | +import { common } from "./SvmSpoke.common"; |
| 41 | +import { |
| 42 | + createDefaultSolanaClient, |
| 43 | + createDefaultTransaction, |
| 44 | + signAndSendTransaction, |
| 45 | + testAcrossPlusMessage, |
| 46 | +} from "./utils"; |
29 | 47 | const { provider, connection, program, owner, chainId, seedBalance } = common; |
30 | 48 | const { recipient, initializeState, setCurrentTime, assertSE, assert } = common; |
31 | 49 |
|
@@ -631,4 +649,107 @@ describe("svm_spoke.fill", () => { |
631 | 649 | assertSE(event.messageHash, new Uint8Array(32), `MessageHash should be zeroed`); |
632 | 650 | assertSE(event.relayExecutionInfo.updatedMessageHash, new Uint8Array(32), `UpdatedMessageHash should be zeroed`); |
633 | 651 | }); |
| 652 | + |
| 653 | + describe("codama client and solana kit", () => { |
| 654 | + it("Fills a V3 relay and verifies balances with codama client and solana kit", async () => { |
| 655 | + const rpcClient = createDefaultSolanaClient(); |
| 656 | + const signer = await createSignerFromKeyPair(await createKeyPairFromBytes(relayer.secretKey)); |
| 657 | + |
| 658 | + const [eventAuthority] = await getProgramDerivedAddress({ |
| 659 | + programAddress: address(program.programId.toString()), |
| 660 | + seeds: ["__event_authority"], |
| 661 | + }); |
| 662 | + |
| 663 | + let recipientAccount = await getAccount(connection, recipientTA); |
| 664 | + assertSE(recipientAccount.amount, "0", "Recipient's balance should be 0 before the fill"); |
| 665 | + |
| 666 | + let relayerAccount = await getAccount(connection, relayerTA); |
| 667 | + assertSE(relayerAccount.amount, seedBalance, "Relayer's balance should be equal to seed balance before the fill"); |
| 668 | + |
| 669 | + const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); |
| 670 | + |
| 671 | + const formattedAccounts = { |
| 672 | + state: address(accounts.state.toString()), |
| 673 | + instructionParams: address(program.programId.toString()), |
| 674 | + mint: address(mint.toString()), |
| 675 | + relayerTokenAccount: address(relayerTA.toString()), |
| 676 | + recipientTokenAccount: address(recipientTA.toString()), |
| 677 | + fillStatus: address(accounts.fillStatus.toString()), |
| 678 | + tokenProgram: address(TOKEN_PROGRAM_ID.toString()), |
| 679 | + associatedTokenProgram: address(ASSOCIATED_TOKEN_PROGRAM_ID.toString()), |
| 680 | + systemProgram: address(anchor.web3.SystemProgram.programId.toString()), |
| 681 | + program: address(program.programId.toString()), |
| 682 | + eventAuthority, |
| 683 | + signer, |
| 684 | + }; |
| 685 | + |
| 686 | + const formattedRelayData = { |
| 687 | + relayHash: new Uint8Array(relayHash), |
| 688 | + relayData: { |
| 689 | + depositor: address(relayData.depositor.toString()), |
| 690 | + recipient: address(relayData.recipient.toString()), |
| 691 | + exclusiveRelayer: address(relayData.exclusiveRelayer.toString()), |
| 692 | + inputToken: address(relayData.inputToken.toString()), |
| 693 | + outputToken: address(relayData.outputToken.toString()), |
| 694 | + inputAmount: relayData.inputAmount.toNumber(), |
| 695 | + outputAmount: relayData.outputAmount.toNumber(), |
| 696 | + originChainId: relayData.originChainId.toNumber(), |
| 697 | + depositId: new Uint8Array(relayData.depositId), |
| 698 | + fillDeadline: relayData.fillDeadline, |
| 699 | + exclusivityDeadline: relayData.exclusivityDeadline, |
| 700 | + message: relayData.message, |
| 701 | + }, |
| 702 | + repaymentChainId: 1, |
| 703 | + repaymentAddress: address(relayer.publicKey.toString()), |
| 704 | + }; |
| 705 | + |
| 706 | + const approveIx = getApproveCheckedInstruction({ |
| 707 | + source: address(accounts.relayerTokenAccount.toString()), |
| 708 | + mint: address(accounts.mint.toString()), |
| 709 | + delegate: address(accounts.state.toString()), |
| 710 | + owner: address(accounts.signer.toString()), |
| 711 | + amount: BigInt(relayData.outputAmount.toString()), |
| 712 | + decimals: tokenDecimals, |
| 713 | + }); |
| 714 | + |
| 715 | + const fillRelayInput: FillRelayAsyncInput = { |
| 716 | + ...formattedRelayData, |
| 717 | + ...formattedAccounts, |
| 718 | + }; |
| 719 | + |
| 720 | + const fillRelayIxData = await SvmSpokeClient.getFillRelayInstructionAsync(fillRelayInput); |
| 721 | + const fillRelayIx = { |
| 722 | + ...fillRelayIxData, |
| 723 | + accounts: fillRelayIxData.accounts.map((account) => |
| 724 | + account.address === program.programId.toString() ? { ...account, role: AccountRole.READONLY } : account |
| 725 | + ), |
| 726 | + }; |
| 727 | + const remainingAccounts: IAccountMeta<string>[] = fillRemainingAccounts.map((account) => ({ |
| 728 | + address: address(account.pubkey.toString()), |
| 729 | + role: AccountRole.WRITABLE, |
| 730 | + })); |
| 731 | + (fillRelayIx.accounts as IAccountMeta<string>[]).push(...remainingAccounts); |
| 732 | + |
| 733 | + const tx = await pipe( |
| 734 | + await createDefaultTransaction(rpcClient, signer), |
| 735 | + (tx) => appendTransactionMessageInstruction(approveIx, tx), |
| 736 | + (tx) => appendTransactionMessageInstruction(fillRelayIx, tx), |
| 737 | + (tx) => signAndSendTransaction(rpcClient, tx) |
| 738 | + ); |
| 739 | + |
| 740 | + const events = await readEventsUntilFound(connection, tx, [program]); |
| 741 | + const event = events.find((event) => event.name === "filledRelay")?.data; |
| 742 | + assert.isNotNull(event, "FilledRelay event should be emitted"); |
| 743 | + |
| 744 | + relayerAccount = await getAccount(connection, relayerTA); |
| 745 | + assertSE( |
| 746 | + relayerAccount.amount, |
| 747 | + seedBalance - relayAmount, |
| 748 | + "Relayer's balance should be reduced by the relay amount" |
| 749 | + ); |
| 750 | + |
| 751 | + recipientAccount = await getAccount(connection, recipientTA); |
| 752 | + assertSE(recipientAccount.amount, relayAmount, "Recipient's balance should be increased by the relay amount"); |
| 753 | + }); |
| 754 | + }); |
634 | 755 | }); |
0 commit comments