Skip to content

Latest commit

 

History

History
115 lines (105 loc) · 6.99 KB

File metadata and controls

115 lines (105 loc) · 6.99 KB

ADR-033: The encode funnel's intake contract — one encode request per path, one context per session

Status: accepted. Decision recorded 2026-08-17 (architecture review, encode-seam candidate). Implementation: ergo task NSOFR2 under epic 4GHYBP (follow-up SMOZG3 for the ERC6909 capture it keeps first-class).

Context

The composer funnel encode_cmd_stream (degenbot-executor) was the single public entry the whole repo encodes through, and its interface carried the grammar's internal vocabulary at the seam:

  • 8 positional args — path infos, three parallel amount arrays (optimal_input / hop_outputs / consumed_inputs), three session-scoped deployment addresses, and an options bundle.
  • A solver-internal invariant re-asked of every callerconsumed_inputs[i] must be the executable input to hop i; for an over-fed CL hop the solver's clamp sets it to input_consumed − 1 so the on-chain exact-in loop terminates at amountRemaining == 0 (UO3JM4 / path-5000 EMPTY-HALT). The invariant is documented in ≥4 files, yet the declarative harness — the one tool meant to exercise encode behaviour on the real cmd_executor — synthesizes consumed_inputs as once(optimal_input).chain(hop_outputs), so the clamp path was never tested at runtime.
  • A legacy precedence rule at the seamerc6909_profit: bool vs the capture axis, collapsed by resolve_axes.
  • Facade rot — a 43-line one-statement grammar.rs pass-through; a crate header still describing the crate as a warmup-slot math leaf; a doc/code split on check_mode (doc: 0 for non-ERC-6909; code emits 1, plus an undocumented SweepToAddress → 3); an ignored _pool_manager parameter; dead fits_int128 tombstones.

Decision

  1. EncodeRequest (new, degenbot-executor::composers) is the funnel's intake: it owns the PathInfo plus the amount triple plus the axis options, checked at construction (array-length alignment; a mismatch panics loudly naming the array — a programmer error, not a Result). A request without its path is the shape that let amounts be synthesized blind to what the path constrains; path-and-amounts are one unit.
  2. EncodeContext (new, same module) carries the session-scoped deployment addresses (executor / PoolManager / WETH); one per session, never per-path.
  3. The funnel becomes encode_cmd_stream(&EncodeContext, &EncodeRequest) -> Option<Vec<u8>> — name and decline-None semantics unchanged (ADR-030's public-seam shape stands), 8-arg form deleted in the same commit. Hard cutover: every in-tree caller migrates in one pass; there is no parallel implementation (AGENTS.md: no back-compat layer; byte-identity is already gated by the golden corpus + revm matrix).
  4. erc6909_profit stays a first-class option. It is the operator's toggle for Uniswap-V4 ERC6909-vault profit capture — modeled end to end (axis, check_mode = 2 on-chain assert, warm slots) but not yet production-wired. It is a live feature knob, not dead weight; wiring it correctly is follow-up task SMOZG3, not this ADR's scope.
  5. The sweep deletes the pass-through, fixes the facade and the check_mode doc to the code's reality, drops the ignored parameter (and its _ffi.executor stub ripple), and removes the dead tombstones.
  6. The harness earns the depth: a new declarative entry (run_chain_with_consumed) accepting caller-supplied per-hop amounts — the shape the production solver commits after clamp_cl_hop_capacity re-aligns hop_outputs[i]/consumed_inputs[i+1] (path-73385) — so the production encode_cmd_stream intake is first exercised with non-synthesized amounts against the real cmd_executor, with a clamped aligned-shape fixture. The over-fed CL pair (clamped profitable / unclamped EMPTY-HALT) is parked with a note: the stub PoolV3 is closed-form (single active range, no tick bitmap), so its capacity bound sits at the extreme MIN/MAX price limits — u128-amount-infeasible for any loopback path at those prices. The profitable clamped-execution / EMPTY-HALT verdict proof lives in the real-PoolManager tier-3 regression (tier3_path5000_v4_clamp.rs); the pure clamp rule (V3SwapOutcome::exact_input_clamp_bound) is unit-green in degenbot-pools.
  7. Internal encoders stay (encode_cmd_3_hop's 8 pinned call sites included) — the deepening is the interface move; the walker/encoder internals are the grammar-walker continuation's territory.

Considered options

  • Path stays a funnel argument ((ctx, &PathInfo, &EncodeRequest)): rejected — the request without its path is exactly the drift shape; the funnel's whole interface should be “session context + per-path request.”
  • Addresses folded into the request: rejected — session scope re-stated per path, and it would make the harness (which has no deployment addresses) build fakes.
  • Kill the legacy erc6909_profit bool at the seam (collapse into the capture axis, deprecate the Python kwarg): rejected on operator decision — the bool is the user-facing toggle for V4 ERC6909-vault capture, which is an intended feature awaiting correct implementation, not a legacy relic.
  • Additive cutover (new entry alongside the 8-arg funnel, later deletion): rejected — no external surface depends on the arg shape (the encode funnel is not exposed to Python at all; _ffi exposes only warmup-slot/config helpers), and the byte-gates make a hard cutover low-risk.
  • Result at the value level for length misalignment: rejected — a funnel argument-shape error is a programmer error; the fixture-driver convention in this repo is a loud, named failure, and Option at the seam stays ADR-030's decline semantics.

Consequences

  • The interface a caller (or test) must learn is two small values; the amount-triple invariants become a property of one constructor instead of prose repeated at every crossing.
  • The clamp invariant's runtime home is split by pool fidelity: the declarative matrix exercises the intake with the clamp's re-aligned amount shape (clamped commit + aligned hop_outputs[0], executed against the real cmd_executor), while the real-PoolManager tier-3 regression owns the profitable clamped-execution / EMPTY-HALT verdict (Decision 6, parked rationale).
  • No new crate edge was added: the harness consumes the engine's already-committed amounts (the seam's contract), so the pure clamp rule stays in degenbot-pools and the application policy (margin, which hops, forward-alignment) stays in the engine's clamp_cl_hop_capacity.
  • The degenbot-execution seam's decoupled amount bundle (ADR-025 D5) is deliberately untouched; unifying it, if the default adapter deepening proceeds, is a separate decision.
  • Semver: the public funnel signature changes within 0.6.x — acceptable per the repo's migration posture; standalone Rust consumers of encode_cmd_stream (examples, standalone_consumer.rs) migrate in-tree.