# Strategy VM Tutorial

Build, simulate, trace, and execute multi-step DeFi strategies on AeX-402 pools — all from the CLI.

## What is the Strategy VM?

The Strategy VM is an on-chain bytecode interpreter built into the AeX-402 program. Instead of sending one swap transaction at a time, you compose a sequence of operations (swap, split, assert, store/load registers) into a compact bytecode program that executes atomically in a single transaction.

Two execution modes:
- **rtquote** — read-only simulation on-chain (no state changes, returns output)
- **rtexec** — real execution with CPI transfers (moves tokens)

The CLI provides a local simulator that runs the same logic off-chain, so you can validate strategies before spending SOL on transaction fees.

## Quick Start

```bash
# See available preset strategies
aex strategy list

# Disassemble a preset to see its opcodes
aex strategy disasm 2hop

# Simulate with synthetic pool state (no RPC needed)
aex strategy sim --preset 2hop

# Simulate with trace (shows acc/regs at every op)
aex strategy sim --preset 2hop --trace

# Simulate against real pool state
aex strategy sim --preset 2hop \
  --pool <pool1_address> --pool <pool2_address>

# Execute on-chain (simulate first, then send TX)
aex strategy exec --preset 2hop \
  --pool <pool1_address> --pool <pool2_address> \
  --amount 1000000000 --min-out 950000000
```

## Walkthrough: 2-Hop Swap

### Step 1: Understand the strategy

A 2-hop swap routes through two pools sequentially: Token A -> Token B -> Token C.

```bash
$ aex strategy disasm 2hop
```
```
Preset: 2hop — 2-pool swap chain (A->B->C)

Strategy: total_in=0 min_out=0 n_ops=2 mode=quote
  op 1: SWAP pool dir=0 slot=0
  op 2: SWAP pool dir=1 slot=3
```

What this means:
- **op 1**: Swap through pool at slot 0, direction 0 (token0 -> token1)
- **op 2**: Swap through pool at slot 3, direction 1 (token1 -> token0)
- **slot** is an index into the transaction's account array. Each pool uses 3 accounts (pool state, vault_in, vault_out), so pool 0 is at slot 0 and pool 1 is at slot 3.

### Step 2: Simulate locally

Run the strategy against synthetic pools to see approximate output:

```bash
$ aex strategy sim --preset 2hop --trace
```
```
Strategy: 2hop — 2-pool swap chain (A->B->C)
Input: 1000000000, Min output: 0

Strategy: total_in=1000000000 min_out=0 n_ops=2 mode=quote
  op 1: SWAP pool dir=0 slot=0
  op 2: SWAP pool dir=1 slot=3

No --pool flags. Using synthetic pool state (1T:1T, amp=100, 30bps) at all slots.

Result: OK
  Output:       993999155
  Ops executed: 2
  CU estimate:  ~5000

Execution trace:
op#  opcode                    acc             acc2  regs (non-zero)
---  ------------ ---------------- ----------------  --------------------
  1  SWAP                996995055                0
  2  SWAP                993999155                0
```

Reading the trace:
- The **accumulator** (`acc`) carries the token amount through the pipeline
- After op 1: 1 SOL -> 996,995,055 (lost ~3M to fees on a 1T:1T pool)
- After op 2: 996M -> 993,999,155 (lost another ~3M to fees)
- **CU estimate ~5000**: two Newton-Raphson swap computations at ~2500 CU each

### Step 3: Simulate against real pools

Replace synthetic state with actual on-chain pool data:

```bash
$ aex strategy sim --preset 2hop --trace \
  --pool <SOL_USDC_pool_address> \
  --pool <USDC_USDT_pool_address>
```

The CLI fetches each pool's balances, amp, and fee from devnet/mainnet RPC and runs the simulation against real state. The output will reflect actual slippage and pool depth.

### Step 4: Set slippage protection

Add `--min-out` to catch bad routes:

```bash
$ aex strategy sim --preset 2hop \
  --pool <pool1> --pool <pool2> \
  --amount 1000000000 --min-out 950000000
```

If the simulated output is below 950M, you'll see:
```
  Slippage:     FAIL (output 943000000 < min 950000000)
```

### Step 5: Execute on-chain

When simulation looks good, send the real transaction:

```bash
$ aex strategy exec --preset 2hop \
  --pool <pool1> --pool <pool2> \
  --amount 1000000000 --min-out 950000000
```

The exec command:
1. Fetches pool state from RPC
2. Runs pre-flight simulation locally
3. Aborts if simulated output < min_out
4. Derives your token account ATAs automatically
5. Orders vault accounts correctly based on swap direction
6. Shows a summary and asks for confirmation: `Send this transaction? [y/N]`
7. Sends the transaction with a CU budget based on the simulation estimate

Use `--dry-run` to do everything except actually send:
```bash
$ aex strategy exec --preset 2hop --pool <p1> --pool <p2> \
  --amount 1000000000 --dry-run
```

## Split Routing

Split your input across multiple pools to reduce price impact:

```bash
$ aex strategy sim --preset split-60-40 --trace
```
```
Strategy: split-60-40 — 60/40 split across 2 pools
Input: 1000000000, Min output: 0

Strategy: total_in=1000000000 min_out=0 n_ops=6 mode=quote
  op 1: SPLIT n=2 pcts=[6000, 4000]
  op 2: LEG
  op 3: SWAP pool dir=0 slot=0
  op 4: LEG
  op 5: SWAP pool dir=0 slot=3
  op 6: MERGE

Result: OK
  Output:       996997429

Execution trace:
op#  opcode                    acc             acc2  regs (non-zero)
---  ------------ ---------------- ----------------  --------------------
  1  SPLIT              1000000000                0  [split:1]
  2  LEG                 600000000                0  [split:1]
  3  SWAP                598198220                0  [split:1]
  4  LEG                 400000000                0  [split:1]
  5  SWAP                398799209                0  [split:1]
  6  MERGE               996997429                0
```

The split strategy produces 997M vs the 2hop's 994M on the same pools — better output because each leg has less price impact on the deep synthetic pools.

The trace shows:
- **SPLIT** saves the accumulator and records percentages
- **LEG** loads the next leg's portion (600M for 60%, 400M for 40%)
- **MERGE** sums all leg outputs
- `[split:1]` means we're inside a split at depth 1

## Round-Trip (Fee Check)

Test how much a round-trip through the same pool costs:

```bash
$ aex strategy sim --preset roundtrip --trace
```
```
Execution trace:
op#  opcode                    acc             acc2  regs (non-zero)
---  ------------ ---------------- ----------------  --------------------
  1  SWAP                996995055                0
  2  STORE               996995055                0  r0=996995055
  3  SWAP                994009030                0  r0=996995055
  4  ASSERT_GT           994009030                0  r0=996995055
```

Started with 1,000,000,000 and ended with 994,009,030 — lost ~6M (0.6%) to fees + slippage on a round trip. The `ASSERT_GT 0` ensures we got something back (would catch a broken pool).

## Building Custom Strategies in Zig

The CLI presets cover common patterns, but you can build any strategy programmatically:

```zig
const Strategy = @import("strategy").Strategy;
const Simulator = @import("simulator").Simulator;

// Flash arbitrage: borrow from pool A, swap through B and C, repay, keep profit
var s = Strategy.initExec(0, 1); // min_out=1 (must be profitable)
s.fBorrow(0, 0);       // flash borrow token0 from pool at slot 0
s.swap(.pool, 0, 3);   // swap through pool at slot 3
s.swap(.pool, 1, 6);   // swap through pool at slot 6
s.fRepay();             // repay flash loan + fee
s.assertGt(0);          // abort if not profitable
s.emit(0x01);           // log the profit

// Simulate before sending
var sim = Simulator.init(5_000_000_000); // 5 SOL flash borrow
sim.setPool(0, pool_a_state);
sim.setPool(3, pool_b_state);
sim.setPool(6, pool_c_state);

switch (sim.run(&s)) {
    .ok => |r| std.debug.print("Profit: {d}\n", .{r.acc}),
    .err => |e| std.debug.print("Would fail: {s}\n", .{@tagName(e.err)}),
}
```

## Disassembling Raw Bytecode

If you have strategy bytecode from a transaction log or another tool, decode it:

```bash
# Hex bytecode: SWAP(pool,dir=0,slot=0) + SWAP(pool,dir=1,slot=3)
$ aex strategy disasm 01000000010001030100010300
```

## Composite Macros

The WASM editor (DeFi > Strat > press `m`) provides 8 composite macros that expand to real opcode sequences. Each macro takes 1-2 args and generates 3-9 ops:

| Macro | What it does | Expands to |
|-------|-------------|-----------|
| **Zap In** | Single-token LP entry | store → pct(50%) → swap → store → load → pct(50%) → addLiq |
| **Zap Out** | Single-token LP exit | remLiq → store → store2 → load/swap → addR |
| **Harvest** | Compound farm rewards | farmClaim → store → pct → swap → store → load → pct → addLiq → farmStake |
| **Exit If Low** | Conditional exit on price drop | poolBal → skipGt(threshold) → remLiq |
| **Exit If High** | Conditional exit on price rise | poolBal → skipLt(threshold) → remLiq |
| **Hedge** | Reduce directional exposure | store → pct(50%) → swap → store → load → pct(50%) |
| **Sandwich Guard** | Abort if price impact too high | poolBal → store → poolBal → subR → assertLt |
| **DCA Split** | Equal split across 2-4 pools | split(N) → [leg → swap] × N → merge |

**Important:** Exit If Low / Exit If High check the pool's on-chain balance at the time of execution, not your entry price. For persistent price monitoring, use `aex402-daemon` which polls continuously and sends exit TXs when conditions trigger.

The disassembler shows the expanded opcodes — what you see is what executes on-chain.

## All 48 Opcodes

The Strategy VM supports 48 opcodes across 12 categories:

| Category | Opcodes | Description |
|----------|---------|-------------|
| Swap | `SWAP` | Through pool, npool, cl, or vpool |
| Control | `SPLIT`, `LEG`, `MERGE`, `SKIP_LT/GT/EQ`, `NOP` | Flow control |
| Registers | `STORE`, `LOAD`, `SWAP_REG`, `STORE2` | 8 general-purpose registers |
| Arithmetic | `SET`, `ADD`, `SUB`, `PCT`, `MIN`, `MAX` | Accumulator math |
| State | `BAL`, `LP_SUPPLY`, `POOL_BAL`, `CLOCK` | Read on-chain state |
| Assertions | `ASSERT_GT/LT/NZ`, `ASSERT_DEADLINE` | Abort conditions |
| Liquidity | `ADD_LIQ`, `ADD_LIQ1`, `REM_LIQ` | LP operations |
| Farming | `FARM_STAKE/UNSTAKE/CLAIM` | Farming operations |
| Virtual Pools | `VP_BUY/SELL/CLAIM` | Bonding curve operations |
| veToken | `VE_STAKE/UNSTAKE`, `VE_CLM_FEE` | Vote-escrowed staking |
| Flash Loans | `F_BORROW`, `F_REPAY` | Atomic flash loans |
| Orderbook | `PLACE_ORD`, `CANCEL_ORD`, `FILL_ORD` | Limit orders |
| SOL | `WRAP`, `UNWRAP` | SOL <-> wSOL |
| Monitoring | `EMIT`, `EMIT2`, `HALT` | Logging and termination |

## Simulator Limitations

The local simulator fully supports:
- 2-token pool swaps (StableSwap + constant product)
- SPLIT/LEG/MERGE (nested up to depth 4)
- All register and arithmetic operations
- All assertions and conditionals
- ADD_LIQ, ADD_LIQ1, REM_LIQ
- CLOCK, BAL, LP_SUPPLY, POOL_BAL queries
- Flash loans: F_BORROW sets acc to borrowed amount, F_REPAY deducts principal + dynamic fee (base 9bps + 1bps per 1% of pool borrowed, capped at 100bps — matches on-chain formula minus volatility component)
- WRAP/UNWRAP (identity in simulation)

Operations marked **partial** (skipped but cursor advanced):
- Farming (on-chain reward state not available locally)
- Virtual pool bonding curves
- veToken staking
- Orderbook matching
- NPool and Concentrated Liquidity swaps

When any partial op is encountered, the result includes `partial: true` as a warning.

**Flash loan accuracy note:** The on-chain fee adds +10bps for volatile pools (>5% price range in last day). The simulator reads `max_price`/`min_price` from pool candle data when available (via `--pool` flags). Without candle data (synthetic pools), the volatility component is skipped, slightly underestimating fees on volatile pools.

## Reference

- Strategy builder API: `cli/src/shared/strategy.zig`
- Simulator: `cli/src/shared/simulator.zig`
- CLI command: `cli/src/commands/strategy_cmd.zig`
- On-chain VM: `docs/STRATEGY_VM.md`
- Opcode reference: `docs/PROGRAM_REFERENCE.md`
