Skip to content

feat: DelegatedMaker — one-transaction, no-signature, no-escrow maker flow - #432

Merged
deacix merged 1 commit into
cursor/fusion-announcement-anchored-auction-9252from
cursor/delegated-maker-9252
Aug 5, 2026
Merged

feat: DelegatedMaker — one-transaction, no-signature, no-escrow maker flow#432
deacix merged 1 commit into
cursor/fusion-announcement-anchored-auction-9252from
cursor/delegated-maker-9252

Conversation

@deacix

@deacix deacix commented Aug 4, 2026

Copy link
Copy Markdown
Member

Stacked on #430 — the announcement-anchored auction and the maker-only, signature-free OrderRegistrator this contract builds on. The base branch is cursor/fusion-announcement-anchored-auction-9252; GitHub retargets this PR to master when #430 merges, and since the repo merges with merge commits the diff collapses to the DelegatedMaker changes with no rebase.

Split out of #430 deliberately: this is the one custody-adjacent surface of the feature set. A contract that concentrates standing allowances reviews differently from pure pricing logic, so the auction core and this helper each get a review pass matched to their risk. Nothing in #430 references this contract; it composes from the outside like any other maker.

What it is

DelegatedMaker gives ERC-20 makers the flow the feature set was built toward — one transaction per order, no signature, no escrow. One shared contract is the maker for all of its users (CoW Protocol's presign plus its VaultRelayer just-in-time pull, as prior art): createOrder records the caller as the order's owner (the presign read by isValidSignature), validates that the order names this contract as maker, the remaining invalidator (the bit-invalidator nonce space would be shared across users) and a pre-interaction routed to this contract, then announces — anchoring the auction clock at the create block. Funds stay in the owner's wallet until the fill: the pre-interaction pulls exactly the filled amount through the owner's standing allowance immediately before the maker-asset transfer, topping up the protocol's own allowance for the maker asset whenever it runs short (this lazy top-up replaced a one-shot approveRouter step during #430's pruning rounds, fixing the latent depletion defect for tokens that decrement even infinite allowances), so custody time is zero and the contract holds no balances between transactions. createOrderWithPermit folds the owner's EIP-2612 allowance into the create transaction itself — owner pinned to the caller, failures swallowed the way the protocol treats maker permits, so a front-run permit changes nothing and a wrong one leaves an order that a later approval revives. cancelOrder deletes the presign and invalidates at the protocol as the maker — both load-bearing, since the core validates ERC-1271 only on the first fill (OrderMixin._fillContractOrder) — and the duplicate check reads the registrator's ever-registered state, so a cancelled order cannot be re-created into a presigned-but-protocol-dead shell. The contract concentrates standing allowances (the same trust shape as the router itself) and has no owner powers over user funds.

Proceeds must route to the creator, one way or the other. The default rule is receiver == creator. A non-creator receiver is accepted in exactly one shape — fee collection: the order's post-interaction must target the receiver itself (which is FeeTaker's own distribution condition, order.receiver.get() == address(this)), carry the custom-receiver flag, and name the creator in the custom-receiver slot, with the post-interaction maker trait set. That guarantees a conforming FeeTaker-layout settlement forwards the net to the wallet that funded the order; anything else — a zero receiver, missing hook trait, short fee bytes, a foreign target or a foreign custom receiver — reverts InvalidFeeReceiver, because each of those strands or diverts the taking asset (FeeTaker would otherwise pay the maker's share to order.maker, the shared contract, which deliberately cannot withdraw). The fee contract itself is the creator's choice and trust; naming a hostile one harms only the creator, whose funds alone back the order.

How it fits among the flows

Flow diagram: three creation flows (DelegatedMaker, a Safe MultiSend batch, classic EOA) all reaching the registrator as the maker of the order, and the fill path showing the presign check, the just-in-time pull and the post-interaction

The Safe and classic-EOA lanes ship in #430; this PR adds the top lane and the just-in-time pull row.

Open product decision: the fee shape

The fee-collection validation is the most coupling-heavy part of this contract — it mirrors FeeTaker's custom-receiver byte layout (_CUSTOM_RECEIVER_OFFSET = 61), the only place in the stack that depends on another contract's internals. It is kept because production Fusion orders route fees through the settlement-as-receiver, which is exactly this shape. If a feeless v1 for delegated orders is acceptable (the quoter zeroes integrator and protocol fees for them), the whole validation collapses to the one-line receiver == creator rule and the coupling disappears; re-adding it later is purely additive. Flagging the choice here rather than deciding it.

Tests

18 unit tests against LimitOrderProtocol, restored byte-identical from #430's pre-split state: the one-transaction lifecycle (create → anchored at the create block → filled with the empty signature, wallet untouched until the fill, the shared contract holding nothing before or after, and the protocol allowance self-provisioned on the first fill); exact just-in-time pulls across partial fills with proceeds landing on the owner directly; every creation rule rejected (wrong maker, zero receiver, bit-invalidator traits, missing pre-interaction hook, foreign pre-interaction target, duplicate hash, cancel-then-recreate); fee collection in the full production shape — fee taker as receiver paying the creator as custom receiver, anchored auction through the fee taker's getters, exclusivity through its post-interaction tail, the pull on the same order — plus five fee-shape reverts (missing flag, foreign custom receiver, foreign target, short bytes, missing hook trait) with a positive control; both permit paths — the allowance folded into one create transaction, and a front-run permit swallowed with the order still created and fillable; cancellation of a half-filled order killing the rest at the protocol (the first-fill-only ERC-1271 nuance); pull authorization (non-protocol callers and ownerless orders both refused); and two users on the shared maker isolated from each other's orders and cancellations.

2 mainnet-fork integration tests (the fourth and fifth of the stack's five, yarn test:fork with MAINNET_RPC_URL set):

  • the DelegatedMaker flow end to end through the live Aggregation Router V6: allowance, one createOrder transaction, and a fill that pulls just in time — nothing signed, nothing escrowed, the protocol allowance self-provisioned inside the fill;
  • fee collection through the live Fusion settlement (0x2Ad5004c60e16E54d5007C80CE329Adde5B51Ef5): the settlement is the order's receiver and pays the creator as its custom receiver, with a deliberate surplus fee set — the fill's curve premium lands above the scaled estimate and the settlement takes exactly surplusFee% of it, pinning the surplus-on-premium interaction feat: announcement-anchored Fusion auction, volume-ladder fill pricing, and signature-free maker flows #430's composition notes reference.

On this branch the full stack holds 100% coverage across statements, branches, functions and lines on all three contracts (DelegatedMaker, plus #430's FusionAnchoredAuction and OrderRegistrator); 239 unit tests and all 5 fork tests pass.

Review trail

The contract went through #430's seven Bugbot rounds while it lived there — including the round dedicated to the maker-only authorization and the round dedicated to the fee shape and permit paths — plus an eighth round on this split, focused on the restore commit and the stack's consistency (registrator two-argument registerOrder, the announcedAt duplicate check, the pull path, the fee offsets, permit owner-pinning, the dual-action cancel): no findings. The Slither run and the audit cross-checks recorded in #430's trail covered this contract as well.

Follow-ups

  • Fees couple to FeeTaker's custom-receiver layout by design; if the layout ever moves, the offsets in _createOrder move with it.
  • EIP-7702 is the eventual per-user alternative to the shared maker: a delegated EOA can hold the presign locally with no allowance concentration and the same one-transaction UX, and needs nothing from this repo.
  • Deploys through its own create3 salt and deploy script, after the registrator and the auction.
Open in Web Open in Cursor 

… flow

Co-authored-by: Sergej Kunz <info@deacix.de>
@deacix
deacix marked this pull request as ready for review August 5, 2026 07:46
@deacix
deacix merged commit df9c8e4 into cursor/fusion-announcement-anchored-auction-9252 Aug 5, 2026
3 checks passed
@deacix
deacix deleted the cursor/delegated-maker-9252 branch August 5, 2026 07:46
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.

2 participants