icRamp Devlog #11 — Testing Saga 4: Vault State (SOL + SPL), Full Suite Green

icRamp Devlog #11 — Testing Saga 4: Vault State (SOL + SPL), Full Suite Green

9/17/20253 min • icramp
SolanaVaultPocketICTesting CanistersSPL Tokens

In Parts 1–2 we stood up PocketIC and built a deterministic JSON-RPC harness. In Part 3 we nailed the token registry (incl. Token-2022) and made wasm paths bullet-proof. Today we finish the vault flows for SOL and SPL tokens — no external RPC needed — and ship a clean test suite developers can lean on.

Vault design in a sentence

The vault tracks balances for two roles:

  • Offramper: deposits funds (SOL or SPL), can cancel partially; can lock funds to a specific onramper.
  • Onramper: receives locked funds; can complete (consume) or see funds unlocked back to the offramper.

Everything is accounted in-canister state. No Solana transactions here. The backend will orchestrate Solana transfers separately.

#[derive(CandidType, Deserialize, Clone, Debug, Default)]
pub struct VaultEntry {
  pub lamports: u64,
  pub tokens: HashMap<String, u64>, // mint -> balance (smallest units)
}

Guards: if token_mint.is_some(), it must be previously registered or we return UnsupportedToken.

Tests that read like specs

We reuse the test env wrappers (env.rs) so tests call methods by intent:

  • deposit(offramper, amount, spl_mint)
  • cancel(offramper, amount, spl_mint)
  • lock(offramper, onramper, amount, spl_mint)
  • unlock(offramper, onramper, amount, spl_mint)
  • complete(onramper, amount, spl_mint)
  • get_offramper_vault(addr) / get_onramper_vault(addr)

SOL vault (no registration needed)

let amount = 1_000_000_000;
env.deposit(offramper.clone(), amount, None)?;
env.cancel(offramper.clone(), 200_000_000, None)?;
env.lock(offramper.clone(), onramper.clone(), amount - 200_000_000, None)?;
env.unlock(offramper.clone(), onramper.clone(), 300_000_000, None)?;
env.complete(onramper.clone(), amount - 200_000_000 - 300_000_000, None)?;

Assertions verify each intermediate state (offramper/onramper lamports).

SPL vault (register once, then pure state)

We mock one RPC round to register a token (Token program owner + 1-byte decimals), then everything is state:

// register
register_token_ok(&mut env, &mint, "USDC", "USDC", 6);
 
// vault ops (now pure state)
env.deposit(offramper.clone(), 1_000_000, Some(mint.clone()))?;
env.cancel(offramper.clone(), 200_000, Some(mint.clone()))?;
env.lock(offramper.clone(), onramper.clone(), 800_000, Some(mint.clone()))?;
env.unlock(offramper.clone(), onramper.clone(), 300_000, Some(mint.clone()))?;
env.complete(onramper.clone(), 500_000, Some(mint.clone()))?;

We assert the tokens[mint] balances at each step.

Full Solana suite: 17/17 ✅

We now cover:

  • Registry
    • invalid pubkey, unregistered, unsupported owner
    • Token (SPL) happy path
    • Token-2022 happy path
    • bad decimal slice length
    • missing mint data
  • Runtime flows
    • get SOL account balance
    • SPL balance 0 path
    • create ATA when missing
    • send SOL
    • send SPL (creates dest ATA)
  • Vault
    • lamports flow (deposit → cancel → lock → unlock → complete)
    • SPL flow (same, keyed by mint)

Final test tree

tests/
├── bitcoin_tests
   ├── Cargo.toml
   └── src
       ├── bitcoin_tests.rs
       ├── env.rs
       ├── helpers.rs
       ├── lib.rs
       ├── rune_tests.rs
       ├── setup.rs
       ├── token_tests.rs
       └── vault_tests.rs
├── fixtures
   └── wasm
       ├── ic-btc-canister.wasm.gz
       └── sol_rpc_canister.wasm.gz
├── README.md
├── solana_tests
   ├── Cargo.toml
   └── src
       ├── env.rs
       ├── helpers
   ├── json.rs
   ├── mock.rs
   └── mod.rs
       ├── lib.rs
       ├── registry_tests.rs
       ├── setup.rs
       ├── solana_tests.rs
       └── vault_tests.rs
└── testkit
    ├── Cargo.toml
    └── src
        ├── helpers.rs
        └── lib.rs

How to run

# Build the Solana backend wasm once
cargo build -p solana_backend --release --target wasm32-unknown-unknown
 
# Run the full suite
cargo test -p solana_tests -- --nocapture
 
# Or a single test
cargo test -p solana_tests vault_tests::test_token_vault -- --nocapture

Example:

cargo test -p solana_tests -- --nocapture
   Compiling solana_tests v0.1.0 (/home/reymon/Projects/crypto/eth-prague/ic2P2ramp/tests/solana_tests)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 4.44s
     Running unittests src/lib.rs (target/debug/deps/solana_tests-d15fb005799d4f4f)
 
running 17 tests 
2025-09-18T23:17:25.379203Z INFO pocket_ic_server: The PocketIC server is listening on port 44343 
test registry_tests::test_get_token_info_invalid_pubkey ... ok 
test registry_tests::test_get_token_info_unregistered_err ... ok 
test registry_tests::test_is_token_supported_unregistered ... ok 
test registry_tests::test_register_and_query_success_token_program ... ok 
test registry_tests::test_register_missing_mint_data_rejected ... ok 
test registry_tests::test_register_and_query_success_token_2022 ... ok 
test registry_tests::test_register_token_bad_decimal_len_rejected ... ok 
test registry_tests::test_register_token_invalid_pubkey_rejected ... ok 
test registry_tests::test_register_token_wrong_owner_rejected ... ok 
test solana_tests::test_create_ata_when_missing ... ok 
test solana_tests::test_get_sol_account_balance ... ok 
test solana_tests::test_get_spl_token_balance_zero ... ok 
test solana_tests::test_send_sol_happy_path ... ok 
test solana_tests::test_send_spl_token_creates_dest_ata ... ok 
test solana_tests::test_solana_canister_init ... ok 
test vault_tests::test_sol_vault ... ok 
test vault_tests::test_token_vault ... ok

That's a wrap. With these test suit, we can consider our Solana milestone fully covered and much more.

Stay Updated

Get notified when I publish new articles about Web3 development, hackathon experiences, and cryptography insights.

You might also like

icRamp Devlog #10 — Testing Saga 3: Token Registry, Token-2022 & Solid Wasm Paths

We include the Solana token registry tests (incl. Token-2022), fix flaky JSON-RPC shapes, and make the test harness robust with `include_bytes!` + workspace-aware wasm paths.

icRamp Devlog #9 — Testing Saga 2: PocketIC Solana Mocks & Test Harness

We continue the Solana testing story by building a clean HTTP-outcall mocking layer, composable responders, and readable integration tests.

icRamp Devlog #8 — Testing Saga 1: Refractor and Solana Test Expansion

We refractored and improved our testing architecture and expanded it to include a fully-fledged solana backend canister integration test flow.