diff --git a/test/svm/SvmSpoke.Fill.AcrossPlus.ts b/test/svm/SvmSpoke.Fill.AcrossPlus.ts index dc858cedf..a89d42d82 100644 --- a/test/svm/SvmSpoke.Fill.AcrossPlus.ts +++ b/test/svm/SvmSpoke.Fill.AcrossPlus.ts @@ -33,6 +33,20 @@ import { import { MulticallHandler } from "../../target/types/multicall_handler"; import { common } from "./SvmSpoke.common"; import { FillDataParams, FillDataValues } from "../../src/types/svm"; +import { getApproveCheckedInstruction, getTransferCheckedInstruction } from "@solana-program/token"; +import { + AccountRole, + address, + appendTransactionMessageInstruction, + createKeyPairFromBytes, + createSignerFromKeyPair, + getProgramDerivedAddress, + IAccountMeta, + pipe, +} from "@solana/kit"; +import { createDefaultSolanaClient, createDefaultTransaction, signAndSendTransaction } from "./utils"; +import { FillRelayAsyncInput } from "../../src/svm/clients/SvmSpoke"; +import { SvmSpokeClient } from "../../src/svm"; const { provider, connection, program, owner, chainId, seedBalance } = common; const { initializeState, assertSE } = common; @@ -384,4 +398,141 @@ describe("svm_spoke.fill.across_plus", () => { "Recipient's balance should be increased by the relay amount" ); }); + + describe("codama client and solana kit", () => { + it("Forwards tokens to the final recipient within invoked message call using codama client", async () => { + const iRelayerBal = (await getAccount(connection, relayerATA)).amount; + + // Construct ix to transfer all tokens from handler to the final recipient. + const transferIx = createTransferCheckedInstruction( + handlerATA, + mint, + finalRecipientATA, + handlerSigner, + relayData.outputAmount, + mintDecimals + ); + + const multicallHandlerCoder = new MulticallHandlerCoder([transferIx]); + + const handlerMessage = multicallHandlerCoder.encode(); + + const message = new AcrossPlusMessageCoder({ + handler: handlerProgram.programId, + readOnlyLen: multicallHandlerCoder.readOnlyLen, + valueAmount: new BN(0), + accounts: multicallHandlerCoder.compiledMessage.accountKeys, + handlerMessage, + }); + + const encodedMessage = message.encode(); + + // Update relay data with the encoded message. + const newRelayData = { ...relayData, message: encodedMessage }; + updateRelayData(newRelayData); + + const rpcClient = createDefaultSolanaClient(); + const signer = await createSignerFromKeyPair(await createKeyPairFromBytes(relayer.secretKey)); + + const [eventAuthority] = await getProgramDerivedAddress({ + programAddress: address(program.programId.toString()), + seeds: ["__event_authority"], + }); + const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); + + const formattedAccounts = { + state: address(accounts.state.toString()), + instructionParams: address(program.programId.toString()), + mint: address(mint.toString()), + relayerTokenAccount: address(relayerATA.toString()), + recipientTokenAccount: address(handlerATA.toString()), + fillStatus: address(accounts.fillStatus.toString()), + tokenProgram: address(TOKEN_PROGRAM_ID.toString()), + associatedTokenProgram: address(ASSOCIATED_TOKEN_PROGRAM_ID.toString()), + systemProgram: address(anchor.web3.SystemProgram.programId.toString()), + program: address(program.programId.toString()), + eventAuthority, + signer, + }; + + const formattedRelayData = { + relayHash: new Uint8Array(relayHash), + relayData: { + depositor: address(relayData.depositor.toString()), + recipient: address(relayData.recipient.toString()), + exclusiveRelayer: address(relayData.exclusiveRelayer.toString()), + inputToken: address(relayData.inputToken.toString()), + outputToken: address(relayData.outputToken.toString()), + inputAmount: relayData.inputAmount.toNumber(), + outputAmount: relayData.outputAmount.toNumber(), + originChainId: relayData.originChainId.toNumber(), + depositId: new Uint8Array(relayData.depositId), + fillDeadline: relayData.fillDeadline, + exclusivityDeadline: relayData.exclusivityDeadline, + message: encodedMessage, + }, + repaymentChainId: 1, + repaymentAddress: address(relayer.publicKey.toString()), + }; + + const approveIx = getApproveCheckedInstruction({ + source: address(accounts.relayerTokenAccount.toString()), + mint: address(accounts.mint.toString()), + delegate: address(accounts.state.toString()), + owner: address(accounts.signer.toString()), + amount: BigInt(relayData.outputAmount.toString()), + decimals: mintDecimals, + }); + + const fillRelayInput: FillRelayAsyncInput = { + ...formattedRelayData, + ...formattedAccounts, + }; + + const fillRelayIxData = await SvmSpokeClient.getFillRelayInstructionAsync(fillRelayInput); + const fillRelayIx = { + ...fillRelayIxData, + accounts: fillRelayIxData.accounts.map((account) => + account.address === program.programId.toString() || + account.address === TOKEN_PROGRAM_ID.toString() || + account.address === ASSOCIATED_TOKEN_PROGRAM_ID.toString() + ? { ...account, role: AccountRole.READONLY } + : account + ), + }; + + const _remainingAccounts: AccountMeta[] = [ + { pubkey: handlerProgram.programId, isSigner: false, isWritable: false }, + ...multicallHandlerCoder.compiledKeyMetas, + ]; + const remainingAccounts: IAccountMeta[] = _remainingAccounts.map((account) => ({ + address: address(account.pubkey.toString()), + role: account.isWritable ? AccountRole.WRITABLE : AccountRole.READONLY, + })); + (fillRelayIx.accounts as IAccountMeta[]).push(...remainingAccounts); + + await pipe( + await createDefaultTransaction(rpcClient, signer), + (tx) => appendTransactionMessageInstruction(approveIx, tx), + (tx) => appendTransactionMessageInstruction(fillRelayIx, tx), + (tx) => signAndSendTransaction(rpcClient, tx) + ); + + // Verify relayer's balance after the fill + const fRelayerBal = (await getAccount(connection, relayerATA)).amount; + assertSE( + fRelayerBal, + iRelayerBal - BigInt(relayAmount), + "Relayer's balance should be reduced by the relay amount" + ); + + // Verify final recipient's balance after the fill + const finalRecipientAccount = await getAccount(connection, finalRecipientATA); + assertSE( + finalRecipientAccount.amount, + relayAmount, + "Final recipient's balance should be increased by the relay amount" + ); + }); + }); });