Skip to content

fix(svm): L-01 create new vault on deposit if needed #957

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ jobs:
- name: Install Cargo toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
toolchain: nightly-2025-04-01
profile: minimal
components: rustc
- name: Install packages
run: yarn install --frozen-lockfile && rustup component add --toolchain nightly-x86_64-unknown-linux-gnu rustfmt
run: yarn install --frozen-lockfile && rustup component add --toolchain nightly-2025-04-01-x86_64-unknown-linux-gnu rustfmt
- name: Lint js
shell: bash
run: yarn lint-js
Expand Down Expand Up @@ -69,7 +69,7 @@ jobs:
- name: Install Cargo toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: nightly-2025-04-01
profile: minimal
components: rustc
- name: Cache Cargo dependencies
Expand Down
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,25 @@
"lint": "yarn lint-solidity && yarn lint-js && yarn lint-rust",
"lint-solidity": "yarn solhint ./contracts/**/*.sol",
"lint-js": "yarn prettier --list-different **/*.js **/*.ts",
"lint-rust": "cargo +nightly fmt --all -- --check && cargo clippy",
"lint-fix": "yarn prettier --write **/*.js **/*.ts ./programs/**/*.rs ./contracts**/*.sol && cargo +nightly fmt --all && cargo clippy",
"lint-rust": "cargo +nightly-2025-04-01 fmt --all -- --check && cargo clippy",
"lint-fix": "yarn prettier --write **/*.js **/*.ts ./programs/**/*.rs ./contracts**/*.sol && cargo +nightly-2025-04-01 fmt --all && cargo clippy",
"clean-fast": "for dir in node_modules cache cache-zk artifacts artifacts-zk dist typechain; do mv \"${dir}\" \"_${dir}\"; rm -rf \"_${dir}\" &; done",
"clean": "rm -rf node_modules cache cache-zk artifacts artifacts-zk dist typechain",
"generate-svm-assets": "sh ./scripts/generate-svm-assets.sh && yarn generate-svm-clients",
"generate-svm-clients": "yarn ts-node ./scripts/svm/utils/generate-svm-clients.ts && yarn ts-node ./scripts/svm/utils/rename-clients-imports.ts",
"build-evm": "hardhat compile",
"build-svm": "echo 'Generating IDLs...' && anchor build > /dev/null 2>&1 || true && anchor run generateExternalTypes && anchor build",
"build-svm": "echo 'Generating IDLs...' && yarn anchor-build > /dev/null 2>&1 || true && anchor run generateExternalTypes && yarn anchor-build",
"build-ts": "rm -rf ./dist && tsc && rsync -a --include '*/' --include '*.d.ts' --exclude '*' ./typechain ./dist/",
"copy-idl": "mkdir -p dist/src/svm/assets/idl && cp src/svm/assets/idl/*.json dist/src/svm/assets/idl/",
"build": "yarn build-evm && yarn build-svm && yarn generate-svm-assets && yarn build-ts && yarn copy-idl",
"test-evm": "IS_TEST=true hardhat test",
"test-svm": "anchor test -- --features test",
"test-svm": "sh ./scripts/anchor-test.sh",
"test": "yarn test-evm && yarn test-svm",
"test:report-gas": "IS_TEST=true REPORT_GAS=true hardhat test",
"generate-evm-assets": "rm -rf typechain && TYPECHAIN=ethers yarn hardhat typechain",
"prepublish": "yarn build && hardhat export --export-all ./cache/massExport.json && ts-node ./scripts/processHardhatExport.ts && prettier --write ./deployments/deployments.json && yarn generate-evm-assets",
"pre-commit-hook": "sh scripts/preCommitHook.sh"
"pre-commit-hook": "sh scripts/preCommitHook.sh",
"anchor-build": "sh ./scripts/anchor-build.sh"
},
"dependencies": {
"@across-protocol/constants": "^3.1.46",
Expand Down
13 changes: 11 additions & 2 deletions programs/svm-spoke/src/instructions/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -28,6 +31,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()],
Expand All @@ -45,7 +49,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
Expand All @@ -59,6 +64,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(
Expand Down
2 changes: 2 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly-2025-04-01"
16 changes: 16 additions & 0 deletions scripts/anchor-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/sh

echo "🔨 Building anchor programs (no IDL)..."
anchor build --no-idl

echo "📦 Generating IDLs (using nightly)..."

for program in programs/*; do
[ -d "$program" ] || continue

dir_name=$(basename "$program")
program_name=$(echo "$dir_name" | tr '-' '_')

echo "→ IDL for $program_name"
RUSTUP_TOOLCHAIN="nightly-2025-04-01" anchor idl build -p "$dir_name" -o "target/idl/$program_name.json" -t "target/types/$program_name.ts"
done
19 changes: 19 additions & 0 deletions scripts/anchor-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/sh

echo "🔨 Building anchor programs (with test feature, no IDL)..."
anchor build --no-idl -- --features test

echo "📦 Generating IDLs (using nightly)..."

for program in programs/*; do
[ -d "$program" ] || continue

dir_name=$(basename "$program")
program_name=$(echo "$dir_name" | tr '-' '_')

echo "→ IDL for $program_name"
RUSTUP_TOOLCHAIN="nightly-2025-04-01" anchor idl build -p "$dir_name" -o "target/idl/$program_name.json" -t "target/types/$program_name.ts"
done

echo "🧪 Running anchor tests..."
anchor test --skip-build
46 changes: 45 additions & 1 deletion test/svm/SvmSpoke.Deposit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
pipe,
} from "@solana/kit";
import {
ASSOCIATED_TOKEN_PROGRAM_ID,
ExtensionType,
NATIVE_MINT,
TOKEN_2022_PROGRAM_ID,
Expand Down Expand Up @@ -120,10 +121,14 @@ describe("svm_spoke.deposit", () => {
.accounts(calledDepositAccounts)
.instruction();
const depositTx = new Transaction().add(approveIx, depositIx);
const tx = await sendAndConfirmTransaction(connection, depositTx, [payer, depositor]);
const tx = await sendAndConfirmTransaction(connection, depositTx, [depositor]);
return tx;
};

before(async () => {
await connection.requestAirdrop(depositor.publicKey, 10_000_000_000); // 10 SOL
});

beforeEach(async () => {
({ state } = await initializeState());

Expand Down Expand Up @@ -695,6 +700,45 @@ 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
const depositDataValues = Object.values(depositData) as DepositDataValues;
await approvedDeposit(depositDataValues);

// 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
Expand Down
Loading