Single-asset tokenized vault built with Pinocchio.
This repo implements the core vault instruction surface:
initialize_vaultdepositwithdrawpauseunpausetransfer_adminaccept_admin
The vault is similar in spirit to ERC-4626, but intentionally smaller: one underlying SPL Token mint, one share mint, PDA custody, conservative share accounting, and authority controls.
- Program ID: not deployed yet
Implemented and tested:
- vault initialization with deterministic PDA seeds
- PDA-controlled underlying token custody
- PDA-controlled share mint authority
- first-deposit 1:1 share minting
- proportional deposits after bootstrap
- proportional withdrawals by burning shares
- conservative floor rounding for deposits and withdrawals
- user slippage protection on deposit and withdraw
- pause and unpause controls
- two-step admin transfer
- wrong user underlying mint rejection
- non-admin admin-transfer rejection
- SBF build loaded into LiteSVM tests
Not implemented yet:
- yield strategy integration
- Token-2022 support
- protocol, management, or performance fees
- governance
- exact preview helpers
- fuzz tests across many randomized users
flowchart LR
A[Underlying SPL mint]
B[Share SPL mint]
C[Initialize Vault]
D[Create Vault State PDA]
E[Validate Vault Authority PDA]
F[Validate Underlying Vault]
A --> C
B --> C
C --> D
C --> E
C --> F
Each vault stores:
adminpending_adminpause_authorityunderlying_mintshare_mintunderlying_vaultvault_authoritytotal_underlyingtotal_shares- PDA bumps
Vault state PDA:
[b"vault", underlying_mint, share_mint]
Vault authority PDA:
[b"vault_authority", vault_state]
This guarantees one deterministic vault state account per underlying/share mint pair.
flowchart LR
A[User wallet<br/>Underlying tokens]
B[Deposit]
C[Underlying vault receives assets]
D[User receives vault shares]
E[Vault accounting increases]
A --> B
B --> C
B --> D
B --> E
Vault shares represent proportional ownership of the vault's underlying balance.
Bootstrap deposit:
- shares minted =
amount_in
Non-bootstrap deposit:
- shares minted =
floor(amount_in * total_shares / total_underlying)
Deposits round down against the depositor. If rounding produces zero shares, the instruction fails.
flowchart LR
A[User holds vault shares]
B[Withdraw]
C[Burn user shares]
D[Transfer underlying from vault]
E[Vault accounting decreases]
A --> B
B --> C
B --> D
B --> E
Withdrawals burn shares and return proportional underlying.
Underlying returned:
underlying_out = floor(shares_in * total_underlying / total_shares)
Withdrawals round down against the withdrawer. If rounding produces zero underlying, the instruction fails.
The vault separates operational and ownership controls:
vault_authorityis a PDA used for SPL Token CPIspause_authoritycan pause and unpause deposits and withdrawalsadmincan start an admin handoffpending_adminmust accept the handoff before admin changes
This keeps token custody program-controlled and avoids single-step admin replacement.
flowchart TD
A[User Wallet]
B[Vault Instructions<br/>initialize_vault<br/>deposit<br/>withdraw<br/>pause<br/>unpause]
C[Vault State PDA]
D[Vault Authority PDA]
E[Underlying Token Vault]
F[Share Mint]
G[User Share Account]
A --> B
B --> C
C --> D
D --> E
D --> F
F --> G
Vault math lives in math.rs:
checked_mul_div_floorcalculate_deposit_sharescalculate_withdraw_underlying
This keeps:
- instruction orchestration
- token CPIs
- pure share accounting
separated cleanly.
Program entrypoint:
Processor and dispatch:
Vault state and PDA helpers:
Reusable token validation helpers:
Error definitions:
All instruction data is fixed-size and little-endian.
| Discriminant | Instruction | Payload |
|---|---|---|
0 |
initialize_vault |
none |
1 |
deposit |
amount_in: u64, minimum_shares_out: u64 |
2 |
withdraw |
shares_in: u64, minimum_underlying_out: u64 |
3 |
pause |
none |
4 |
unpause |
none |
5 |
transfer_admin |
new_admin: [u8; 32] |
6 |
accept_admin |
none |
- Payer signer, writable
- Admin signer
- Vault state PDA, writable
- Vault authority PDA
- Underlying mint
- Share mint
- Underlying vault token account
- System program
- Token program
The payer funds the new vault state account. The share mint and underlying vault token account must already exist and must be controlled by the vault authority PDA.
- Depositor signer
- Vault state, writable
- Vault authority PDA
- User underlying token account, writable
- Underlying vault token account, writable
- Share mint, writable
- User share token account, writable
- Token program
- Withdrawer signer
- Vault state, writable
- Vault authority PDA
- User share token account, writable
- Share mint, writable
- Underlying vault token account, writable
- User underlying token account, writable
- Token program
- Pause authority signer
- Vault state, writable
- Admin signer
- Vault state, writable
- Pending admin signer
- Vault state, writable
This repo uses Rust unit tests and LiteSVM integration tests.
Test layers:
- Rust unit tests for math and instruction decoding
- LiteSVM integration tests that load the compiled SBF program
Current integration coverage includes:
- initialize vault happy path
- first deposit success
- second deposit success
- partial withdraw success
- pause blocks deposit
- pause blocks withdraw
- unpause restores operations
- two-step admin transfer success
- wrong user underlying mint rejection
- non-admin transfer-admin rejection
See:
Build the SBF program:
cargo build-sbf --features bpf-entrypointRun Rust unit tests and LiteSVM integration tests:
cargo testRun formatting:
cargo fmt --all --checkRun Clippy:
cargo clippy --all-targets --all-features -- -D warningsThe LiteSVM tests load:
target/deploy/pinocchio_vault.so
So run the SBF build before the full test suite if the artifact is missing or stale.
The important part of a vault is not the number of instructions. It is the accounting invariant.
For this vault, the bar is:
- shares cannot be minted without an underlying deposit
- underlying cannot leave without burning shares
- vault accounting tracks token balances and share supply
- rounding is conservative
- authority checks happen before value-moving CPIs
Once these rules are reliable, fees, strategies, Token-2022, and governance can be added without weakening the base primitive.