diff --git a/programs/svm-spoke/src/instructions/deposit.rs b/programs/svm-spoke/src/instructions/deposit.rs index e2e6380b7..5f56aa738 100644 --- a/programs/svm-spoke/src/instructions/deposit.rs +++ b/programs/svm-spoke/src/instructions/deposit.rs @@ -4,7 +4,10 @@ // implemented. For more details, refer to the documentation: https://docs.across.to use anchor_lang::prelude::*; -use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; +use anchor_spl::{ + associated_token::AssociatedToken, + token_interface::{Mint, TokenAccount, TokenInterface}, +}; use crate::{ constants::{MAX_EXCLUSIVITY_PERIOD_SECONDS, ZERO_DEPOSIT_ID}, @@ -30,6 +33,7 @@ use crate::{ pub struct Deposit<'info> { #[account(mut)] pub signer: Signer<'info>, + #[account( mut, seeds = [b"state", state.seed.to_le_bytes().as_ref()], @@ -50,7 +54,8 @@ pub struct Deposit<'info> { pub depositor_token_account: InterfaceAccount<'info, TokenAccount>, #[account( - mut, + init_if_needed, + payer = signer, associated_token::mint = mint, associated_token::authority = state, // Ensure owner is the state as tokens are sent here on deposit. associated_token::token_program = token_program @@ -64,6 +69,10 @@ pub struct Deposit<'info> { pub mint: InterfaceAccount<'info, Mint>, pub token_program: Interface<'info, TokenInterface>, + + pub associated_token_program: Program<'info, AssociatedToken>, + + pub system_program: Program<'info, System>, } pub fn _deposit( diff --git a/test/svm/SvmSpoke.Deposit.ts b/test/svm/SvmSpoke.Deposit.ts index 8b32dcfee..a1d319a23 100644 --- a/test/svm/SvmSpoke.Deposit.ts +++ b/test/svm/SvmSpoke.Deposit.ts @@ -12,6 +12,7 @@ import { pipe, } from "@solana/kit"; import { + ASSOCIATED_TOKEN_PROGRAM_ID, ExtensionType, NATIVE_MINT, TOKEN_2022_PROGRAM_ID, @@ -163,9 +164,13 @@ describe("svm_spoke.deposit", () => { .accounts(calledDepositAccounts) .instruction(); const depositTx = new Transaction().add(approveIx, depositIx); - return sendAndConfirmTransaction(connection, depositTx, [payer, depositor]); + return sendAndConfirmTransaction(connection, depositTx, [depositor]); }; + before(async () => { + await connection.requestAirdrop(depositor.publicKey, 10_000_000_000); // 10 SOL + }); + beforeEach(async () => { ({ state, seed } = await initializeState()); @@ -730,6 +735,44 @@ describe("svm_spoke.deposit", () => { ); }); + it("Deposits tokens to a new vault", async () => { + // Create new input token without creating a new vault for it. + await setupInputToken(); + const inputTokenAccount = await provider.connection.getAccountInfo(inputToken); + if (inputTokenAccount === null) throw new Error("Input mint account not found"); + vault = getAssociatedTokenAddressSync( + inputToken, + state, + true, + inputTokenAccount.owner, + ASSOCIATED_TOKEN_PROGRAM_ID + ); + + // Update global variables using the new input token. + depositData.inputToken = inputToken; + depositAccounts.depositorTokenAccount = depositorTA; + depositAccounts.vault = vault; + depositAccounts.mint = inputToken; + + // Verify there is no vault account before the deposit. + assert.isNull(await provider.connection.getAccountInfo(vault), "Vault should not exist before the deposit"); + + // Execute the deposit call + await approvedDeposit(depositData); + + // Verify tokens leave the depositor's account + const depositorAccount = await getAccount(connection, depositorTA); + assertSE( + depositorAccount.amount, + seedBalance - depositData.inputAmount.toNumber(), + "Depositor's balance should be reduced by the deposited amount" + ); + + // Verify tokens are credited into the new vault + const vaultAccount = await getAccount(connection, vault); + assertSE(vaultAccount.amount, depositData.inputAmount, "Vault balance should equal the deposited amount"); + }); + describe("codama client and solana kit", () => { it("Deposit with with solana kit and codama client", async () => { // typescript is not happy with the depositData object