Skip to content

Read the paired FinalizeSettle with its own parser in BeginSettle - #123

Open
mayanksharma-eth wants to merge 1 commit into
cowprotocol:mainfrom
mayanksharma-eth:begin-settle-reuses-finalize-parser
Open

Read the paired FinalizeSettle with its own parser in BeginSettle#123
mayanksharma-eth wants to merge 1 commit into
cowprotocol:mainfrom
mayanksharma-eth:begin-settle-reuses-finalize-parser

Conversation

@mayanksharma-eth

@mayanksharma-eth mayanksharma-eth commented Aug 26, 2026

Copy link
Copy Markdown

The problem

FinalizeSettle's account layout — "three fixed accounts, then [source_buffer, destination] per push" — was stated in three unlinked places:

  1. A meta literal in the builder (From<FinalizeSettle> for Instruction)
  2. A slice pattern in FinalizeSettleInput::parse_body
  3. Index arithmetic in the program, in push_destinations:
(FINALIZE_FIXED_ACCOUNTS + 1..instruction.num_account_metas())
    .step_by(2)

Change the fixed accounts on one side and the others still compile. Missing the third copy is the dangerous one and it fails silently: BeginSettle would start reading source buffers where it expects destinations, so the check that each order is paid to its own buy_token_account would compare the wrong account.

This is the change anyone adding an account to the settle instructions has to get right — a solver signer for solver authentication, most obviously.

The change

BeginSettle now reads its counterpart through FinalizeSettleInput::parse, the same parser the finalize applies to itself. The parser is already generic over the account type; the only reason the program couldn't use it is that introspection hands out metas one index at a time rather than as a slice, so introspected_accounts collects the addresses and bridges that gap.

Deleted: push_destinations, finalize_pushes, and finalize_push_amounts (its only non-test caller was finalize_pushes).

Both settle builders now emit their fixed metas from a fixed_accounts helper returning [AccountMeta; N], so the count is load-bearing — adding a fixed account without updating N is a compile error rather than something review has to catch. FINALIZE_FIXED_ACCOUNTS is crate-private now; nothing outside the interface needs it.

Behaviour change

BeginSettle now rejects a finalize whose accounts and data disagree, before pulling any funds, instead of leaving it to the finalize.

The transaction failed either way, so the on-chain outcome is unchanged — but the failing instruction index moves from 1 to 0. Three tests asserted on that index, and their comments described the old blind spot explicitly:

This is the account/data disagreement BeginSettle structurally can't see.

It can see it now. Those tests and comments are updated; no test was weakened to accommodate the change.

Cost

3–4% more compute on the settle path, for the collected address slice:

Benchmark Before After Δ
settles_a_single_order 12,285 12,794 +509
settles_multiple_orders 22,679 23,549 +870
pushes_a_single_order 12,267 12,776 +509
finalizes_with_no_pushes 7,043 7,264 +221

Worst case +870 CU on a 23.5k CU settlement. bench-report.json is regenerated.

If that trade isn't wanted, the cheaper fallback keeps most of the benefit: leave the two read paths separate but derive both from the single fixed_accounts array, so the count still can't drift even though the offsets stay duplicated. Happy to switch if you'd prefer that.

Testing

  • just test — 298 tests, all passing
  • just lint (clippy --deny=warnings), just fmt-check, just doc-dev — clean
  • just bench — regenerated, drift check passes

The property test guarding the two routes into the parser is kept and retargeted: introspected_parse_matches_account_parse now asserts that parsing the introspected metas and parsing the runtime AccountViews recover the builder's pushes identically. On the interface side, parser_recovers_builder_pushes replaces the amounts-only cross-check with a full round-trip over all four push fields (source buffer, destination, bump, amount), since the metas and the instruction data are written by separate paths in the builder.


Context: this came out of reading the settle path while scoping solver authentication, which DESIGN.md specifies but the program doesn't implement yet — BeginSettle/FinalizeSettle currently take no signer. That's a separate PR; this one is just the layout dedup that it depends on. Happy to open an issue for the rest of what I noticed if that's useful.

BeginSettle re-derived the finalize's account layout by hand, walking the
introspected metas from `FINALIZE_FIXED_ACCOUNTS + 1` in steps of two, while
the builder wrote that same layout as a meta literal and the parser read it
back as a slice pattern. Three statements of one fact, none of them linked,
so a fixed account added or removed on one side had to be caught by review.
Missing the introspection copy is silent: BeginSettle would start reading
source buffers where it expects destinations, and validate each order's push
against the wrong account.

Collect the introspected account addresses into a slice and hand them to
`FinalizeSettleInput::parse`, the parser the finalize already applies to
itself. `push_destinations`, `finalize_pushes` and the now-unused
`finalize_push_amounts` go away with it.

Tie the remaining counts to the wire format rather than restating it: both
settle builders emit their fixed metas from a `fixed_accounts` helper
returning `[AccountMeta; N]`, so a fixed account added without updating N is
now a compile error instead of a review item.

This closes a blind spot three tests documented: BeginSettle now rejects a
finalize whose accounts and data disagree, before pulling any funds, rather
than leaving it to the finalize. The transaction failed either way, so the
on-chain outcome is unchanged; the failing instruction index moves from 1 to
0, which those tests assert on.

Costs 3-4% more compute on the settle path (at most +870 CU on a 23.5k CU
settlement) for the collected address slice; bench-report.json updated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mayanksharma-eth
mayanksharma-eth requested a review from a team as a code owner August 26, 2026 10:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant