Skip to content

feat: native Curve StableSwap 2-token pool simulation#557

Open
sunce86 wants to merge 1 commit intopropeller-heads:mainfrom
sunce86:feat/native-curve-stableswap
Open

feat: native Curve StableSwap 2-token pool simulation#557
sunce86 wants to merge 1 commit intopropeller-heads:mainfrom
sunce86:feat/native-curve-stableswap

Conversation

@sunce86
Copy link
Copy Markdown

@sunce86 sunce86 commented Mar 18, 2026

Summary

Native Rust implementation of the Curve StableSwap invariant for 2-token plain pools (e.g., DAI/USDC, FRAX/USDC, USDS/USDC). This replaces the VM/revm fallback for these pool types, providing ~100x faster swap simulation.

Motivation

StableSwap pools are among the highest-volume DeFi pools. The current VM fallback executes Curve bytecode in revm at ~100-200μs per simulation. A native implementation runs at ~900ns (benchmarked in a separate project), enabling significantly better routing performance when evaluating thousands of pool candidates.

Implementation

Math (math.rs):

  • get_d() — Newton-Raphson solver for StableSwap invariant D
  • get_y() — Newton-Raphson solver for output balance given input and D
  • spot_price_raw() — Analytical marginal price from invariant derivative (no simulation needed)
  • All arithmetic ported line-by-line from Curve's Vyper source, matching multiplication order for bit-for-bit identical results

State (state.rs):

  • ProtocolSim implementation with get_amount_out, spot_price, get_limits, delta_transition
  • Reserves stored in raw (native) token decimals; normalized to 18 decimals internally via rates (matching Curve's RATES mechanism)
  • Handles mixed-decimal pairs (e.g., DAI 18-dec / USDC 6-dec)

Decoder (decoder.rs):

  • Decodes reserve0, reserve1, A, fee from indexer attributes
  • Extracts token decimals from all_tokens for rate computation
  • Converts raw A() to internal precision (A * A_PRECISION)

Verification

On-chain verification against FRAX/USDC pool (0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2) on Ethereum mainnet:

  • 6 swap amounts tested (both directions: FRAX→USDC and USDC→FRAX)
  • All assert_eq!bit-for-bit identical to on-chain get_dy(), zero difference

30 unit tests covering:

  • Math convergence (balanced, imbalanced, high/low A, symmetry)
  • Spot price (balanced pool, imbalanced, inverse property)
  • Swap amounts (same decimals, mixed decimals, both directions, imbalanced)
  • Delta transitions (reserves, amplification, missing attributes)
  • Limits (both directions, empty pool, max_out ≤ reserve)
  • Roundtrip consistency, state immutability

Usage

.exchange::<CurveStableSwapState>("curve_stableswap", filter, None)

Scope & Limitations

  • 2-token plain pools only — MetaPools and Tricrypto are separate invariants
  • Requires parsed attributes from indexer (reserve0, reserve1, A, fee). The current Curve substreams module may send raw storage slots for the VM adapter instead. An indexer update to emit parsed attributes would be needed.
  • query_pool_swap not yet implemented (returns error, consistent with V4/VM)

Follow-up

  • MetaPool support (nested pool resolution)
  • N-token plain pool generalization
  • query_pool_swap implementation for target-price solving

@github-project-automation github-project-automation bot moved this to Todo in Tycho Mar 18, 2026
@sunce86 sunce86 force-pushed the feat/native-curve-stableswap branch 4 times, most recently from 4c7d453 to 0a50a6d Compare March 18, 2026 15:32
Add a native Rust implementation of the Curve StableSwap invariant for
2-token plain pools (e.g., DAI/USDC, USDS/USDC). This provides ~100x
faster swap simulation compared to the VM/revm fallback.

Implementation:
- Newton-Raphson solvers (get_d, get_y) ported from Curve's Vyper source
- ProtocolSim trait with get_amount_out, spot_price, get_limits, delta_transition
- Decoder for Tycho indexer snapshots (reserve0, reserve1, A, fee attributes)
- 19 unit tests covering math convergence, swaps, roundtrips, symmetry,
  delta transitions, decoding, immutability, and split-vs-single behavior

Usage:
  .exchange::<CurveStableSwapState>("curve_stableswap", filter, None)

Scope: 2-token plain pools only. MetaPools and Tricrypto can follow in
separate PRs.
@sunce86 sunce86 force-pushed the feat/native-curve-stableswap branch from 0a50a6d to 6222013 Compare March 18, 2026 15:37
@sunce86
Copy link
Copy Markdown
Author

sunce86 commented Mar 29, 2026

Hey team,

I want to flag that this PR is premature and incomplete as-is. Since opening it, I've been working on curve-math, a standalone Rust crate that implements the complete math for all Curve pool types (StableSwap, CryptoSwap, MetaPools, Tricrypto, etc.), not just 2-token plain pools. It's currently in alpha while I validate against on-chain results across all pool types and chains.

The right approach would be to use curve-math as the foundation and implement the full ProtocolSim trait on top of it, rather than shipping partial coverage that only handles one pool type.

I also have a native Curve Tycho substream adapter prepared that is compatible with curve-math and could be contributed alongside it.

My question to the team: would you be interested in me contributing this once curve-math moves to beta? I'd like to gauge interest before investing time in the integration work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant