Replaying EIGEN Season 1: Bit-Exact Quoter Validation on Real Swaps
EigenLayer's EIGEN token became transferable on 2024-10-01. We pin block 20,900,000, fork mainnet, execute the same swaps Uniswap V3's Quoter quoted, and assert delta == 0. This post is the full walkthrough.
Part of the Incident replays series.
Post 2 — The Credibility Wall used this replay as a claim. This post is the proof: how we picked the block, what the test actually does, why Quoter bit-exact is a stronger semantic than "within 50 bps," and the half-dozen things we deliberately don't test yet.
The incident
EigenLayer's EIGEN token became transferable at the start of Season 1 unlocks on 2024-10-01. The window immediately after that switch is the first observable EIGEN sell-pressure event on Uniswap V3. It's the canonical incident for our incident-replay gate because:
- It's recent enough that the relevant Uniswap V3 pool (EIGEN/WETH 0.30%) has real liquidity at the pinned block.
- It's old enough that the on-chain truth at that block is immutable — no follow-up unlock can perturb the test's pinned state.
- The asset has a clear pre-and-after structure: before October 1, no swaps possible (transferability disabled). After, every swap is a legitimate market action.
The test pins block 20,900,000 (≈ 2024-10-05, four days post-transferability). The pool has had time to attract liquidity and direct sell-pressure flow.
The strategy: Quoter bit-exact
The classical replay strategy is: pick a named historical transaction, fork at the block before it, execute the same calldata against the forked state, compare results. This works but it has known failure modes:
- Most retail flow post-2023 routes through Universal Router rather than the legacy SwapRouter our
find_swap_in_rangescanner walks. For EIGEN specifically, named-tx replay would be a sparse handful of swaps. - The tolerance has to be > 0 because mempool ordering between the real tx and the simulated tx can differ.
Quoter bit-exact dodges both problems by replaying Uniswap's own simulation oracle instead of a specific user's transaction:
- Fork mainnet at the pinned block.
- Static-call Uniswap V3's QuoterV2 for an
amountOut. - Fund a synthetic trader (the bypass-during-snapshot path from Post 2), approve the SwapRouter, execute the same swap.
- Assert
swap_amount_out == quoter_amount_outbytewise.
The Quoter and the SwapRouter share the same pool math — same tick crossing, same fee calculation, same rounding. If our engine produces a different number from the Quoter at the same block, it's a Mayavi bug, full stop. Delta must be exactly zero. No tolerance band.
The test
tests/replay/test_eigen_incident.py:
EIGEN_POST_TRANSFERABILITY_BLOCK = 20_900_000
EIGEN_WETH_FEE = 3000 # 0.30% pool
EIGEN_INCIDENT_CHECKS: tuple[BitExactCheck, ...] = (
BitExactCheck(
name="WETH->EIGEN 0.5 @ 0.30% pool, post-transferability",
block_number=EIGEN_POST_TRANSFERABILITY_BLOCK,
token_in=WETH,
token_out=EIGEN,
fee=EIGEN_WETH_FEE,
amount_in=5 * 10**17, # 0.5 WETH
decimals_in=18,
decimals_out=18,
),
BitExactCheck(
name="WETH->EIGEN 2 @ 0.30% pool, post-transferability",
block_number=EIGEN_POST_TRANSFERABILITY_BLOCK,
token_in=WETH,
token_out=EIGEN,
fee=EIGEN_WETH_FEE,
amount_in=2 * 10**18, # 2 WETH
decimals_in=18,
decimals_out=18,
),
)
@pytest.mark.fork
@pytest.mark.slow
@pytest.mark.parametrize("check", EIGEN_INCIDENT_CHECKS, ids=lambda c: c.name)
def test_eigen_airdrop_replay_within_tolerance(alchemy_rpc_url, check):
result = run_check(check, rpc_url=alchemy_rpc_url)
assert result.error is None, f"check {check.name} errored: {result.error}"
assert result.bit_exact, (
f"NOT bit-exact: {check.name}\n"
f" quoter_amount_out: {result.quoter_amount_out}\n"
f" swap_amount_out: {result.swap_amount_out}\n"
f" delta: {result.delta} ({result.delta_bps:+.4f} bps)"
)run_check lives in mayavi/replay/validator.py. It's a thin coordinator: open a Fork, call QuoterV2's quoteExactInputSingle, deposit the trader's WETH, approve the SwapRouter, execute exactInputSingle, return both numbers + the delta.
The two swap sizes (0.5 WETH and 2 WETH) are deliberately modest — well below the per-tick liquidity ceiling at this block. Bit-exactness is amount-independent (the math is deterministic at any size), but realistic numbers keep the test honest about pool-impact scenarios. A future check at 100 WETH would surface different tick crossings; that's exactly the regime where mocked-AMM simulators silently drift.
What we deliberately don't test
A few things are out of scope today, on purpose:
- A specific user's named-tx replay. Post-2023 most retail flow routes through Universal Router. Our
find_swap_in_rangescanner filters forSwapRouter.exactInputSingle— sparse for EIGEN in the relevant window. A Universal-Router-aware replay scanner is queued for Phase 3. - Sell-side (EIGEN → WETH) swaps. Symmetric to the buy-side; the same Quoter→SwapRouter equivalence holds. Adding two more
BitExactCheckentries is a five-line PR. Not in PR L, deliberately. - Other pool tiers (0.05%, 1.00%). The 0.30% pool is the deepest tier for EIGEN/WETH at this block. The 0.05% tier had insufficient liquidity for meaningful swap sizes. The 1.00% tier wasn't deployed yet for this pair.
- Cross-block consistency. A second-pinned-block check at, say, block 21,000,000 would catch a class of bugs where state mutations between blocks aren't preserved. Not added yet because the fork-cache integrity gate (
tests/evm/test_cache_integrity.py) already covers that for the storage-slot layer.
What a regression here looks like
If a future PR introduces a half-wei rounding error in the swap path — say, a Python truncation in our amountOut decode — this test fires immediately:
NOT bit-exact: WETH->EIGEN 0.5 @ 0.30% pool, post-transferability
quoter_amount_out: 3897582718445789016
swap_amount_out: 3897582718445789015
delta: -1 (-0.0000 bps)
One wei. That's the kind of thing the bps-tolerance world hides. The Quoter bit-exact world finds it. Credibility doesn't compound — every other claim Mayavi makes is suspect-by-association if this test ever silently regresses.
The credibility ratchet
This test is one of seven in the Phase-5 sim-to-real validation suite, all gated as release-blockers:
- Replay validation (this test).
- Fork cache integrity (Post 2).
- PPO vs baselines under interest accrual (Post 8).
- Stablecoin depeg → CDP cascade (Post 7).
- Real-launch vesting-cliff replay (Post 12 — next).
- Determinism (same seed → byte-identical output).
- Gym env contract (
gymnasium.utils.env_checker).
Adding the eighth is the easiest kind of contribution: write a new BitExactCheck tuple, pin a block, parametrize the test. The infrastructure carries.
Next in this series: Post 12 — ENA vesting cliff replay, the ledger-style test that lives one shelf below bit-exact and required a five-month-old validation.md TODO to close.