ADR-034: ERC6909-vault profit capture — production wiring + the batch×capture decline

Status: accepted. Decided during ergo task SMOZG3 (2026-08-18, epic 4GHYBP), on top of NSOFR2 (ADR-033 encode intake).

Context

capture = Erc6909 (ProfitCapture) was modeled end to end — the axis, check_mode = 2 config packing, the V4_MINT_COMPACT opcode, the warm slots, and a spike-oracle proof chain — but it was never production-wired: the declarative harness ran every chain under config=0 (fast path, no check), and the Python cockpit constructed DispatchCandidate without the erc6909_profit kwarg, so production always ran capture = Custody (check_mode = 1). The task: make the V4 ERC6909-vault capture path selectable in production dispatch, simulated end to end, and proven by an oracle assertion on the contract-computed ERC6909 balance.

Oracle findings (verified against the executor source — then

tier3-oracle/src-executor/cmd_executor.vy, now executor/contracts/cmd_executor.vy after SRMMM7 — + runtime harness probes)

  1. Fresh mint suffices — no pre-held ERC6909 position required. V4_MINT_COMPACT (0x58) converts the caller’s positive PM delta into an ERC6909 claim on self (credit-before-debit; the stub PM models the same rule as v4-core’s callback mint). The executor’s own warmup command pre-warms the slot with a 1-wei mint. The harness now pins the zero- position regime explicitly.

  2. check_mode = 2 semantics (the on-chain half of the oracle). The slow path (modes 1 and 2) reads _combined_balance(mode) at start and end of the command loop and asserts after >= before unconditionally (the U3WVLL self-read fix — the expected_value config bits are IGNORED in all modes). Mode 2 reads PM.erc6909BalanceOf(self, uint160(WETH)). Mode 2 is a position floor (no ERC6909 decrease), not a profitability check: on a flash path before = 0 and the assert is trivial; a losing path is still stopped at the protocol layer (flash repayment). Mode 1 (default Custody) is the active WETH+ETH money-loss floor.

  3. use_v4_batch × erc6909_profit on a WETH terminal is unexecutable (new finding). _cmd_v4_batch’s tail settle TAKES any positive WETH/native delta into custody (_v4_settle_currency, positive branch → PM.take) inside the batch command — before the follow-up V4_MINT_COMPACT runs. The mint then finds no delta and reverts with the PoolManager’s D0 (credit-before-debit). Probed at runtime: the combined stream reverted D0 on the real executor bytecode.

  4. Bribe × mode 2 is orthogonal. Bribes compute on the TRUE delta from _combined_balance(mode) and pay from WETH/ETH — mode-independent; no interaction to gate.

Decisions

  1. The declarative harness mirrors production config. run_path_with_* now executes under config_for_options(opts, 0) — the same Q35IJN expression the arbitrage strategy packs — instead of the raw config=0. The 36-family matrix therefore runs under the ACTIVE check_mode=1 assert; the losing-self-fund negative control now proves the on-chain floor reverts (InsufficientProfit), and a sweep-opt-out control (ProfitCapture::SweepToAddress → check_mode=3) preserves the off-chain delta-guard probe.

  2. The oracle half of check_mode=2 is measured, not assumed. ChainResult.erc6909_delta (the PM.balanceOf(executor, weth) delta across execute) + assert_erc6909_capture (executed; delta > 0; 0.1% tolerance per assert_profitable; custody WETH must NOT also carry the profit) pin the magnitude the on-chain floor alone cannot see. Covered: v4_v4 + v4_v4_v4, WETH terminal, starting from a zero ERC6909 position.

  3. The operator knob is wired. DEGENBOT_ERC6909_PROFIT=1 → driver_constants.ERC6909_PROFIT → _dispatch_profitable → DispatchCandidate(erc6909_profit=...) → to_simulate_path → encode_request (ADR-033 intake) → resolve_axes → the Erc6909 axis → check_mode=2 (end-to-end pinned by a degenbot-arbitrage unit test). Stream effect is pure-V4-only (per family_axis_support: only v4_v4/v4_v4_v4 branch the capture axis); every other family keeps custody capture and only the mode-2 ERC6909 floor arms — a safe, documented degradation, not a mis-capture.

  4. The batch×capture combination DECLINES. v4_v4/v4_v4_v4 return None (the ADR-030 funnel decline) for erc6909_profit && use_v4_batch on a WETH terminal, via the shared grammar_shape::erc6909_batch_capture_declines guard, scoped to the plan branches that actually emit the mint (the currency-gap branch is opt-invariant — it never batches nor mints). The batch gas saving and the vault capture are mutually exclusive on the WETH terminal; the operator chooses one. Emitting the combined stream was a latent guaranteed-gas-loss (reverted on submission); declining routes it to the encode-failed fail bucket instead. The decline is interim: correct against the current executor bytecode, but the executor is not deployed — the bot operates it via state-override code injection (INJECT_EXECUTOR_CODE defaults to 1, rust-owned-bot.md §11.4) and its Vyper source is in-repo with a pinned compile pipeline (just verify-tier3-executor-artifact). Making the combination executable is an in-scope contract change — tracked as TGUZCT (settle-skip batch variant + ledger-validated pairing).

  5. Warm slots: already production-wired. WarmupSlots carries the PM ERC6909 WETH balanceOf slot (erc6909_weth) and is consumed by state_override.rs — no change needed.

  6. EXECUTE_CONFIG stays, re-documented. It is the raw config=0 for the offline calldata-dump examples (path26154/path5000), which encode but never execute. The stale “production still uses the hardcoded EXECUTE_CONFIG = ZERO” doc line in config_for_options is corrected — production packs through config_for_options (Q35IJN, now also mirrored by the harness).

Consequences

  • The declarative matrix is stronger: every family now executes with the on-chain profit assert armed, exactly like production; a regression that breaks the assert (e.g. a stream that stops paying its flash) shows up as a matrix revert, not a silent check_mode=0 pass.

  • A new encode decline exists (one cell of the terminal×mode×batch option space); the opt-matrix parity tests pin it.

  • The operator flag is env-gated and off by default — no behavior change for existing runs; enabling it changes pure-V4 paths only.

  • The check_mode=2 floor is a floor, not a profit check (decision 1, finding 2); operators who want a profit floor under capture on self-funded flows should note mode 2 asserts the ERC6909 position, while mode 1 asserts the custody balance. A combined mode would be a contract change — in scope, because the executor is pre-deployment and its source is in-repo (pinned vyper pipeline). Tracked as TGUZCT (settle-skip batch variant + ledger pairing validation); the interim decline stays until it ships.

  • No new crate edges; no pyo3 surface change (the DispatchCandidate kwarg pre-existed — ADR-033 D4 kept it first-class; this ADR wires the driver).

Amendment (2026-08-20 — epic TGUZCT)

Decision 4 and finding 3 were recorded against the injected bytecode of the time (the July contracts/ bake — 15,605 B runtime, pre-U3WVLL/767TN5), which lacked the settle-skip batch variant. The premise that a “currently undeployed” fixed artifact foreclosed the fix was wrong: the executor is operated entirely through state-override code injection (INJECT_EXECUTOR_CODE=1, rust-owned-bot.md §11.4), so “the current executor” is exactly whatever contracts/ bakes — and it lagged the in-repo Vyper source by precisely this feature. TGUZCT has shipped (2026-08-20): the source (and the re-baked artifact, X6OKMV) now carries V4_BATCH_OPEN_WETH (0x43) — V4_BATCH semantics with the final settle skipped, leaving the positive WETH (and, when the native currency is WETH, native) delta open for the follow-up V4_MINT_COMPACT; the U3WVLL profit assert and the 767TN5 sweep still apply.

Corrections this amendment makes, superseding the original text:

  1. Decision 4 — the decline is REVERSED (hard cutover, no parallel flag). grammar_shape::erc6909_batch_capture_declines is deleted; the v4_v4/v4_v4_v4 plans emit V4_BATCH_OPEN_WETH + the trailing V4_MINT_COMPACT under erc6909_profit, and the declarative harness pins the pair executing with the vault delta asserted (check_mode=2 floor armed). The encode-failed decline route no longer exists.

  2. Finding 3 — the combined stream is executable. The runtime harness probe on the new artifact (revm, harness_artifact_probes.rs) pins execution, check_mode=2 pass under erc6909_profit, positive ERC6909 delta, native-balance non-regression; verify-tier3-executor-artifact.sh now re-runs that probe after every fresh compile.

  3. Consequences — “the interim decline stays until it ships” is superseded: the decline is removed, and the opt-matrix parity tests pin the emission instead. The gas/quality trade-off is now a pure operator choice between 0x43 (batch + capture) and the non-batched mint flow, and is no longer a fail-closed exclusion.

The original text is retained as the record of the then-current facts.