Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,11 @@ register → cold-start → subscribe → react → simulate.
exact masked reserve write (event-sourced, no refetch).
- **Uniswap V3 family** (V3, PancakeSwap V3, Slipstream) — `QuoterV2`
quotes; slot0 + liquidity + a bounded, fixed-radius **multi-word tick-window
warm-up** at cold-start; `Swap` → slot0/liquidity, `Mint`/`Burn` → tick-range
resync.
warm-up** at cold-start; `Swap` → slot0/liquidity. `Mint`/`Burn` are
**event-sourced**: the exact `liquidityGross`/`liquidityNet` (packed word 0),
`tickBitmap` bit, and in-range global `liquidity` are written directly from the
event for warm (in-window) ticks with no RPC, and only genuinely-cold ticks
fall back to a targeted resync.
- **Balancer V2** — `Vault.queryBatchSwap` quotes; discover→verify cold-start
(`getPoolTokens` read-set); `Swap` → balance-slot resync.
- **Solidly V2** (Aerodrome / Velodrome) — pool `getAmountOut` quotes;
Expand All @@ -91,10 +94,13 @@ register → cold-start → subscribe → react → simulate.
quote entrypoint inside a local revm against the warmed cache, then decodes the
result. There is **no reimplemented AMM math**.

**Reactive synchronization** — fully offline (no RPC in the hot path). Pools
whose events carry absolute state are event-sourced with exact writes (Uniswap
V2 / Solidly `Sync`); pools whose events carry deltas re-verify just the
affected slots (Uniswap V3 tick ranges, Balancer / Curve `VerifySlots`).
**Reactive synchronization** — fully offline (no RPC in the hot path) for the
common case. Pools whose events carry absolute state are event-sourced with exact
writes (Uniswap V2 / Solidly `Sync`); Uniswap V3 `Mint`/`Burn` are event-sourced
too, applying the exact liquidity delta to the warmed tick/bitmap/liquidity slots
and resyncing only ticks outside the warmed window; Balancer / Curve events carry
deltas over a non-predictable layout and re-verify the discovered slots
(`VerifySlots`).

**Testing & CI**

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread"] }
# `plotters` deps — keeps the build lean and, since the crate deliberately ships
# without rayon, avoids reintroducing it even in the dev/bench build.
criterion = { version = "0.5", default-features = false }
# The live V3-liquidity parity test parses `trace_replayTransaction` stateDiff as
# raw JSON (per-tx storage before/after ground truth). Dev-only.
serde_json = "1.0"

# Runnable adapters-path demo: register -> cold-start -> WS event subscribe ->
# reactive apply -> simulate_swap. Env-gated; no-ops if the RPC/WS URL is unset.
Expand Down Expand Up @@ -194,6 +197,13 @@ required-features = ["uniswap-v3"]
name = "v3_full_sync_rpc"
required-features = ["uniswap-v3"]

# Live parity for event-sourced V3 Mint/Burn (env-gated, #[ignore]): for a real
# add- and remove-liquidity tx, apply the event and assert the adapter's writes
# reproduce the on-chain per-tx storage diff (trace_replayTransaction stateDiff).
[[test]]
name = "v3_liquidity_rpc"
required-features = ["uniswap-v3"]

[[test]]
name = "reactive_ws_e2e"
required-features = ["uniswap-v2"]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Each protocol is a single [`AmmAdapter`] implementation; the
| Protocol | Feature | Quote entrypoint | Cold-start | Reactive |
| --- | --- | --- | --- | --- |
| Uniswap V2 | `uniswap-v2` | `Router02.getAmountsOut` | named slots | `Sync` → exact masked write |
| Uniswap V3 family (V3, PancakeSwap V3, Slipstream) | `uniswap-v3` (`pancake-v3`, `slipstream`) | `QuoterV2.quoteExactInputSingle` | slot0 + liquidity + multi-word tick scan (per-pool radius), or the one-shot full-range program sync (`v3_sync`) | `Swap` → slot0/liquidity; `Mint`/`Burn` → tick-range resync |
| Uniswap V3 family (V3, PancakeSwap V3, Slipstream) | `uniswap-v3` (`pancake-v3`, `slipstream`) | `QuoterV2.quoteExactInputSingle` | slot0 + liquidity + multi-word tick scan (per-pool radius), or the one-shot full-range program sync (`v3_sync`) | `Swap` → slot0/liquidity; `Mint`/`Burn` → exact tick + global-liquidity writes where warm, resync only cold ticks |
| Balancer V2 | `balancer-v2` | `Vault.queryBatchSwap` | discover → verify (`getPoolTokens`) | `Swap` → balance-slot resync |
| Solidly V2 (Aerodrome / Velodrome) | `solidly-v2` | pool `getAmountOut` | named slots (config layout) | `Sync` → two exact slot writes |
| **Curve** (StableSwap, StableSwap-NG, CryptoSwap v2, Tricrypto-NG) | `curve` | pool `get_dy` | discover → verify (`get_dy` read-set) | `TokenExchange` + liquidity events → slot resync |
Expand Down
2 changes: 1 addition & 1 deletion docs/protocol-support-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ backend or extra setup is still needed. This complements the summary table in th
| Protocol (feature) | Cold-start | Offline quote after cold-start | Reactive event → state | Factory discovery | Known limitations |
| --- | --- | --- | --- | --- | --- |
| **Uniswap V2** (`uniswap-v2`) | named slots (token0/1, reserves) | ✅ fully offline | `Sync` → **exact** masked write, no RPC | ✅ `getPair[t0][t1]` | — |
| **Uniswap V3** (`uniswap-v3`) | slot0 + liquidity + a bounded multi-word tick window (all four `Tick.Info` words), or the one-shot full-range program | ✅ offline within the warmed tick window; a swap crossing beyond it lazily fetches (or use the one-shot full sync for zero-lazy) | `Swap` → **exact** slot0/liquidity; `Mint`/`Burn` → bounded tick-range **resync** | ✅ fee-keyed `getPool[t0][t1][fee]` | — |
| **Uniswap V3** (`uniswap-v3`) | slot0 + liquidity + a bounded multi-word tick window (all four `Tick.Info` words), or the one-shot full-range program | ✅ offline within the warmed tick window; a swap crossing beyond it lazily fetches (or use the one-shot full sync for zero-lazy) | `Swap` → **exact** slot0/liquidity; `Mint`/`Burn` → **exact** direct writes to warm ticks (packed `liquidityGross`/`liquidityNet`, bitmap flip) + in-range global liquidity, **resync** only for cold (out-of-window) ticks | ✅ fee-keyed `getPool[t0][t1][fee]` | — |
| **PancakeSwap V3** (`pancake-v3`) | as Uniswap V3 (Pancake slot layout) | ✅ as Uniswap V3 | as Uniswap V3 (Pancake `Swap` topic) | ✅ fee-keyed | one-shot full sync uses the layout-only `core` spec (Pancake's fee-growth/observation slots are unverified) |
| **Slipstream / Aerodrome CL** (`slipstream`) | as Uniswap V3 (Slipstream slot layout) | ⚠️ **discovery + cold-start only** | as Uniswap V3 | ✅ tickSpacing-keyed `getPool[t0][t1][spacing]` | discovered `fee` is left unset (its quoter takes a different ABI); `simulate_swap` returns `MissingMetadata` unless the caller supplies a compatible quoter + fee |
| **Balancer V2** (`balancer-v2`) | discover→verify (`getPoolTokens` read-set) | ✅ (the vault's code is lazily fetched on the first quote) | `Swap` → balance-slot **resync** | ❌ not shipped (no on-chain token→pool index; needs an async log scan) | register pools explicitly |
Expand Down
Loading
Loading