Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
PRIVATE_KEY=
SALT=GATOR
DELEGATION_MANAGER_ADDRESS=
META_SWAP_ORDER_DELEGATION_MANAGER_ADDRESS=
ENTRYPOINT_ADDRESS=0x0000000071727De22E5E9d8BAf0edAc6f37da032
MULTISIG_DELEGATOR_IMPLEMENTATION_ADDRESS=
META_SWAP_ADAPTER_OWNER_ADDRESS=
Expand Down
70 changes: 70 additions & 0 deletions documents/CaveatEnforcers.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,76 @@ Enforcers can target specific call type modes: **single** or **batch**, and exec

---

### MetaSwapFlexibleSettlementEnforcer

Authorizes one successful MetaSwap settlement with a redeemer-selected route. It binds the input, approval behavior,
output asset, recipient, and minimum output while keeping route discovery flexible. A successful settlement is
permanently consumed; a reverted fill, including insufficient output, rolls back the consumed state and remains
retryable.

It accepts one direct `BATCH_DEFAULT_MODE` redemption with:

- Native input: `MetaSwap.swap{ value: tokenInAmount }(...)`
- ERC-20 skipping approval: `MetaSwap.swap(...)`
- ERC-20 approval: `approve(metaSwap, tokenInAmount)`, then `MetaSwap.swap(...)`
- ERC-20 reset approval: `approve(metaSwap, 0)`, `approve(metaSwap, tokenInAmount)`, then `MetaSwap.swap(...)`

Terms are packed as:

```text
metaSwap(20) | tokenIn(20) | tokenInAmount(32) | approvalMode(1) |
tokenOut(20) | recipient(20) | tokenOutMin(32)
```

`address(0)` represents the native token. The one-byte `ApprovalMode` enum selects exactly one execution shape:

- `0`: `None`, required for native input
- `1`: `SkipApproval`
- `2`: `Approve`
- `3`: `ResetApprove`

ERC-20 input requires modes `1` through `3`. The execution count must match the signed mode; caveat args are not used.
`SkipApproval` does not inspect allowance; the swap must have sufficient allowance to execute successfully.

Example terms for an ERC-20 settlement requiring `approve(amount)`:

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

The enforcer permanently records successful use in a boolean mapping and temporarily caches the recipient's raw
pre-execution output balance in a separate mapping. Both mappings are keyed by the DelegationManager and delegation hash.
The balance snapshot is deleted after validation for a storage refund. Any reverted settlement atomically rolls back the
consumed flag and remains retryable.

Approval spender and swap input token arguments must use canonical 32-byte ABI address words, including zeroed upper
bytes. Swap calldata must be at least 196 bytes: the selector, four-word static head, and two dynamic length words required
by `swap(string,address,uint256,bytes)`.

#### Trust Assumptions

MetaSwap's `aggregatorId` and route `data` remain unrestricted. The delegator trusts the delegate to provide safe route
data and trusts the configured MetaSwap contract and its adapters. The enforcer fixes the input token, input amount,
approval spender, approval amounts, output token, output recipient, and minimum net balance increase, but it cannot
prevent arbitrary route side effects or protect unrelated assets already approved to MetaSwap or its adapters.

The minimum output may be satisfied by any balance increase during the execution, including unrelated transfers or token
rebases. A malicious or non-standard output token may report misleading balances. A residual input allowance may remain
if MetaSwap spends less than the approved amount.

Deployment uses `script/DeployCaveatEnforcers.s.sol`. After recording deployed addresses, verification uses the shared
`script/verification/verify-enforcer-contracts.sh` flow.

---

## Enforcer Details

### NativeTokenPaymentEnforcer
Expand Down
89 changes: 89 additions & 0 deletions documents/MetaSwapSpecializedDelegationManagers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# MetaSwap Specialized Delegation Managers

These experimental managers preserve the existing three-array `redeemDelegations` entrypoint but intentionally support
only one batch/default settlement, one root delegation, and one caveat. The caveat's enforcer address must be the manager
itself because settlement enforcement is internal and no caveat hooks are called.

`executeFromExecutor` remains on the EIP-7702 account. Managers only call it.

## MetaSwapOrderDelegationManager

One manager for both product intents. Terms start with a one-byte `Intent`.

### ExactCalldata (gasless)

Replaces `ExactExecutionBatchEnforcer` + `LimitedCallsEnforcer(1)`. The user is shown the real swap batch and signs its
hash. Expiry stays off-chain (relayer stops submitting).

```text
terms = intent(1) | executionHash(32)
executionCallDatas[0] = ExecutionLib.encodeBatch(executions)
```

Require `keccak256(executionCallDatas[0]) == executionHash`, then execute the batch directly. No nested self-`execute`
wrap. Supported shapes:

- `[swap]`
- `[approve(amount), swap]`
- `[approve(0), approve(amount), swap]`
- `[swap{value}]` native

### FlexibleSettlement (limit order)

Same economics as `MetaSwapFlexibleSettlementEnforcer` / the hookless manager:

```text
terms = intent(1) | metaSwap(20) | tokenIn(20) | tokenInAmount(32) | approvalMode(1)
| tokenOut(20) | recipient(20) | tokenOutMin(32)
```

Redeemer supplies an encoded `Execution[]`. The manager validates approval/swap shape (not `aggregatorId` / route),
snapshots the output balance in memory, executes, then enforces `tokenOutMin`.

## Prototype managers kept for A/B

### MetaSwapHooklessDelegationManager

Flexible-only. Redeemer supplies a complete ABI-encoded `Execution[]`. Validates shape, then executes.

### MetaSwapExecutionBuilderDelegationManager

Flexible-only. Redeemer supplies `abi.encode(aggregatorId, routeData)`. Manager constructs approvals and swap.

Signatures try ECDSA first (EOA and EIP-7702 ETH keys). If that misses, empty accounts revert; accounts with code fall back to ERC-1271.

`disabledDelegations` is both cancel and one-shot consumption. Failed execution or insufficient output reverts atomically.

## Redemption gas comparison (EIP-7702)

Measured with `gasleft()` immediately around `redeemDelegations` in
`test/MetaSwapOrderDelegationManager.t.sol`. Each pair executes the same swap shape from a one-element root delegation,
using an ECDSA signature and a zero starting output-token balance. Values include the manager call but exclude top-level
transaction intrinsic calldata gas.

| Purpose | Swap shape | Existing `DelegationManager` path | Existing gas | Order gas | Saving |
| ------------------------ | ----------------- | --------------------------------------------- | ------------ | --------- | ------ |
| Gasless exact swap | Native swap | `ExactExecutionBatch` + `LimitedCalls(1)` | `167,475` | `96,618` | 42.3% |
| Gasless exact swap | `approve + swap` | `ExactExecutionBatch` + `LimitedCalls(1)` | `230,987` | `152,242` | 34.1% |
| Gasless exact swap | `reset + approve + swap` | `ExactExecutionBatch` + `LimitedCalls(1)` | `244,355` | `157,710` | 35.5% |
| Flexible limit order | Native swap | `MetaSwapFlexibleSettlementEnforcer` | `143,414` | `101,730` | 29.1% |
| Flexible limit order | `approve + swap` | `MetaSwapFlexibleSettlementEnforcer` | `200,783` | `158,990` | 20.8% |
| Flexible limit order | `reset + approve + swap` | `MetaSwapFlexibleSettlementEnforcer` | `207,974` | `166,072` | 20.1% |

Takeaways:

- Exact orders save 34–42% by replacing generic delegation loops, hook calls, full execution terms, and the
`LimitedCallsEnforcer` state/event with a specialized one-shot path.
- Flexible orders save 20–29% while retaining the existing enforcer's approval-shape, swap-calldata, and output-delta
validations.
- The specialized manager's compact redemption event is part of the measured saving; the generic manager emits the full
delegation.

## Limitations

- Delegation chains, multiple caveats, multiple redemption batches, self-authorized empty contexts, try mode, and generic
enforcers are intentionally unsupported.
- No on-chain expiry for exact/gasless; relayers enforce freshness off-chain.
- No `enableDelegation` or pause controls.
- Flexible route data retains the same trusted-delegate and unrelated-balance-increase assumptions as the settlement
enforcer.
4 changes: 4 additions & 0 deletions script/DeployCaveatEnforcers.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { ExactExecutionEnforcer } from "../src/enforcers/ExactExecutionEnforcer.
import { IdEnforcer } from "../src/enforcers/IdEnforcer.sol";
import { LimitedCallsEnforcer } from "../src/enforcers/LimitedCallsEnforcer.sol";
import { LogicalOrWrapperEnforcer } from "../src/enforcers/LogicalOrWrapperEnforcer.sol";
import { MetaSwapFlexibleSettlementEnforcer } from "../src/enforcers/MetaSwapFlexibleSettlementEnforcer.sol";
import { MultiTokenPeriodEnforcer } from "../src/enforcers/MultiTokenPeriodEnforcer.sol";
import { NativeBalanceChangeEnforcer } from "../src/enforcers/NativeBalanceChangeEnforcer.sol";
import { NativeTokenPaymentEnforcer } from "../src/enforcers/NativeTokenPaymentEnforcer.sol";
Expand Down Expand Up @@ -133,6 +134,9 @@ contract DeployCaveatEnforcers is Script {
deployedAddress = address(new LogicalOrWrapperEnforcer{ salt: salt }(delegationManager));
console2.log("LogicalOrWrapperEnforcer: %s", deployedAddress);

deployedAddress = address(new MetaSwapFlexibleSettlementEnforcer{ salt: salt }());
console2.log("MetaSwapFlexibleSettlementEnforcer: %s", deployedAddress);

deployedAddress = address(new MultiTokenPeriodEnforcer{ salt: salt }());
console2.log("MultiTokenPeriodEnforcer: %s", deployedAddress);

Expand Down
39 changes: 39 additions & 0 deletions script/DeployMetaSwapOrderDelegationManager.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT AND Apache-2.0
pragma solidity 0.8.23;

import "forge-std/Script.sol";
import { console2 } from "forge-std/console2.sol";

import { MetaSwapOrderDelegationManager } from "../src/MetaSwapOrderDelegationManager.sol";

/**
* @title DeployMetaSwapOrderDelegationManager
* @notice Deploys the experimental MetaSwap order delegation manager.
* @dev Experimental. EIP-7702 accounts must be wired to this manager address.
*
* forge script script/DeployMetaSwapOrderDelegationManager.s.sol --rpc-url <rpc> --private-key $PRIVATE_KEY --broadcast
*
* Env:
* - SALT
*/
contract DeployMetaSwapOrderDelegationManager is Script {
bytes32 salt;

function setUp() public {
salt = bytes32(abi.encodePacked(vm.envString("SALT")));

console2.log("~~~");
console2.log("Salt:");
console2.logBytes32(salt);
}

function run() public {
console2.log("~~~");
vm.startBroadcast();

address deployedAddress = address(new MetaSwapOrderDelegationManager{ salt: salt }());
console2.log("MetaSwapOrderDelegationManager: %s", deployedAddress);

vm.stopBroadcast();
}
}
11 changes: 11 additions & 0 deletions script/verification/VerificationInstructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ Verifies an array of enforcer contracts.
./verify-enforcer-contracts.sh
```

#### `verify-metaswap-order-delegation-manager.sh`

Experimental. Verifies `MetaSwapOrderDelegationManager`.
Requires `META_SWAP_ORDER_DELEGATION_MANAGER_ADDRESS`.

**Usage:**

```bash
./verify-metaswap-order-delegation-manager.sh
```

## Notes

- Ensure the `.env` file is correctly configured and contains all necessary API keys.
Expand Down
55 changes: 55 additions & 0 deletions script/verification/verify-metaswap-order-delegation-manager.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# verify-metaswap-order-delegation-manager.sh
#
# Usage:
# ./verify-metaswap-order-delegation-manager.sh
#
# Experimental. Verifies MetaSwapOrderDelegationManager across configured chains.
# Requires in .env:
# META_SWAP_ORDER_DELEGATION_MANAGER_ADDRESS

set -e

set -o allexport
source ../../.env
set +o allexport

source ./verify-utils.sh

declare -a CONTRACTS

add_contract() {
local name="$1"
local path="$2"
local address="$3"
local constructor_args="$4"
local lib_string="$5"
CONTRACTS+=("$name:$path:$address:$constructor_args:$lib_string")
}

add_contract \
"MetaSwapOrderDelegationManager" \
"src/MetaSwapOrderDelegationManager.sol" \
"${META_SWAP_ORDER_DELEGATION_MANAGER_ADDRESS}" \
"" \
""

for contract in "${CONTRACTS[@]}"; do
IFS=':' read -r name path address constructor_args lib_string <<< "$contract"

echo "============================================="
echo "Verifying contract: $name"
echo "============================================="

verify_across_chains \
"$path" \
"$name" \
"$address" \
"$constructor_args" \
"$lib_string"

echo "============================================="
echo "Completed verification for: $name"
echo "============================================="
echo
done
Loading
Loading