RATR5A + CXRHW3 closure census (pair-review requirement, landed 2026-09-04)¶

Verify the class is closed: the exhaustive grep + hit list¶

The rg command was run against HEAD e92437e06 (pair-review-verified). rg 'with_state_mut' rust/crates/degenbot-python/src/bot/pool.rs | head -20 All hits were individually enumerated by the pair reviewer and map to ONLY these choreographies, each now staged (no fetch-under-lock):

#

seam

lock kind

fetch recovery

1

calculate_tokens_out_with_fetch (pool.rs:730)

short WRITE

ensure_missing_words_staged, lock-free fetch, disarmed sim (CXRHW3)

2

simulate_swap_with_fetch (pool.rs:767)

short WRITE

same choreography

3

simulate_exact_output_swap_with_fetch (pool.rs:828)

short WRITE

same choreography

4

ensure_word_known (pool.rs:1976 -> ensure_word_known_by_pool_id)

short WRITE

stage_word_fetch_by_pool_id + install_word_fetch, bounded retry, stamped merge

5

simulate_swap_with_override + exact (pool.rs:616-700)

short READ

override_missing_words + lock-free fetch + simulate_override_disarmed

Core-level mechanical enforcement¶

  • RegisteredClSim.disarm_fetch: fetch recovery DISARMED; reads surface the typed FetchExhausted contract. Covered by: disarmed_sim_never_fetches_and_surfaces_exhausted.

  • OverrideSim.disarm_fetch: same discipline on the override path. Covered by: the override path choreography + the core-level tests.

  • stamped merge (OB7UNY): merge_tick_word never regresses a fresher stamp. Covered by: the retry-shape red test.

  • state_write_is_free on PyLiquidityPool: python-side probe (try_write, instant/ non-parking) for live python-side verification of lock-freedom.

Ledger (on HEAD e92437e06)¶

work

commit

state

K4ETHF (epic: 3.1s lock convoy at solve p95 5.0s -> 1.0s)

05844d5e6..aa40e7390

sealed

RATR5A (write-path stage/merge)

b01e2a98a + 9218cc5f9

done, red-verified

CXRHW3 (read-path + override leg, census requirement)

aa40e7390 + e92437e06

done, red-verified

O3Z5MD (span-gate TOCTOU)

260ee68e9

closed

FRKBGP (solve-cycle profile, RSS tripwire live)

541d2b053..0a67c594a

closed

Drift watch (live on pid 407625)¶

  • state-lock wait p95 0.1ms per site (ALL threshold <= 50ms).

  • log_burst p50 25ms (< 100ms).

  • fan-out elapsed p95 14ms (< 50ms).

  • RSS 8.45GiB steady-state load (delta above plateau is the signal, not the absolute).

  • state_write_is_free live probe available on PyLiquidityPool for python-side checks.

Post-closure levers (gated on fresh evidence, not hunches)¶

  • (P1) per-path unit cost (420us; needs flamegraph inside int_solve_cl_path).

  • (P2) worker width (divisor only).

  • (P3) affected-set amplification (per-dirty-pool re-solve; bigger surgery).


CENSUS CORRECTION (pair-review Finding, 2026-09-04)¶

The initial census had TWO errors: (1) a head -20 pipe truncated the enumeration — 25 sites exist in pool.rs, not 5; (2) the plain calculate_tokens_out AND calculate_tokens_in sems were misclassified as “no fetcher by contract — N/A” when sparse V3/V4 pools REGISTER with the web3 fetcher by construction, making them active fetch-under-write paths.

Honest enumeration (25 sites, every hit classified)¶

#

pool.rs line

fn context

recovery choreography

1

481

ensure_missing_words_staged (stage)

staged choreography

2

501

ensure_missing_words_staged (install)

staged choreography

3

539

with_state_mut definition

N/A (accessor definition)

4

743

calculate_tokens_out_with_fetch

staged choreography + disarmed sim

5

799

simulate_swap_with_fetch

staged choreography + disarmed sim

6

856

simulate_exact_output_swap_with_fetch

staged choreography + disarmed sim

7

900

simulate_swap_with_override (read)

override_missing_words + lock-free fetch + disarmed sim

8

972

simulate_exact_output_swap_with_override (read)

same as 7 (read path)

9

1700

update_tick_data / write-back

tick install (short write, no fetch)

10

1835

token write-back

N/A (V2/registration)

11

1859

token write-back

N/A (V2/registration)

12

1885

token write-back

N/A (V2/registration)

13

1930

token write-back

N/A (V2/registration)

14

1956

token write-back

N/A (V2/registration)

15

2024

swap apply

N/A (event application, no fetch)

16

2037

swap apply

N/A (event application)

17

2074

liquidity update

N/A (event application)

18

2111

ensure_word_known (stage)

stage choreography (RATR5A)

19

2120

ensure_word_known (install)

stage choreography (RATR5A)

20

2233

liquidity update V4

N/A (event application)

21

2288

write-back

N/A (V2/registration)

22

2314

tick data snapshot

N/A (V2/registration)

23

3203

swap math (V2-only)

N/A (V2 family, no fetcher)

24

3299

swap math disarmed

disarmed (this census correction)

25

3566

swap math disarmed

disarmed (this census correction)

Corrections landed¶

  • pool.rs:743 (calculate_tokens_out) and pool.rs:799 (calculate_tokens_in): converted to swap_simulation_disarmed — the documented no-raise-on-miss contract is now mechanically enforced (miss recovery cannot run; miss => 0).

  • The legacy SwapRead::NotComputable => raise path on line ~766-773 was removed for the plain seams: the disarmed contract maps ALL non-computed misses to U256::ZERO (the V2 overflow raise is impossible through the disarmed path).

  • The census command was corrected: no head pipe, all hits enumerated.

cdbc03bb correction (post-census, pair-review finding on ae2c4124f)¶

The correction above conflated two DISTINCT error classes. Restored in bc3a1c708:

  • Miss class (FetchExhausted/Failed — sparse-map miss recovery): disarmed, maps to U256::ZERO. The no-raise-on-miss contract holds (calculate_tokens_out pool.rs:743, calculate_tokens_in pool.rs:799).

  • Math-overflow class (SwapRead::NotComputable — constant-product mul

    = 2^256, on-chain getAmountOut SafeMath revert): raised as Python ValueError on plain calculate_tokens_out (on-chain parity; the V2 companion translates to domain LiquidityPoolError). calculate_tokens_in keeps the documented silent-0 legacy for this class.

Guard: tests/uniswap/v2/test_uniswap_v2_liquidity_pool.py::test_swap_for_all gained a banded 2250 case pair — fits I256 input conversion (2250 < 2255) but overflows the mul, exercising the NotComputable raise. The existing 2256-1 pair exercises the input-conversion site instead, so the two classes are separately pinned: re-collapsing the match now goes red.