V2-as-degenerate-CL swap-step parity probe¶
Probe harness: rust/crates/engine/degenbot-solvers/examples/v2_cl_parity_probe.rs
Run: cargo run -p degenbot-solvers --example v2_cl_parity_probe
This probe answers the decision question in ADR-060 (“Collapse the V2 walk dispatch — representation vs interface”): can a V2 constant-product hop be represented as a degenerate single-range CL tick sequence without changing the per-step arithmetic? It measures the swap-step level, which is the site that flips the ADR decision; the full-path returned-tuple bar is a downstream concern.
The probe runs the production code paths unchanged: the baseline is
degenbot_math::v2::IntHopState::swap, the candidate is
degenbot_solvers::cl::simulate_v3_range_swap over the projected range. No
special rounding, no configuration switch.
Mapping construction¶
For a V2 hop (R_in, R_out, gamma_numer, fee_denom) and a chosen direction:
Liquidity:
L = floor(sqrt(R_in * R_out))— the integer geometric mean. The real geometric mean is irrational for any non-squareR_in * R_out, so this floor is lossy by construction and is the first expected parity break.Spot price:
sqrt_price_x96 = floor(L * 2^96 / R_in), placing the projected spot at the reserve ratio. Also generally non-integral.Range span: one range with empty interior word boundaries, so the CL step degenerates to a single
compute_swap_step_v3to the exit boundary. The span is widened by2^32on the swap side ([sqrtP >> 32, sqrtP]for zero-for-one,[sqrtP, sqrtP << 32]for one-for-zero). Every tested input is orders of magnitude below the resulting capacity, so the step stays in the target-unreachable (open-AMM) branch — the only branch with a V2 analogue.Fee: the V2 fraction is mapped onto the identical CL fraction,
fee_pips = 1e6 * (fee_denom - gamma_numer) / fee_denom, givinggamma_CL = 1e6 - fee_pipsoverfee_denom_CL = 1e6. Exact for 997/1000, 9975/10000, 9995/10000 and 99/100.Direction: both projections are measured. Zero-for-one projects
R_in -> token0, one-for-zero projectsR_out -> token0. The two CL rounding paths differ (zero-for-one floorsamount1; one-for-zero floorsamount0), so a representation must clear both.
The candidate’s net input is floor(x * gamma/fee_denom) (the CL
amount_remaining_less_fee); the baseline keeps the fee inside the rational
floor(x * gamma * R_out / (fee_denom * R_in + x * gamma)).
Corpus inventory¶
Source |
V2 hop states |
Notes |
|---|---|---|
|
27 unique (369 occurrences) |
real mixed V2+CL solver captures |
|
1 |
real UniV2 hop with recorded input |
|
1 |
real UniV2 hop, recorded 58,233,015 input |
|
1 |
real SushiV2 hop, recorded 9,085,365 input |
real total |
30 |
fee tiers 0.30% and 0.25% |
synthetic grid |
36 |
fees {0.30%, 1%, 0.05%} × reserve ratios {~1x, 10x, 1000x} × magnitudes {1e18, 1e21, 1e24, 1e27} |
Each state is swept over an 8-point log-spaced input grid up to the order of
magnitude of R_in, plus the 3 recorded hotspot inputs, in both directions:
1236 comparisons.
Coverage gaps (methodology limitations):
No boundary-exact cases. The range is deliberately widened past every tested input to keep the step target-unreachable; a synthetic range-boundary hit is a projection artifact, not a V2 behaviour.
range_boundary_hits = 0.No input beyond ~
R_inand no multi-hop composition; the probe is a per-step comparison by scope.Real captures cover only 0.30%/0.25% fee tiers and two ratio regimes (~2.5e-9 and ~2e2..5e2). The 1% and 0.05% tiers are synthetic only.
Per-hop input amounts are not captured in the mixed JSONL, so the input grid plus the recorded single-path hotspots stand in for solved amounts.
Divergence distribution (1236 comparisons)¶
Bucket |
n |
divergent |
% |
negative |
positive |
min delta |
median (non-zero) |
max delta |
|---|---|---|---|---|---|---|---|---|
overall |
1236 |
225 |
18.20% |
192 |
33 |
-530,594,942 |
-11 |
+7,255 |
real |
660 |
66 |
10.00% |
58 |
8 |
-530,594,942 |
-211 |
+7,255 |
synthetic |
576 |
159 |
27.60% |
134 |
25 |
-999 |
-9 |
+148 |
zero-for-one |
618 |
103 |
16.67% |
103 |
0 |
-530,594,942 |
-14 |
-1 |
one-for-zero |
618 |
122 |
19.74% |
89 |
33 |
-530,594,942 |
-9 |
+7,255 |
ratio >=100x |
110 |
66 |
60.00% |
58 |
8 |
-530,594,942 |
-211 |
+7,255 |
ratio 1000x |
192 |
85 |
44.27% |
72 |
13 |
-999 |
-208 |
+148 |
ratio 10x |
192 |
64 |
33.33% |
52 |
12 |
-10 |
-5 |
+2 |
ratio ~1x |
192 |
10 |
5.21% |
10 |
0 |
-1 |
-1 |
-1 |
ratio <1e-3 |
550 |
0 |
0.00% |
0 |
0 |
0 |
0 |
0 |
0.30% (synthetic) |
192 |
48 |
25.00% |
40 |
8 |
-996 |
-9 |
+138 |
1.00% (synthetic) |
192 |
46 |
23.96% |
38 |
8 |
-989 |
-9 |
+148 |
0.05% (synthetic) |
192 |
65 |
33.85% |
56 |
9 |
-999 |
-9 |
+134 |
997/1000 (real) |
594 |
66 |
11.11% |
58 |
8 |
-530,594,942 |
-211 |
+7,255 |
9975/10000 (real) |
66 |
0 |
0.00% |
0 |
0 |
0 |
0 |
0 |
consumed_input matched on every comparison (consumed_mismatch = 0): with the
range never reached, both simulators consume the full x. Divergence is
therefore entirely in output. It is not one-sided overall: zero-for-one is
strictly non-positive, one-for-zero can overshoot.
Representative real divergences (all consumed_v2 == consumed_cl):
x=1 R_in=7456337076646 R_out=3886685670626625517819 V2=519695605 CL=0
x=1 R_in=272235829501 R_out=144881599035718159565 V2=530594942 CL=0
x=9085365 R_in=1835250620298287096280 R_out=4569594589161887004337777
V2=22553805448 CL=22553803195 delta=-2253
x=58233015 R_in=7456337076646 R_out=3886685670626625517819
V2=144559529641 CL=144559527263 delta=-2378
Structural reasons for the divergence¶
Post-fee net input is floored before the invariant. This is the dominant and decisive break. V2’s
getAmountOutkeeps the fee as an exact rational:out = floor(x·gamma·R_out / (fee_denom·R_in + x·gamma)). The CL exact-in step first computesamount_remaining_less_fee = floor(x·gamma/fee_denom)(amuldiv), then derives the price move from that floored quantity. For a non-unitgamma, small inputs floor to zero: atx = 1and 0.3% the CL step seesfloor(0.997) = 0and returns zero output while V2 returns hundreds of millions of wei. This produces the-530,594,942real-fixture miss and the zero-for-one one-sidedness.Geometric-mean liquidity floor.
L = floor(sqrt(R_in·R_out))equals the real geometric mean only for perfect squares; the projected constant-product invariant therefore differs by up to one unit ofL. The output effect scales withR_outand with the reserve ratio.Spot-price floor.
floor(L·2^96/R_in)sits off the exact reserve ratio by up to one Q96 unit, shifting the projected price.Per-step rounding shape. Even with exact
Land sqrt price, the CL step roundssqrt_price_nextup and floors the output delta in two stages, while V2 performs a single terminalDIV. That asymmetry is why the zero-for-one arm is one-sided (candidate ≤ baseline) but the one-for-zero arm can exceed the baseline (theamount1input path rounds differently), yielding the +7,255 maximum overshoot.
The states that matched everywhere (ratio < 1e-3, including the three real
0.25% pools and the deep-ratio 0.3% pools) do so because their outputs are small
relative to the reserve magnitude: the L, sqrt-price and net-input floors all
fall below the 1-wei output quantum at the tested inputs. The projection is
arithmetically identical there by magnitude, not by construction. Divergence
concentrates where R_out >= 100·R_in (60% of comparisons).
Verdict¶
B — keep the interface collapse; the representation collapse fails the zero-tolerance bar.
ADR-060 clears option A only if every compared step is byte-identical. The probe
found 225 of 1236 comparisons (18.2%) with a non-zero output delta, including a
real captured hop whose x = 1 output differs by 530,594,942 wei and a real
x = 58,233,015 comparison differing by 2,378 wei. No wei tolerance applies:
the structural cause — flooring the post-fee input before the invariant, plus
the geometric-mean and spot-price floors — is inherent to the CL step, not an
accumulation a probe could erase. A representation swap would therefore change
landed range geometry and any candidate sitting on a crossing_gross_input
boundary, which is an observable result change dressed as a refactor.
Collapsing the V2 walk dispatch at the piece-view seam (interface collapse) removes the enum match sites without touching either production simulator and remains byte-identical by construction. This probe is the evidence record for that choice; it does not modify any production source.