A sharded perpetual exchange protocol for Solana, implementing the design from plan.md.
Percolator consists of two main on-chain programs:
The global coordinator managing collateral, portfolio margin, and cross-slab routing.
Program ID: RoutR1VdCpHqj89WEMJhb6TkGT9cPfr1rVjhM3e2YQr
State structures:
Vault- Collateral custody per asset mintEscrow- Per (user, slab, mint) pledges with anti-replay noncesCap(Capability) - Time-limited, scoped debit authorization tokens (max 2 minutes TTL)Portfolio- Cross-margin tracking with exposure aggregation across slabsSlabRegistry- Governance-controlled registry with version validation
PDA Derivations:
- Vault:
[b"vault", mint] - Escrow:
[b"escrow", user, slab, mint] - Capability:
[b"cap", user, slab, mint, nonce_u64] - Portfolio:
[b"portfolio", user] - Registry:
[b"registry"]
LP-run perp engines with 10 MB state budget, fully self-contained matching and settlement.
Program ID: SLabZ6PsDLh2X6HzEoqxFDMqCVcJXDKCNEYuPzUvGPk
State structures:
SlabHeader- Metadata, risk params, anti-toxicity settingsInstrument- Contract specs, oracle prices, funding rates, book headsOrder- Price-time sorted orders with reservation trackingPosition- User positions with VWAP entry pricesReservation- Reserve-commit two-phase execution stateSlice- Sub-order fragments locked during reservationTrade- Ring buffer of executed tradesAggressorEntry- Anti-sandwich tracking per batch
PDA Derivations:
- Slab State:
[b"slab", market_id] - Authority:
[b"authority", slab]
- 10 MB budget strictly enforced at compile time
- O(1) freelist-based allocation for all pools
- Zero allocations after initialization
- Pool sizes (tuned to fit within 10 MB):
- Accounts: 5,000
- Orders: 30,000
- Positions: 30,000
- Reservations: 4,000
- Slices: 16,000
- Trades: 10,000 (ring buffer)
- Instruments: 32
- DLP accounts: 100
- Aggressor entries: 4,000
- Price-time priority with strict FIFO at same price level
- Reserve operation: Walk book, lock slices, calculate VWAP/worst price
- Commit operation: Execute at captured maker prices
- Cancel operation: Release reservations
- Pending queue promotion: Non-DLP orders wait one batch epoch
- Order book management: Insert, remove, promote with proper linking
- Local (slab) margin: IM/MM calculated per position
- Global (router) margin: Cross-slab portfolio netting
- Equity calculation with unrealized PnL and funding payments
- Pre-trade margin checks
- Liquidation detection
- Time-limited caps (max 2 minutes TTL)
- Scoped to (user, slab, mint) triplet
- Anti-replay with nonces
- Remaining amount tracking
- Automatic expiry checks
- 6-decimal precision for prices
- VWAP calculations
- PnL computation
- Funding payment tracking
- Margin calculations in basis points
- Router: Vault, Escrow, Capability, Portfolio, Registry PDAs
- Slab: Slab State, Authority PDAs
- Verification functions for account validation
- Comprehensive seed management
- 6 instruction types: Reserve, Commit, Cancel, BatchOpen, Initialize, AddInstrument
- Discriminator-based routing
- Error handling for invalid instructions
- Account validation framework ready
- Batch windows (
batch_ms) - Delayed maker posting (pending → live promotion)
- JIT penalty detection
- Kill band parameters
- Freeze levels configuration
- Aggressor roundtrip guard (ARG) data structures
- Panic handlers for no_std builds
panic = "abort"configuration- Pinocchio integration for zero-dependency Solana programs
53 tests passing across all packages:
- ✅ VWAP calculations (single/multiple fills, zero quantity)
- ✅ PnL calculations (long/short profit/loss, no change)
- ✅ Funding payment calculations
- ✅ Tick/lot alignment and rounding
- ✅ Margin calculations (IM/MM, scaling with quantity/price)
- ✅ Type defaults (Side, TimeInForce, MakerClass, OrderState, Order, Position)
- ✅ Vault pledge/unpledge operations
- ✅ Escrow credit/debit with nonce validation
- ✅ Capability lifecycle (creation, usage, expiry)
- ✅ Capability TTL capping (max 2 minutes)
- ✅ Portfolio exposure tracking
- ✅ Portfolio margin aggregation
- ✅ Registry operations (add/validate slabs)
- ✅ Pool allocation/free operations
- ✅ Pool capacity limits and reuse
- ✅ Header validation and monotonic IDs
- ✅ JIT penalty detection
- ✅ Timestamp updates
- ✅ Book sequence numbers
- ✅ Reserve operation with max charge calculation
- ✅ Margin requirement calculations
- ✅ Slab size constraint (≤10 MB)
Note: PDA tests require Solana syscalls and are marked #[cfg(target_os = "solana")]. They will be tested in integration tests with Surfpool.
# Build all programs (libraries)
cargo build
# Build in release mode
cargo build --release
# Build specific package
cargo build --package percolator-slab# Run all tests
cargo test
# Run only library tests
cargo test --lib
# Run tests for specific package
cargo test --package percolator-common
cargo test --package percolator-router
cargo test --package percolator-slab
# Run specific test
cargo test test_vwap_calculation
# Run tests with output
cargo test -- --nocapture
# Run tests in release mode (faster)
cargo test --release# Install Solana toolchain (if not already installed)
sh -c "$(curl -sSfL https://release.solana.com/stable/install)"
# Build BPF programs
cargo build-sbf
# Build specific program
cargo build-sbf --manifest-path programs/slab/Cargo.toml
cargo build-sbf --manifest-path programs/router/Cargo.tomlSurfpool provides a local Solana test validator with mainnet state access for realistic integration testing.
# Clone surfpool
git clone https://github.com/txtx/surfpool
cd surfpool
# Install dependencies
npm install
# Start local validator
npm run validatorCreate tests/integration/ directory for surfpool-based tests:
// tests/integration/test_reserve_commit.rs
use surfpool::prelude::*;
use percolator_slab::*;
use percolator_router::*;
#[surfpool::test]
async fn test_reserve_and_commit_flow() {
// Initialize test environment
let mut context = SurfpoolContext::new().await;
// Deploy programs
let router_program = context.deploy_program("percolator_router").await;
let slab_program = context.deploy_program("percolator_slab").await;
// Initialize slab state (10 MB account)
let slab_pda = derive_slab_pda(b"BTC-PERP", &slab_program.id());
context.create_account(&slab_pda, 10 * 1024 * 1024, &slab_program.id()).await;
// Initialize router accounts
let vault_pda = derive_vault_pda(&usdc_mint, &router_program.id());
// ... setup vault, escrow, portfolio
// Test reserve operation
let reserve_ix = create_reserve_instruction(/* ... */);
context.send_transaction(&[reserve_ix]).await.unwrap();
// Verify reservation created
let slab_state = context.get_account::<SlabState>(&slab_pda).await;
assert!(slab_state.reservations.used() > 0);
// Test commit operation
let commit_ix = create_commit_instruction(/* ... */);
context.send_transaction(&[commit_ix]).await.unwrap();
// Verify trade executed
assert_eq!(slab_state.trade_count, 1);
}# Start surfpool validator (terminal 1)
cd surfpool && npm run validator
# Run integration tests (terminal 2)
cargo test --test integration
# Run specific integration test
cargo test --test integration test_reserve_and_commit_flow-
Order Matching
- Place limit orders on both sides
- Execute market order
- Verify VWAP calculation and position updates
-
Reserve-Commit Flow
- Reserve liquidity for aggregator order
- Verify slices locked correctly
- Commit at reserved prices
- Check trades executed at expected prices
-
Cross-Slab Portfolio
- Open positions on multiple slabs
- Verify router aggregates exposures
- Check cross-margin calculation
-
Capability Security
- Create time-limited cap
- Use cap to debit escrow
- Verify expiry enforcement
-
Anti-Toxicity
- Post pending order
- Open batch window
- Verify promotion after epoch
- Test JIT penalty application
-
Liquidation
- Open underwater position
- Trigger liquidation
- Verify position closure and PnL settlement
Safety:
- Slabs cannot access Router vaults directly
- Slabs can only debit via unexpired, correctly scoped Caps
- Total debits ≤ min(cap.remaining, escrow.balance)
- No cross-contamination: slab cannot move funds for (user', slab') ≠ (user, slab)
Matching:
- Price-time priority strictly maintained
- Reserved qty ≤ available qty always
- Book links acyclic and consistent
- Pending orders never match before promotion
Risk:
- IM monotone: increasing exposure increases margin
- Portfolio IM ≤ Σ slab IMs (convexity not double-counted)
- Liquidation triggers only when equity < MM
Anti-Toxicity:
- Kill band: reject if mark moved > threshold
- JIT penalty: DLP orders posted after batch_open get no rebate
- ARG: roundtrip trades within batch are taxed/clipped
- Core data structures (Router & Slab)
- Memory pools with O(1) freelists
- Order book management (insert, remove, promote)
- Reserve operation (lock slices, calculate VWAP)
- Commit operation (execute trades at maker prices)
- Risk calculations (equity, IM/MM, liquidation checks)
- Capability system (time-limited scoped debits)
- Fixed-point math utilities (VWAP, PnL, margin)
- Compile-time size constraints (10 MB enforced)
- PDA derivation helpers (all account types)
- Instruction dispatching framework
- BPF build support (panic handlers, no_std)
- Comprehensive unit tests (53 tests passing)
- Integration tests with Surfpool
- Property-based invariant testing
- BPF deployment and CU benchmarks
- Anti-toxicity mechanism integration (kill band, ARG enforcement)
- Funding rate updates (time-weighted calculations)
- Liquidation execution (position closure, PnL settlement)
- Router orchestration (multi-slab reserve/commit atomicity)
- Instruction handler implementations (account validation, parsing)
- Account initialization helpers
- Client SDK (TypeScript/Rust)
- CLI tools for LP operations
- Documentation and examples
- Framework: Pinocchio v0.9.2 - Zero-dependency Solana SDK
- Testing: Surfpool - Local Solana test validator with mainnet state
- Language: Rust (no_std, zero allocations, panic = abort)
- Plan Document - Full protocol specification
- Pinocchio Docs
- Surfpool
- Solana Cookbook
Apache-2.0
Status: Core infrastructure complete ✅ | 53 tests passing ✅ | Ready for integration testing 🚀