Skip to content

Commit 5e6000c

Browse files
committed
feat(DelegationMetaSwapAdapter): restrict swapByDelegation to allowed operators
- Add allowedOperators mapping and onlyAllowedOperator modifier - Add updateAllowedOperators(operators, statuses) for owner to add/remove operators - Emit OperatorStatusChanged(operator, status); revert with NotAllowedOperator(operator) when caller not allowed - In tests: _allowOperator(subVault.deleGator) in mock and fork setup so existing tests pass - Add tests for NotAllowedOperator, OperatorStatusChanged, updateAllowedOperators (add/remove, onlyOwner, InputLengthsMismatch), and operator add/remove/re-add swap flow Made-with: Cursor
1 parent b553aae commit 5e6000c

File tree

6 files changed

+1050
-79
lines changed

6 files changed

+1050
-79
lines changed

.cursorrules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
This guide focuses on creating smart contracts that work seamlessly with the MetaMask Delegation Toolkit. The key principle is to keep your contracts simple, focused on core functionality, and completely unaware of the delegation system itself.
44

5+
## Build / Tooling
6+
7+
- Do **not** use `via_ir = true` (or `viaIR: true`) in Foundry/Hardhat config. Resolve stack-too-deep by refactoring (e.g. structs, internal helpers) instead.
8+
59
## Core Principles
610

711
1. **Simplicity**: Contracts should focus solely on their core business logic.

script/DeployDelegationMetaSwapAdapter.s.sol

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ contract DeployDelegationMetaSwapAdapter is Script {
2424
IDelegationManager delegationManager;
2525
IMetaSwap metaSwap;
2626
address argsEqualityCheckEnforcer;
27+
address metaSwapParamsEnforcer;
2728

2829
function setUp() public {
2930
salt = bytes32(abi.encodePacked(vm.envString("SALT")));
@@ -32,11 +33,13 @@ contract DeployDelegationMetaSwapAdapter is Script {
3233
metaSwap = IMetaSwap(vm.envAddress("METASWAP_ADDRESS"));
3334
swapApiSignerEnforcer = vm.envAddress("SWAPS_API_SIGNER_ADDRESS");
3435
argsEqualityCheckEnforcer = vm.envAddress("ARGS_EQUALITY_CHECK_ENFORCER_ADDRESS");
36+
metaSwapParamsEnforcer = vm.envAddress("META_SWAP_PARAMS_ENFORCER_ADDRESS");
3537
deployer = msg.sender;
3638
console2.log("~~~");
3739
console2.log("MetaSwap: %s", address(metaSwap));
3840
console2.log("SwapApiSignerEnforcer: %s", address(swapApiSignerEnforcer));
3941
console2.log("ArgsEqualityCheckEnforcer: %s", address(argsEqualityCheckEnforcer));
42+
console2.log("MetaSwapParamsEnforcer: %s", address(metaSwapParamsEnforcer));
4043
console2.log("DelegationManager: %s", address(delegationManager));
4144
console2.log("Deployer: %s", address(deployer));
4245
console2.log("DelegationMetaSwapAdapter Owner %s", address(metaSwapAdapterOwner));
@@ -50,7 +53,7 @@ contract DeployDelegationMetaSwapAdapter is Script {
5053

5154
address delegationMetaSwapAdapter = address(
5255
new DelegationMetaSwapAdapter{ salt: salt }(
53-
metaSwapAdapterOwner, swapApiSignerEnforcer, delegationManager, metaSwap, argsEqualityCheckEnforcer
56+
metaSwapAdapterOwner, swapApiSignerEnforcer, delegationManager, metaSwap, argsEqualityCheckEnforcer, metaSwapParamsEnforcer
5457
)
5558
);
5659
console2.log("DelegationMetaSwapAdapter: %s", delegationMetaSwapAdapter);
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// SPDX-License-Identifier: MIT AND Apache-2.0
2+
pragma solidity 0.8.23;
3+
4+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
6+
import { CaveatEnforcer } from "./CaveatEnforcer.sol";
7+
import { ModeCode } from "../utils/Types.sol";
8+
9+
/**
10+
* @title MetaSwapParamsEnforcer
11+
* @notice Enforces that the output token of a swap (tokenTo) is in the root delegator's allowed list.
12+
* @dev Used by DelegationMetaSwapAdapter. Terms = abi.encode(IERC20[] allowedTokens, address recipient, uint256
13+
* maxSlippagePercent).
14+
* recipient and maxSlippagePercent are read by the adapter only; this enforcer only validates tokenTo.
15+
* Slippage format: 100e18 = 100%, 10e18 = 10%, 0 = no per-delegation check.
16+
* Args = abi.encode(IERC20 tokenTo), set by the adapter before redeem.
17+
* allowedTokens must not be empty. Use a single element ANY_TOKEN to allow any output token; otherwise tokenTo must be in
18+
* allowedTokens.
19+
*/
20+
contract MetaSwapParamsEnforcer is CaveatEnforcer {
21+
////////////////////////////// Constants //////////////////////////////
22+
23+
/// @dev Special token value. When allowedTokens has length 1 and this value, any tokenTo is allowed.
24+
address public constant ANY_TOKEN = address(0xa11);
25+
26+
/// @dev Maximum allowed slippage: 100% in 18-decimal fixed point (100e18). Values must be in [0, PERCENT_100].
27+
uint256 public constant PERCENT_100 = 100e18;
28+
29+
////////////////////////////// External Functions //////////////////////////////
30+
31+
/**
32+
* @notice Decodes the terms used in this CaveatEnforcer.
33+
* @param _terms abi.encode(IERC20[] allowedTokens, address recipient, uint256 maxSlippagePercent)
34+
* @return allowedTokens_ Output tokens permitted by the root delegator (length >= 1; use [ANY_TOKEN] for any).
35+
* @return recipient_ Address to receive swap output (address(0) = root delegator).
36+
* @return maxSlippagePercent_ Max slippage (100e18 = 100%, 10e18 = 10%; 0 = no check; must be <= PERCENT_100).
37+
*/
38+
function getTermsInfo(bytes calldata _terms)
39+
public
40+
pure
41+
returns (IERC20[] memory allowedTokens_, address recipient_, uint256 maxSlippagePercent_)
42+
{
43+
(allowedTokens_, recipient_, maxSlippagePercent_) = abi.decode(_terms, (IERC20[], address, uint256));
44+
require(allowedTokens_.length != 0, "MetaSwapParamsEnforcer:invalid-empty-allowed-tokens");
45+
require(maxSlippagePercent_ <= PERCENT_100, "MetaSwapParamsEnforcer:invalid-max-slippage");
46+
}
47+
48+
/**
49+
* @notice Enforces that the swap output token (args) is in the allowed list (terms).
50+
* @param _terms abi.encode(IERC20[] allowedTokens, address recipient, uint256 maxSlippagePercent)
51+
* @param _args abi.encode(IERC20 tokenTo) — the output token from the swap execution
52+
*/
53+
function beforeHook(
54+
bytes calldata _terms,
55+
bytes calldata _args,
56+
ModeCode _mode,
57+
bytes calldata,
58+
bytes32,
59+
address,
60+
address
61+
)
62+
public
63+
pure
64+
override
65+
onlyDefaultExecutionMode(_mode)
66+
{
67+
(IERC20[] memory allowedTokens_,,) = getTermsInfo(_terms);
68+
IERC20 tokenTo_ = abi.decode(_args, (IERC20));
69+
70+
if (allowedTokens_.length == 1 && address(allowedTokens_[0]) == ANY_TOKEN) return;
71+
72+
for (uint256 i = 0; i < allowedTokens_.length; ++i) {
73+
if (address(allowedTokens_[i]) == address(tokenTo_)) return;
74+
}
75+
revert("MetaSwapParamsEnforcer:token-to-not-allowed");
76+
}
77+
}

0 commit comments

Comments
 (0)