Executor facts-driven Plan walker — spike spec (v3_v4_v3)¶
ADR-031 · epic
62V6Q5. Status: superseded by A3 — the spike’s prototype was generalized (A2) and made the sole producer (A3): the 30build_*_planbodies and thebuild_for/AxisSupportrows are deleted, andfamily_axis_supportis facts-derived. The spike spec below records the A1 proof; see ADR-031 for the accepted end state.Further supersession (arch-review epic
PZBGP7): the end state has since deepened — the 3-hop bodies are topology rule walkers (not prot-keyed arms), and the facts schema gained three position-scoped axes (terminal_form,repay_mechanism,seed_delivery). The PZBGP7 ledger (docs/plans/pzbgp7-walker-decomposition.md) and the T6 rule analysis (docs/spikes/t6-topology-rules-analysis.md) were removed in the stale-docs cleanup71ec78b2; the landed design is recorded in ADR-031’s Resolution paragraph, theCONTEXT.mdwalker glossary, and the rule walkers themselves (with the R1–R3 rules stated on each walker fn) inrust/crates/foundation/degenbot-executor/src/grammar_walker/shapes/three_hop.rs. The frozen A1 schema below is historical.
Why this spike¶
ADR-031 deepens the executor by replacing the 30 hand-written per-family build_*_plan producers (a ~7,000-line adversarial surface) with per-protocol hop facts (data) + per-protocol mechanics (code) + one enclosure-deriving walker. The single risk was whether the facts schema could express the worst enclosure — v3_v4_v3’s 3-level V3c → V3a → V4_UNLOCK nesting. This spike proves that on that family, byte-identical to the hand-authored producer, before generalizing (A2).
What was built (behind --features walk)¶
rust/crates/foundation/degenbot-executor/src/grammar_walker.rs:
HopFacts— the per-protocol declarative data half (ADR-031 D4). Fields:prot,zfo(direction),swap_fee,tick_spacing,out_currency,in_currency,out_dest(Executor / PoolManager / take-to-pool-repay),repay(SelfRefund/Offstream/NetZero).mod mechanics— the per-protocol code half:v3_flashbuilds a V3FlashSwapfrom facts (out_destpicks the recipient routing). A2 adds the V4/V2 mechanics.build_v3v4v3_walk— the walker. Reads the three hops’ facts + solver amounts, derives the enclosure from theRepay/OutDestfacts, and emits a singlePlan(the one representation:plan_to_bytes+LedgerValidatorare untouched pure functions of it).derive_v3v4v3_walk— the tri-state public entry (ADR-030): build →LedgerValidatorgate → bytes.
The enclosure-derivation rule (the novel core)¶
For v3_v4_v3 (leading V3, V4 middle, terminal V3), the walker derives the nesting from the facts rather than hardcoding indices:
SelfFundfunding — entry capital (a funding axis value).V4Sync— the leading V3’sout_dest = PoolManagerseeds the PM ledger the V4 unlock reads.The
Offstreamhop (terminal V3c) is the outermostFlashSwap(repaid by a downstream take).The
SelfRefundhop (leading V3a) is inner; its callback runs the WETH self-refundErc20Transferthen theV4Unlock.The
NetZeroV4 (b) lives inside the unlock:V4Settle(the synced forward) →V4Swap→V4TakeCompacttoc(repayingc’s borrow) →V4SettleAll(PM-net-zero).
This is D3’s “enclosure is derived, not chosen”: the author states facts (where each hop’s output goes, how it’s repaid); the walker states the nesting.
Evidence (the spike’s gate)¶
Byte-identity:
derive_v3v4v3_walk(...)’s bytes equalbuild_v3v4v3_plan’s bytes on representative amount sets (walker_matches_hand_authored_v3v4v3). Byte-identity ⇒ execution-identity: both shareplan_to_bytes, and the revm matrix already runsbuild_v3v4v3_planGREEN.Validator gate: the walked Plan passes
LedgerValidator::validate_full.Default unchanged:
cargo test -p degenbot-executor(nowalk) — 109 lib + all integration suites green;grammar_walkeris#[cfg(feature = "walk")], absent from default builds.Lint:
cargo clippy -p degenbot-executor --features walk --all-targetsclean.
The facts schema (frozen for A2)¶
struct HopFacts {
prot: Prot, // V2 | V3 | V4 | ...
zfo: bool, // swap direction
swap_fee: u16,
tick_spacing: i16, // V4 only
out_currency: Address,
in_currency: Address,
out_dest: OutDest, // Executor | PoolManager | Repay(pool)
repay: Repay, // SelfRefund | Offstream | NetZero
}
A2 generalizes by: (1) adding the V2/V4 mechanics modules (the swap/unlock step constructors), (2) extending OutDest/Repay to any new coupling a family exercises (native bridging, V2 pre-fund, capture terminals), (3) widening the walker to walk an arbitrary hop sequence reading each hop’s facts, and (4) proving the full 25-family revm contract-matrix (not byte-parity against the suspect builders) before hard cutover (A3).
Non-goals (checked)¶
No other family, no default-build output change, no DEX rollout. The two real latent defects B surfaced (the losing InPathFlash stream; v4_v4_v4 no-slack configs) are the motivation: A2/A3 make them unrepresentable at the schema level.