Skip to content

feat: add MetaSwap flexible settlement enforcer - #204

Open
hanzel98 wants to merge 1 commit into
mainfrom
feat/metaswap-flexible-settlement-enforcer
Open

feat: add MetaSwap flexible settlement enforcer#204
hanzel98 wants to merge 1 commit into
mainfrom
feat/metaswap-flexible-settlement-enforcer

Conversation

@hanzel98

@hanzel98 hanzel98 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add MetaSwapFlexibleSettlementEnforcer for one successful MetaSwap settlement with an exact signed input and redeemer-selected route.
  • Support native input plus ERC-20 SkipApproval, approve(amount), and approve(0) + approve(amount) through an exact ApprovalMode enum.
  • Enforce minimum output and atomic one-shot consumption in the same caveat, with shared deployment and verification support.

Behavior

One signed delegation authorizes one direct BATCH_DEFAULT_MODE settlement matching its selected mode:

None (native):        [MetaSwap.swap{ value: tokenInAmount }(...)]
SkipApproval:         [MetaSwap.swap(...)]
Approve:              [token.approve(metaSwap, tokenInAmount), MetaSwap.swap(...)]
ResetApprove:         [token.approve(metaSwap, 0), token.approve(metaSwap, tokenInAmount), MetaSwap.swap(...)]

Terms bind the MetaSwap target, input token and amount, exact approval mode, output token, recipient, and minimum output:

bytes memory terms = abi.encodePacked(
    metaSwap,
    tokenIn,
    tokenInAmount,
    uint8(MetaSwapFlexibleSettlementEnforcer.ApprovalMode.Approve),
    tokenOut,
    recipient,
    tokenOutMin
);

The enum values are None = 0, SkipApproval = 1, Approve = 2, and ResetApprove = 3. Native input requires None; ERC-20 input requires exactly one of the other modes. SkipApproval does not call or validate allowance(). aggregatorId and route data remain flexible and are supplied by the redeemer.

Approval spender and swap input token arguments must be canonical complete ABI address words with zeroed upper bytes. Swap calldata must be at least 196 bytes, covering the selector, four-word head, and two dynamic length words required by swap(string,address,uint256,bytes).

State and gas

A boolean mapping keyed by (DelegationManager, delegationHash) permanently records successful use. A separate mapping temporarily stores the recipient's raw pre-execution balance and is deleted after validation for a storage refund. Failed settlements revert atomically, including both writes, and remain retryable. Identical delegation hashes are isolated across DelegationManager contracts.

Prototype benchmarks against the equivalent three-caveat composition measured approximately 29-36% lower estimated charged redemption gas after applying the balance-snapshot storage refund. The production implementation also emits SettlementConsumed for indexable fills.

Security

The delegator trusts the configured MetaSwap contract, its adapters, and the delegate-provided route because swap route data is intentionally unrestricted. The enforcer binds the exact approval shape, approval spender and amount, swap target, input token and amount, and minimum output.

The enforcer cannot prevent arbitrary route side effects or protect unrelated assets already approved to MetaSwap or its adapters. Unrelated transfers, rebases, or dishonest token balance reporting may satisfy the output check. A residual input allowance may remain if MetaSwap spends less than the approved amount. Input and output tokens must differ.

Deployment and verification

Deploy with script/DeployCaveatEnforcers.s.sol and verify through the existing script/verification/verify-enforcer-contracts.sh flow. No dedicated script or environment variable is introduced.

Test plan

  • 34 focused tests covering native and ERC-20 input/output
  • Skip approval, single approval, and reset approval
  • Fuzzed exact enum/shape combinations and raw enum values 4..255
  • Targets, values, selectors, canonical address words, spender, amounts, modes, terms, and ordering
  • Explicit 196-byte acceptance and 195-byte rejection boundaries
  • Flexible aggregatorId and route data
  • Boolean one-shot state, event emission, insufficient-output rollback, retry, and replay prevention
  • Identical-hash isolation across two deployed DelegationManager contracts
  • Zero starting output balances and canonical DelegationManager redemption
  • 100% production line, statement, branch, and function coverage
  • 1,059 non-fork regression tests passing
  • Fork suites require LINEA_RPC_URL and ARBITRUM_RPC_URL

Note

Medium Risk
New on-chain path for MetaSwap swaps and ERC-20 approvals under delegation; route data stays unconstrained and output checks are balance-delta based, so delegators must trust MetaSwap/adapters and understand documented side-effect and token-behavior limits.

Overview
Adds MetaSwapFlexibleSettlementEnforcer, a single caveat that authorizes one successful MetaSwap settlement per signed delegation in BATCH_DEFAULT_MODE, with redeemer-chosen aggregatorId and route data but fixed swap target, input token/amount, output token, recipient, and minimum net balance increase.

Signed 145-byte packed terms include an ApprovalMode that pins the exact batch shape: native-only swap with value, ERC-20 swap only (SkipApproval), approve + swap, or reset + approve + swap. beforeHook structurally validates each execution (canonical approve words, minimum swap calldata length) and sets a per-(DelegationManager, delegationHash) consumed flag while snapshotting the recipient’s pre-execution output balance. afterHook requires the balance delta to meet tokenOutMin, emits SettlementConsumed, and clears the snapshot; failed redemptions revert atomically so the settlement stays retryable until a successful fill.

Also wires deployment in DeployCaveatEnforcers.s.sol, documents behavior and trust assumptions in CaveatEnforcers.md, and adds a broad Foundry test suite (modes, validation edge cases, end-to-end redemption, replay prevention).

Reviewed by Cursor Bugbot for commit 7995fa6. 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 4 times, most recently from b1dee69 to ff660b3 Compare September 3, 2026 01:14
Authorize one open-route MetaSwap settlement with exact input constraints, signed approval flexibility, minimum output, and atomic one-shot consumption.
@hanzel98
hanzel98 force-pushed the feat/metaswap-flexible-settlement-enforcer branch from ff660b3 to 7995fa6 Compare September 3, 2026 13:52
@hanzel98 hanzel98 self-assigned this Sep 3, 2026
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