Skip to content

feat: add MetaSwap flexible settlement enforcer - #204

Open
hanzel98 wants to merge 2 commits into
mainfrom
feat/metaswap-flexible-settlement-enforcer
Open

hanzel98 wants to merge 2 commits into
mainfrom
feat/metaswap-flexible-settlement-enforcer

Conversation

@hanzel98

@hanzel98 hanzel98 commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add MetaSwapFlexibleSettlementEnforcer: MetaSwap-router caveat for one native/ERC-20 swap (optional approve / reset-approve).
  • Embeds min-output (ERC-20 or native balance change), redeemer allowlist, optional timestamp, and optional id in one caveat.
  • Exact ApprovalMode pins the batch shape; route aggregatorId / data stay redeemer-selected.

Combined behavior

Concern Behavior
Settlement Exact MetaSwap.swap ± approval legs (BATCH_DEFAULT_MODE)
Min output Balance increase ≥ tokenOutMin (tokenOut == address(0) → native)
Redeemer Packed allowlist (≥1)
Timestamp Optional; 0 disables a bound (non-inclusive, like TimestampEnforcer)
Id Optional; 0 → hash one-shot (manager, delegationHash); non-zero → bitmap (manager, delegator, id) + mutual exclusion (hash flag skipped — same id already blocks replay)

Native input requires None; ERC-20 requires SkipApproval / Approve / ResetApprove. Failed fills roll back one-shot state.

Terms

metaSwap(20) | tokenIn(20) | tokenInAmount(32) | approvalMode(1) |
tokenOut(20) | recipient(20) | tokenOutMin(32) |
timestampAfter(16) | timestampBefore(16) | id(32) | redeemers(20*N)
bytes memory terms = abi.encodePacked(
    metaSwap, tokenIn, tokenInAmount,
    uint8(MetaSwapFlexibleSettlementEnforcer.ApprovalMode.Approve),
    tokenOut, recipient, tokenOutMin,
    uint128(0), uint128(0), // no timestamp
    uint256(0),             // hash one-shot
    redeemer
);

Approve spender / swap token words must be canonical ABI addresses. Swap calldata ≥ 196 bytes.

Security

Trust MetaSwap, adapters, and redeemer-chosen routes. Min output can be met by any balance increase. Residual allowance may remain. Input ≠ output.

Deployment

script/DeployCaveatEnforcers.s.sol · verify via script/verification/verify-enforcer-contracts.sh

Test plan

  • Native / ERC-20 in and out; skip / approve / reset shapes
  • Mode pairing enforced at terms decode; fuzzed invalid modes
  • Optional timestamp (disabled, after-open, early, expired)
  • Optional id (hash one-shot vs bitmap, mutual exclusion, rollback)
  • Redeemer allowlist (match / reject / multi-entry)
  • Terms length and redeemer alignment; swap calldata boundaries
  • 100% line / statement / branch / function coverage on the enforcer

Note

Medium Risk
New settlement path touches approvals, swaps, and balance-based min-output checks with unrestricted routes and known gaming vectors (any balance increase counts); extensive tests mitigate but delegators must understand trust assumptions.

Overview
Adds MetaSwapFlexibleSettlementEnforcer, a single caveat that authorizes one MetaSwap-router settlement in batch default mode while folding several policies into packed terms.

Signed terms pin the router, input amount, output recipient, minimum balance increase (ERC-20 or native when tokenOut == address(0)), a required redeemer allowlist, and an ApprovalMode that fixes batch shape: native-only swap, or ERC-20 swap with skip / approve / reset-approve legs. Swap validation checks target, value, selector, token word, and amount; route aggregatorId / data stay redeemer-chosen.

One-shot consumption is optional: id == 0 marks (delegationManager, delegationHash) in beforeHook; non-zero id uses an IdEnforcer-style bitmap per (manager, delegator) so replacement orders sharing an order id are mutually exclusive (hash flag skipped on that path). Optional timestamp bounds mirror TimestampEnforcer. afterHook compares recipient balance delta to tokenOutMin; failed redemptions revert atomically so consumption state rolls back.

Also wires CREATE2 deploy in DeployCaveatEnforcers.s.sol, documents terms/trust assumptions in CaveatEnforcers.md (plus minor doc table formatting), and adds broad Foundry coverage including end-to-end redemption through DelegationManager.

Reviewed by Cursor Bugbot for commit 1455eac. Bugbot is set up for automated code reviews on this repo. Configure here.

@hanzel98
hanzel98 requested a review from a team as a code owner September 2, 2026 14:47
@hanzel98
hanzel98 force-pushed the feat/metaswap-flexible-settlement-enforcer branch 5 times, most recently from ff660b3 to 7995fa6 Compare September 3, 2026 13:52
@hanzel98 hanzel98 self-assigned this Sep 3, 2026
@hanzel98
hanzel98 force-pushed the feat/metaswap-flexible-settlement-enforcer branch 2 times, most recently from 6ac8476 to 4049a6b Compare September 25, 2026 03:05
Authorize one open-route MetaSwap settlement with exact input constraints, signed approval flexibility, minimum output, and atomic one-shot consumption.
Fold RedeemerEnforcer, TimestampEnforcer, IdEnforcer, and ERC20/Native
balance-change semantics into MetaSwapFlexibleSettlementEnforcer so one
MetaSwap-router caveat can settle native or ERC-20 swaps (optional
approval) with an allowlisted redeemer. Timestamp bounds of zero disable
time checks; id zero keeps hash-based one-shot consumption while a
non-zero id uses the per-delegator bitmap for mutual exclusion.
@hanzel98
hanzel98 force-pushed the feat/metaswap-flexible-settlement-enforcer branch from e40716c to 1455eac Compare September 25, 2026 03:39
@hanzel98

hanzel98 commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor Author
image

Existing means previous delegation existing code. Flexible is the new flexible enforcer of this PR

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