
icRamp Devlog #11 — Testing Saga 4: Vault State (SOL + SPL), Full Suite Green
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.