# Quant VM (QVM) — Specification

**Version:** 1.0.0
**Program:** `3AMM53MsJZy2Jvf7PeHHga3bsGjWV4TSaYz29WUtcdje`
**Formally Verified:** TLA+ (9M states, 7 invariants, 0 violations)

## What is the Quant VM?

The Quant VM (QVM) is an on-chain bytecode virtual machine embedded in the AeX-402 Solana program. It executes composable DeFi strategies — multi-hop swaps, split routing, flash arbitrage, adaptive market-making, conditional branching — atomically in a single Solana transaction.

Unlike smart contract platforms where users deploy code, QVM strategies are **data**: compact bytecode programs (max 256 bytes) interpreted by the on-chain VM. This means:

- **No deployment cost** — strategies are encoded in instruction data
- **Atomic execution** — the entire strategy succeeds or reverts as one unit
- **Composable** — strategies can CALL sub-strategies at known offsets
- **Self-modifying** — adaptive opcodes rewrite strategy parameters at runtime based on live pool state
- **Formally verified** — safety invariants proven via TLA+ model checking

## Architecture

```
┌──────────────────────────────────────────────────────┐
│                    Solana Transaction                  │
│  ┌──────────────────────────────────────────────────┐ │
│  │  Instruction Data                                 │ │
│  │  [disc:8][total_in:8][min_out:8][n_ops:1][bytecode]│
│  └──────────────┬───────────────────────────────────┘ │
│                 │                                      │
│  ┌──────────────▼───────────────────────────────────┐ │
│  │          QVM Interpreter (aex402.c)               │ │
│  │                                                    │ │
│  │  ┌─────┐  ┌─────┐  ┌──────┐  ┌───────┐          │ │
│  │  │ acc │  │acc2 │  │regs[8]│  │splits │          │ │
│  │  └──┬──┘  └──┬──┘  └──┬───┘  └───┬───┘          │ │
│  │     │        │        │           │               │ │
│  │  ┌──▼────────▼────────▼───────────▼────────────┐ │ │
│  │  │  Opcode Dispatch (66 opcodes)                │ │ │
│  │  │  SWAP · SPLIT · JUMP · CALL · ADAPT · ...    │ │ │
│  │  └──────────────────┬──────────────────────────┘ │ │
│  │                     │                             │ │
│  │  ┌──────────────────▼──────────────────────────┐ │ │
│  │  │  Pool State (on-chain accounts)              │ │ │
│  │  │  balance0 · balance1 · amp · fee · candles   │ │ │
│  │  └─────────────────────────────────────────────┘ │ │
│  └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
```

## Execution Modes

| Mode | Discriminator | Behavior |
|------|---------------|----------|
| **rtquote** | `0x727471756f746500` | Read-only simulation. Returns `[acc:u64, fees:u64]` via `sol_set_return_data`. No state changes. |
| **rtexec** | `0x7274657865630000` | Real execution. Performs CPI token transfers, updates pool state, tracks reputation on strategy PDAs. |

Both share identical bytecode format. The difference is account consumption: rtquote reads pool accounts read-only, rtexec requires writable pool + vault accounts for CPI.

## Instruction Format

```
[discriminator: 8 bytes]   — "rtquote\0" or "rtexec\0\0"
[total_in: u64]            — initial accumulator value (input amount in lamports)
[min_out: u64]             — minimum final output (ERR_SLIP if not met, 0 to skip)
[n_ops: u8]                — opcode count hint (max 255)
[bytecode: 1-256 bytes]    — concatenated opcodes
```

### Account Layout

```
accounts[0..N-1]  — op-referenced accounts (pools, vaults, farms)
accounts[N]       — (optional) strategy PDA for reputation tracking
accounts[N+1]     — user (signer, writable)
accounts[N+2]     — SPL Token program
```

Opcodes reference accounts by **slot index**: `SWAP pool dir=0 slot=3` means "swap on the pool at account index 3". The slot stride for 2-token pools is 3: `[pool, vault_in, vault_out]`.

## VM State

| Register | Type | Description |
|----------|------|-------------|
| `acc` | u64 | Primary accumulator — current token amount flowing through the strategy |
| `acc2` | u64 | Secondary accumulator — dual-output ops (REMLIQ, VECLMFEE) |
| `regs[0..7]` | u64×8 | General-purpose registers for store/load/arithmetic |
| `split_depth` | u8 | Current split nesting depth (max 1, no nesting) |
| `call_depth` | u8 | Call stack depth (max 4) |
| `flash_active` | bool | Flash loan outstanding |
| `cu_estimate` | u32 | Running CU consumption (budget: 200,000) |

## Opcode Reference (66 opcodes)

### Core Operations

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| SWAP | 0x01 | type(1) dir(1) slot(1) | 2500 | Swap on a pool. type: 0=Pool, 1=NPool, 2=CL, 3=VPool |
| SPLIT | 0x02 | n(1) pcts(n×2) | 200 | Split acc into N legs by percentage (bps, sum=10000) |
| MERGE | 0x03 | — | 100 | End split, sum all leg outputs into acc |
| LEG | 0x08 | — | 50 | Load next leg's input into acc |
| NOP | 0x07 | — | 10 | No operation |
| HALT | 0xFF | — | 50 | Stop execution. Inside CALL, acts as RET. |

### Conditional Skip

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| SKIP_LT | 0x04 | threshold(8) | 120 | Skip next op if acc < threshold |
| SKIP_GT | 0x05 | threshold(8) | 120 | Skip next op if acc > threshold |
| SKIP_EQ | 0x06 | reg(1) | 80 | Skip next op if acc == regs[reg] |

### Register Operations

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| STORE | 0x10 | reg(1) | 50 | regs[reg] = acc |
| LOAD | 0x11 | reg(1) | 50 | acc = regs[reg] |
| SWAP_REG | 0x12 | reg(1) | 50 | Swap acc ↔ regs[reg] |
| STORE2 | 0x13 | reg(1) | 50 | regs[reg] = acc2 |

### Arithmetic

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| SET | 0x20 | val(8) | 50 | acc = val |
| ADD_R | 0x21 | reg(1) | 50 | acc += regs[reg] |
| SUB_R | 0x22 | reg(1) | 50 | acc -= regs[reg] (errors on underflow) |
| PCT | 0x23 | bps(2) | 50 | acc = acc × bps / 10000 |
| MIN_R | 0x24 | reg(1) | 50 | acc = min(acc, regs[reg]) |
| MAX_R | 0x25 | reg(1) | 50 | acc = max(acc, regs[reg]) |

### Pool Queries

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| BAL | 0x30 | slot(1) | 50 | acc = token balance at slot |
| LP_SUPPLY | 0x31 | slot(1) | 80 | acc = pool LP supply |
| POOL_BAL | 0x32 | slot(1) idx(1) | 80 | acc = pool.balance[idx] (0 or 1) |
| CLOCK | 0x33 | which(1) | 100 | acc = timestamp (0) or slot (1) |

### Assertions

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| ASSERT_GT | 0x40 | threshold(8) | 50 | Error if acc ≤ threshold (unsigned) |
| ASSERT_LT | 0x41 | threshold(8) | 50 | Error if acc ≥ threshold (unsigned) |
| ASSERT_NZ | 0x42 | — | 50 | Error if acc == 0 |
| ASSERT_DEADLINE | 0x43 | ts(8) | 100 | Error if clock.timestamp > ts |
| ASSERT_SGT | 0xC9 | threshold(8) | 100 | Error if (i64)acc ≤ threshold — signed comparison |
| ASSERT_SLT | 0xCA | threshold(8) | 100 | Error if (i64)acc ≥ threshold — signed comparison |

### Liquidity Operations

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| ADD_LIQ | 0x50 | type(1) slot(1) | 2500 | Add balanced liquidity, acc = LP minted |
| ADD_LIQ1 | 0x51 | type(1) side(1) slot(1) | 3000 | Single-sided liquidity |
| REM_LIQ | 0x52 | type(1) slot(1) | 2500 | Remove liquidity, acc=token0 acc2=token1 |

### Farming

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| FARM_STAKE | 0x60 | slot(1) | 150 | Stake LP tokens |
| FARM_UNSTAKE | 0x61 | slot(1) | 150 | Unstake LP tokens |
| FARM_CLAIM | 0x62 | slot(1) | 200 | Claim rewards, acc = reward amount |

### Virtual Pools

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| VP_BUY | 0x70 | slot(1) idx(4) | 2500 | Buy on bonding curve |
| VP_SELL | 0x71 | slot(1) idx(4) | 2500 | Sell on bonding curve |
| VP_CLAIM | 0x72 | slot(1) idx(4) | 200 | Claim vested tokens |

### veToken

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| VE_STAKE | 0x80 | slot(1) dur(4) | 200 | Lock tokens for duration |
| VE_UNSTAKE | 0x81 | slot(1) | 200 | Unlock expired position |
| VE_CLM_FEE | 0x82 | slot(1) | 200 | Claim fee share, dual output |

### Flash Loans

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| F_BORROW | 0x90 | slot(1) dir(1) | 200 | Borrow from pool (fee = pool's fee_bps) |
| F_REPAY | 0x91 | — | 200 | Repay principal + fee. Error if acc < repay amount. |

### Orderbook

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| PLACE_ORD | 0xA0 | slot(1) price(8) | 300 | Place limit order |
| CANCEL_ORD | 0xA1 | slot(1) | 200 | Cancel order |
| FILL_ORD | 0xA2 | slot(1) | 300 | Fill best matching order |

### Token Wrapping

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| WRAP | 0xB0 | slot(1) | 100 | SOL → wSOL |
| UNWRAP | 0xB1 | slot(1) | 100 | wSOL → SOL |

### External DEX (CPI)

These opcodes CPI into external Solana DEX programs. Output is measured by balance delta after the CPI returns (no assumption about return data format). Used for cross-DEX arbitrage strategies.

**EXT_QUOTE (0xD1)** — Read reserves without executing a swap (read-only):

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| EXT_QUOTE | 0xD1 | dex(1) slot(1) | 300 | Read external pool state into acc / acc2 |

`dex` field values and what gets loaded:

| dex | DEX | acc | acc2 |
|-----|-----|-----|------|
| 0 | Raydium V4 (`675kPX9M…`) | coin_reserve | pc_reserve |
| 1 | PumpFun (`6EF8rrec…`) | virtual_sol_reserve | real_token_reserve |
| 2 | Meteora Dynamic AMM (`Eo7WjKq…`) | reserve_x | reserve_y |
| 3 | Aldrin AMM (`dAMMP3un…`) | base_reserve | quote_reserve |
| 4 | Meteora DLMM (`LBUZKhRx…`) | active_bin_id (i64 → u64) | reserve_x at offset 144 |

**EXT_SWAP (0xD0)** — Execute CPI swap:

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| EXT_SWAP | 0xD0 | dex(1) dir(1) slot(1) est_reg(1) mo_reg(1) | 5000–8000 | CPI swap on external DEX |

- In **rtquote** mode: returns `regs[est_reg] * 95%` (5% conservatism discount). No CPI executed.
- In **rtexec** mode: executes CPI, measures output by balance delta, aborts if output < `regs[mo_reg]`.
- `slot`: index into `remaining_accounts[]` for the DEX account group.

`dex` field and account strides:

| dex | DEX | `dir` meaning | Stride (accounts) |
|-----|-----|---------------|-------------------|
| 0 | Raydium V4 | 0=A→B, 1=B→A | 18 |
| 1 | PumpFun | 0=buy, 1=sell | 12 |
| 2 | Meteora Dynamic AMM | 0=X→Y, 1=Y→X | 14 |
| 3 | Aldrin AMM | 0=base→quote, 1=quote→base | 11 |
| 4 | Meteora DLMM | **bin_n** (0=default 3, 1-5=explicit count) | 15 + bin_n |

For `dex=4` (DLMM), `dir` is repurposed to encode `bin_n` — the number of pre-computed `BinArray` accounts appended after the 15 fixed DLMM accounts. Direction is encoded in **account ordering**: `usr_in` at slot+4 is the input token ATA; swap direction inferred from which token it holds.

DLMM fixed account layout at `slot+0`:
```
slot+0:  lb_pair (writable)
slot+1:  bitmap_extension (readable)
slot+2:  reserve_x (writable)
slot+3:  reserve_y (writable)
slot+4:  user_in ATA (writable)
slot+5:  user_out ATA (writable)
slot+6:  mint_x (readable)
slot+7:  mint_y (readable)
slot+8:  oracle (writable)
slot+9:  host_fee_in (readable)
slot+10: user (signer)
slot+11: token_program_x (readable)
slot+12: token_program_y (readable)
slot+13: event_authority (readable)
slot+14: dlmm_program (readable)
slot+15..slot+15+bin_n-1: BinArray accounts (writable, pre-derived by client)
```

**EXT_DLMM_REFINE (0xD2)** — DLMM swap with on-chain bin pool selection:

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| EXT_DLMM_REFINE | 0xD2 | pool_n(1) slot(1) est_reg(1) mo_reg(1) | 6000–10000 | Scan candidate bin arrays, select correct 3, execute DLMM swap |

Solves the **Custom:3005 stale active_bin_id** problem without requiring an off-chain refresh loop. The client pre-loads `pool_n` candidate `BinArray` accounts (3–7) covering ±N bin array indices around the expected active position. At execution time, the VM reads `BinArray.index` at byte offset 8 (after the 8-byte Anchor discriminator) from each candidate, selects the 3 forming a contiguous window around the live `active_bin_id`, then executes the CPI.

Aborts with `ERR_STALE` (6028) if no matching 3-array window is found among the candidates.

Account stride: `15 + pool_n` (same fixed layout as EXT_SWAP dex=4, with `pool_n` candidate bin arrays instead of exact arrays).

**Staleness protection pattern** (builder API):

```
EXT_QUOTE  dex=dlmm, slot=0     → acc = active_bin_id (sign-extended i64 → u64)
dlmmBinIdx base=K, reg=r0       → STORE r0; [ASSERT_GT/SGT lo]; [ASSERT_LT/SLT hi]
EXT_SWAP   dex=dlmm, bin_n=3    → execute with pre-derived PDAs (fast path)
```

`dlmmBinIdx` emits unsigned bound checks (ASSERT_GT/LT) when both bounds share the same sign — sign-extended i64 negatives preserve unsigned ordering. For cross-zero windows (e.g., `base_arr_idx` ∈ {-1, 0}), it emits signed ASSERT_SGT/ASSERT_SLT (0xC9/0xCA) instead, enabling correct guards across the i64 sign boundary.

Or use `EXT_DLMM_REFINE` for the slower but more robust on-chain selection path:

```
EXT_DLMM_REFINE pool_n=5, slot=0  → select 3 from 5 candidates, execute
```

**Preset**: `aex strategy disasm dlmm-arb` — EXT_QUOTE → STORE → EXT_SWAP with pre-set est/min_out registers.

**Simulating with live DLMM state:** pass a Meteora LbPair address via `--pool`. The CLI auto-detects the account owner, parses `active_bin_id` (i32 at offset 76) and `bin_step` (u16 at offset 73), and seeds the simulator with real bin position data:

```bash
aex strategy sim --preset dlmm-arb --pool <lb_pair_address> -t
# Output: active_bin_id from live chain, EXT_QUOTE/EXT_SWAP with real position
```

Without `--pool`, simulation uses synthetic defaults. The vault reserve balance (`reserve_x` at offset 144 of LbPair) requires a second RPC call and defaults to 0 in simulation.

### Branching (Phase 4)

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| JUMP | 0xC0 | offset(2) | 50 | Unconditional jump to bytecode offset |
| JUMP_IF_GT | 0xC1 | threshold(8) offset(2) | 80 | Jump if acc > threshold (unsigned) |
| JUMP_IF_LT | 0xC2 | threshold(8) offset(2) | 80 | Jump if acc < threshold (unsigned) |
| JUMP_IF_SGT | 0xCB | threshold(8) offset(2) | 80 | Jump if (i64)acc > threshold — signed |
| JUMP_IF_SLT | 0xCC | threshold(8) offset(2) | 80 | Jump if (i64)acc < threshold — signed |

### Adaptive (Self-Modifying)

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| ADAPT_SPLIT | 0xC3 | slot_a(1) slot_b(1) | 200 | Rewrite split pcts by pool depth ratio. Deeper pool gets more traffic. |
| AUTO_DIR | 0xC4 | slot(1) reg(1) | 80 | Set reg to optimal swap direction (0 if bal0≥bal1, 1 otherwise) |
| PRICE_GUARD | 0xC5 | slot(1) threshold(2) | 120 | Halt if spot price drifted > threshold bps from acc |
| SCALE_DEPTH | 0xC6 | slot(1) ref_depth(8) | 100 | Scale acc by pool depth / reference depth |

### Composability

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| CALL | 0xC7 | offset(2) | 80 | Push return address, jump to offset. Max depth: 4. |
| RET | 0xC8 | — | 50 | Pop call stack, return to caller |

### Events

| Opcode | Hex | Args | CU | Description |
|--------|-----|------|----|-------------|
| EMIT | 0xF0 | tag(1) | 50 | Emit event with tag + acc |
| EMIT2 | 0xF1 | tag(1) | 50 | Emit event with acc + acc2 |

## Safety Guarantees

Proven via TLA+ model checking (9,044,600 states explored):

1. **No infinite loops** — 1000-op execution guard + 200K CU budget
2. **No call stack overflow** — call_depth capped at 4
3. **No split stack overflow** — split_depth capped at implementation limit
4. **Valid split percentages** — ADAPT_SPLIT always produces pcts ∈ [500, 9500], sum = 10000
5. **Flash loan safety** — successful termination ⟹ flash loan repaid
6. **Split closure** — successful termination ⟹ no unclosed splits
7. **Type correctness** — all state variables within declared bounds

Adversarial CU analysis: worst-case successful strategy consumes 112,150 CU (56.1% of budget).

## Strategy Marketplace

### On-Chain Registry

Strategies can be published on-chain as PDAs:

| Handler | Discriminator | Description |
|---------|---------------|-------------|
| `streg` | "streg\0\0\0" | Register/upgrade strategy bytecode |
| `stfork` | "stfork\0\0" | Fork a strategy (increments source fork count) |
| `stclaim` | "stclaim\0" | Creator withdraws accumulated DAO fees |
| `stbet` | "stbet\0\0\0" | Place long/short bet on strategy performance |
| `stsettle` | "ststtl\0\0" | Settle bet after 10+ executions |
| `stfund` | "stfund\0\0" | Create/deposit to autonomous strategy fund |
| `stfexec` | "stfexec\0" | Fund executes a strategy |

### Strategy PDA Layout (380 bytes)

```
[0..8]     discriminator ("STRATEGY")
[8..40]    creator pubkey (32)
[40..42]   bytecode length (u16)
[42..46]   fork count (u32)
[46..48]   version (u16) — increments on upgrade
[48..304]  bytecode (256 bytes max)
[304..312] exec_count (u64)
[312..320] success_count (u64)
[320..328] total_input (u64)
[328..336] total_output (u64)
[336..344] total_profit (i64)
[344..352] total_fees (u64) — creator DAO earnings
[352..360] best_profit (u64)
[360..368] worst_loss (u64)
[368..376] last_exec_ts (i64)
[376..380] avg_cu (u32)
```

### DAO Fee Model

Every `rtexec` that references a strategy PDA as the last writable account transfers **0.1 bps** (1/100,000) of the input amount to the strategy PDA as creator earnings. Creators withdraw via `stclaim`.

### Composability ABI (v1)

```
[0..2]   Magic: "SA" (0x53, 0x41)
[2]      Version: 1
[3]      n_entries (max 16)
[4..4+n*6]  Entry table: [name_hash(4), offset(2)] × n
[table_end..]  Bytecode (main + library routines)
```

Library routines are invoked via CALL. Standard library names:
- `swap_opt` — AUTO_DIR + ADAPT_SPLIT + SWAP
- `risk_chk` — PRICE_GUARD + SCALE_DEPTH
- `flash_arb` — FBORROW + SWAP chain + FREPAY + ASSERT
- `compound` — FARM_CLAIM + SWAP + ADDLIQ + FARM_STAKE

## API

### REST (rpc.aex402.com)

```
POST /v1/strategy/simulate
  Body: { "bytecode": "<hex>", "pools": ["<base58>", ...], "amount": 1000000000 }
  Returns: { "success": bool, "output": "u64", "fees": "u64", "cu_consumed": u32 }

GET /v1/strategy/browse
  Returns: { "strategies": [...], "count": N }
```

### CLI

```bash
aex strategy list                          # Show presets
aex strategy sim --preset adaptive --trace # Simulate with trace
aex strategy exec --preset 2hop --pool <p1> --pool <p2> --amount 1000000000
aex strategy sweep --preset adaptive       # Amount sweep (0.001..1000 SOL)
aex strategy backtest --preset roundtrip --pool <addr>
aex strategy compete --a adaptive --b 2hop --rounds 1000
aex strategy evolve --gens 100 --pop 64    # Genetic algorithm
aex strategy fuzz --iters 50000            # Random fuzz testing
aex strategy fuzz --adversarial --iters 10000  # CU maximization GA
aex strategy register --preset adaptive    # Publish on-chain
aex strategy browse --sort profit          # Leaderboard
aex strategy browse --detail <addr>        # Deep-dive view
```

### Daemon

```bash
aex402-daemon arb --execute --rpc-url <url>  # Reactive arb scanner
aex402-daemon arb --swarm 4                  # 4 parallel scanners
```
