From 19020d17f592d4d563cd7177d907e79add9925f1 Mon Sep 17 00:00:00 2001 From: hanzel98 Date: Tue, 11 Mar 2025 12:11:47 -0600 Subject: [PATCH 1/6] Optional token whitelist validation --- script/DeployDelegationMetaSwapAdapter.s.sol | 84 ++++++++++---------- src/helpers/DelegationMetaSwapAdapter.sol | 62 ++++++++++++++- test/helpers/DelegationMetaSwapAdapter.t.sol | 15 +++- 3 files changed, 111 insertions(+), 50 deletions(-) diff --git a/script/DeployDelegationMetaSwapAdapter.s.sol b/script/DeployDelegationMetaSwapAdapter.s.sol index 12a25cc9..df9d627f 100644 --- a/script/DeployDelegationMetaSwapAdapter.s.sol +++ b/script/DeployDelegationMetaSwapAdapter.s.sol @@ -1,49 +1,49 @@ -// SPDX-License-Identifier: MIT AND Apache-2.0 -pragma solidity 0.8.23; +// // 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 "forge-std/Script.sol"; +// import { console2 } from "forge-std/console2.sol"; -import { DelegationMetaSwapAdapter } from "../src/helpers/DelegationMetaSwapAdapter.sol"; -import { IDelegationManager } from "../src/interfaces/IDelegationManager.sol"; -import { IMetaSwap } from "../src/helpers/interfaces/IMetaSwap.sol"; +// import { DelegationMetaSwapAdapter } from "../src/helpers/DelegationMetaSwapAdapter.sol"; +// import { IDelegationManager } from "../src/interfaces/IDelegationManager.sol"; +// import { IMetaSwap } from "../src/helpers/interfaces/IMetaSwap.sol"; -/** - * @title DeployDelegationMetaSwapAdapter - * @notice Deploys the delegationMetaSwapAdapter contract. - * @dev These contracts are likely already deployed on a testnet or mainnet as many are singletons. - * @dev Fill the required variables in the .env file - * @dev run the script with: - * forge script script/DeployDelegationMetaSwapAdapter.s.sol --rpc-url --private-key $PRIVATE_KEY --broadcast - */ -contract DeployDelegationMetaSwapAdapter is Script { - bytes32 salt; - address deployer; - address metaSwapAdapterOwner; - IDelegationManager delegationManager; - IMetaSwap metaSwap; +// /** +// * @title DeployDelegationMetaSwapAdapter +// * @notice Deploys the delegationMetaSwapAdapter contract. +// * @dev These contracts are likely already deployed on a testnet or mainnet as many are singletons. +// * @dev Fill the required variables in the .env file +// * @dev run the script with: +// * forge script script/DeployDelegationMetaSwapAdapter.s.sol --rpc-url --private-key $PRIVATE_KEY --broadcast +// */ +// contract DeployDelegationMetaSwapAdapter is Script { +// bytes32 salt; +// address deployer; +// address metaSwapAdapterOwner; +// IDelegationManager delegationManager; +// IMetaSwap metaSwap; - function setUp() public { - salt = bytes32(abi.encodePacked(vm.envString("SALT"))); - metaSwapAdapterOwner = vm.envAddress("META_SWAP_ADAPTER_OWNER_ADDRESS"); - delegationManager = IDelegationManager(vm.envAddress("DELEGATION_MANAGER_ADDRESS")); - metaSwap = IMetaSwap(vm.envAddress("METASWAP_ADDRESS")); - deployer = msg.sender; - console2.log("~~~"); - console2.log("Deployer: %s", address(deployer)); - console2.log("DelegationMetaSwapAdapter Owner %s", address(metaSwapAdapterOwner)); - console2.log("Salt:"); - console2.logBytes32(salt); - } +// function setUp() public { +// salt = bytes32(abi.encodePacked(vm.envString("SALT"))); +// metaSwapAdapterOwner = vm.envAddress("META_SWAP_ADAPTER_OWNER_ADDRESS"); +// delegationManager = IDelegationManager(vm.envAddress("DELEGATION_MANAGER_ADDRESS")); +// metaSwap = IMetaSwap(vm.envAddress("METASWAP_ADDRESS")); +// deployer = msg.sender; +// console2.log("~~~"); +// console2.log("Deployer: %s", address(deployer)); +// console2.log("DelegationMetaSwapAdapter Owner %s", address(metaSwapAdapterOwner)); +// console2.log("Salt:"); +// console2.logBytes32(salt); +// } - function run() public { - console2.log("~~~"); - vm.startBroadcast(); +// function run() public { +// console2.log("~~~"); +// vm.startBroadcast(); - address delegationMetaSwapAdapter = - address(new DelegationMetaSwapAdapter{ salt: salt }(metaSwapAdapterOwner, delegationManager, metaSwap)); - console2.log("DelegationMetaSwapAdapter: %s", delegationMetaSwapAdapter); +// address delegationMetaSwapAdapter = +// address(new DelegationMetaSwapAdapter{ salt: salt }(metaSwapAdapterOwner, delegationManager, metaSwap)); +// console2.log("DelegationMetaSwapAdapter: %s", delegationMetaSwapAdapter); - vm.stopBroadcast(); - } -} +// vm.stopBroadcast(); +// } +// } diff --git a/src/helpers/DelegationMetaSwapAdapter.sol b/src/helpers/DelegationMetaSwapAdapter.sol index 746c718e..a82939b4 100644 --- a/src/helpers/DelegationMetaSwapAdapter.sol +++ b/src/helpers/DelegationMetaSwapAdapter.sol @@ -13,6 +13,8 @@ import { IDelegationManager } from "../interfaces/IDelegationManager.sol"; import { CallType, ExecType, Delegation, ModeCode } from "../utils/Types.sol"; import { CALLTYPE_SINGLE, EXECTYPE_DEFAULT } from "../utils/Constants.sol"; +import "forge-std/Test.sol"; + /** * @title DelegationMetaSwapAdapter * @notice Acts as a middleman to orchestrate token swaps using delegations @@ -31,6 +33,9 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { /// @dev The MetaSwap contract used to swap tokens IMetaSwap public immutable metaSwap; + /// @dev The enforcer used to compare args and terms + address public immutable argsEqualityCheckEnforcer; + /// @dev Indicates if a token is allowed to be used in the swaps mapping(IERC20 token => bool allowed) public isTokenAllowed; @@ -45,6 +50,9 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { /// @dev Emitted when the MetaSwap contract address is set. event SetMetaSwap(IMetaSwap indexed newMetaSwap); + /// @dev Emitted when the Args Equality Check Enforcer contract address is set. + event SetArgsEqualityCheckEnforcer(address newArgsEqualityCheckEnforcer); + /// @dev Emitted when the contract sends tokens (or native tokens) to a recipient. event SentTokens(IERC20 indexed token, address indexed recipient, uint256 amount); @@ -87,7 +95,7 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { error TokenToIsNotAllowed(IERC20 token); /// @dev Error when the aggregator ID is not in the allow list. - error AggregatorIdIsNotAllowed(string); + error AggregatorIdIsNotAllowed(string aggregatorId); /// @dev Error when the input arrays of a function have different lengths. error InputLengthsMismatch(); @@ -104,6 +112,9 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { /// @dev Error when the amountFrom in the api data and swap data do not match. error AmountFromMismath(); + /// @dev Error when the delegations do not include the ArgsEqualityCheckEnforcer + error MissingArgsEqualityCheckEnforcer(); + ////////////////////////////// Modifiers ////////////////////////////// /** @@ -130,11 +141,20 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { * @param _delegationManager the address of the trusted DelegationManager contract that will have root access to this contract * @param _metaSwap the address of the trusted MetaSwap contract. */ - constructor(address _owner, IDelegationManager _delegationManager, IMetaSwap _metaSwap) Ownable(_owner) { + constructor( + address _owner, + IDelegationManager _delegationManager, + IMetaSwap _metaSwap, + address _argsEqualityCheckEnforcer + ) + Ownable(_owner) + { delegationManager = _delegationManager; metaSwap = _metaSwap; + argsEqualityCheckEnforcer = _argsEqualityCheckEnforcer; emit SetDelegationManager(_delegationManager); emit SetMetaSwap(_metaSwap); + emit SetArgsEqualityCheckEnforcer(_argsEqualityCheckEnforcer); } ////////////////////////////// External Methods ////////////////////////////// @@ -144,6 +164,31 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { */ receive() external payable { } + function _validateTokens( + IERC20 _tokenFrom, + IERC20 _tokenTo, + Delegation[] memory _delegations, + bool _useTokenWhitelist + ) + private + view + { + if (_delegations[0].caveats.length == 0 || _delegations[0].caveats[0].enforcer != argsEqualityCheckEnforcer) { + revert MissingArgsEqualityCheckEnforcer(); + } + + if (_useTokenWhitelist) { + if (!isTokenAllowed[_tokenFrom]) revert TokenFromIsNotAllowed(_tokenFrom); + if (!isTokenAllowed[_tokenTo]) revert TokenToIsNotAllowed(_tokenTo); + // The Args Enforcer with this data must be the first enforcer in the delegations caveats + _delegations[0].caveats[0].args = abi.encode("Enforce Token Whitelist"); + } else { + // The Args Enforcer with this data must be the first enforcer in the delegations caveats + _delegations[0].caveats[0].args = abi.encode("Skip Token Whitelist"); + } + } + + // * @param _useTokenWhitelist Indicates whether the tokens must be validated or not. /** * @notice Executes a token swap using a delegation and transfers the swapped tokens to the root delegator. * @dev The msg.sender must be the leaf delegator @@ -151,14 +196,23 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { * @param _delegations Array of Delegation objects containing delegation-specific data, sorted leaf to root. */ function swapByDelegation(bytes calldata _apiData, Delegation[] memory _delegations) external { + // function swapByDelegation(bytes calldata _apiData, Delegation[] memory _delegations, bool _useTokenWhitelist) external { (string memory aggregatorId_, IERC20 tokenFrom_, IERC20 tokenTo_, uint256 amountFrom_, bytes memory swapData_) = _decodeApiData(_apiData); uint256 delegationsLength_ = _delegations.length; if (delegationsLength_ == 0) revert InvalidEmptyDelegations(); if (tokenFrom_ == tokenTo_) revert InvalidIdenticalTokens(); - if (!isTokenAllowed[tokenFrom_]) revert TokenFromIsNotAllowed(tokenFrom_); - if (!isTokenAllowed[tokenTo_]) revert TokenToIsNotAllowed(tokenTo_); + + // console2.log("0 _delegations[0].caveats[0].args:"); + // console2.logBytes(_delegations[0].caveats[0].args); + + _validateTokens(tokenFrom_, tokenTo_, _delegations, true); + // _validateTokens(tokenFrom_, tokenTo_, _delegations, _useTokenWhitelist); + + // console2.log("2 _delegations[0].caveats[0].args:"); + // console2.logBytes(_delegations[0].caveats[0].args); + if (!isAggregatorAllowed[keccak256(abi.encode(aggregatorId_))]) revert AggregatorIdIsNotAllowed(aggregatorId_); if (_delegations[0].delegator != msg.sender) revert NotLeafDelegator(); diff --git a/test/helpers/DelegationMetaSwapAdapter.t.sol b/test/helpers/DelegationMetaSwapAdapter.t.sol index df88dece..bb2ce16f 100644 --- a/test/helpers/DelegationMetaSwapAdapter.t.sol +++ b/test/helpers/DelegationMetaSwapAdapter.t.sol @@ -20,6 +20,7 @@ import { AllowedCalldataEnforcer } from "../../src/enforcers/AllowedCalldataEnfo import { AllowedMethodsEnforcer } from "../../src/enforcers/AllowedMethodsEnforcer.sol"; import { ValueLteEnforcer } from "../../src/enforcers/ValueLteEnforcer.sol"; import { RedeemerEnforcer } from "../../src/enforcers/RedeemerEnforcer.sol"; +import { ArgsEqualityCheckEnforcer } from "../../src/enforcers/ArgsEqualityCheckEnforcer.sol"; import { BytesLib } from "@bytes-utils/BytesLib.sol"; import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import { DelegationManager } from "../../src/DelegationManager.sol"; @@ -53,6 +54,7 @@ abstract contract DelegationMetaSwapAdapterBaseTest is BaseTest { AllowedTargetsEnforcer public allowedTargetsEnforcer; AllowedMethodsEnforcer public allowedMethodsEnforcer; ValueLteEnforcer public valueLteEnforcer; + ArgsEqualityCheckEnforcer public argsEqualityCheckEnforcer; RedeemerEnforcer public redeemerEnforcer; bytes public swapDataTokenAtoTokenB; @@ -72,6 +74,7 @@ abstract contract DelegationMetaSwapAdapterBaseTest is BaseTest { allowedMethodsEnforcer = new AllowedMethodsEnforcer(); valueLteEnforcer = new ValueLteEnforcer(); redeemerEnforcer = new RedeemerEnforcer(); + argsEqualityCheckEnforcer = new ArgsEqualityCheckEnforcer(); } //////////////////////// Internal / Private Helpers //////////////////////// @@ -915,7 +918,9 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest vm.expectEmit(true, true, false, true); emit DelegationMetaSwapAdapter.SetMetaSwap(IMetaSwap(dummyMetaSwap_)); // Deploy a new instance to capture the events. - new DelegationMetaSwapAdapter(owner, IDelegationManager(dummyDelegationManager_), IMetaSwap(dummyMetaSwap_)); + new DelegationMetaSwapAdapter( + owner, IDelegationManager(dummyDelegationManager_), IMetaSwap(dummyMetaSwap_), address(argsEqualityCheckEnforcer) + ); } /** @@ -940,8 +945,9 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest metaSwapMock = IMetaSwap(address(new MetaSwapMock(IERC20(tokenA), IERC20(tokenB)))); - delegationMetaSwapAdapter = - new DelegationMetaSwapAdapter(owner, IDelegationManager(address(delegationManager)), metaSwapMock); + delegationMetaSwapAdapter = new DelegationMetaSwapAdapter( + owner, IDelegationManager(address(delegationManager)), metaSwapMock, address(argsEqualityCheckEnforcer) + ); vm.startPrank(owner); @@ -1098,7 +1104,8 @@ contract DelegationMetaSwapAdapterForkTest is DelegationMetaSwapAdapterBaseTest { // Overriding values entryPoint = ENTRY_POINT_FORK; - delegationMetaSwapAdapter = new DelegationMetaSwapAdapter(owner, DELEGATION_MANAGER_FORK, META_SWAP_FORK); + delegationMetaSwapAdapter = + new DelegationMetaSwapAdapter(owner, DELEGATION_MANAGER_FORK, META_SWAP_FORK, address(argsEqualityCheckEnforcer)); delegationManager = DelegationManager(address(DELEGATION_MANAGER_FORK)); hybridDeleGatorImpl = HYBRID_DELEGATOR_IMPL_FORK; From 6e7f5afa03d1355e991a2f6f93b1b823405127b8 Mon Sep 17 00:00:00 2001 From: hanzel98 Date: Thu, 13 Mar 2025 08:05:22 -0600 Subject: [PATCH 2/6] Added whitelist flag to redeemByDelegation --- src/helpers/DelegationMetaSwapAdapter.sol | 92 +++++----- test/helpers/DelegationMetaSwapAdapter.t.sol | 182 ++++++++++++++++--- 2 files changed, 204 insertions(+), 70 deletions(-) diff --git a/src/helpers/DelegationMetaSwapAdapter.sol b/src/helpers/DelegationMetaSwapAdapter.sol index a82939b4..b6ef2f74 100644 --- a/src/helpers/DelegationMetaSwapAdapter.sol +++ b/src/helpers/DelegationMetaSwapAdapter.sol @@ -13,12 +13,13 @@ import { IDelegationManager } from "../interfaces/IDelegationManager.sol"; import { CallType, ExecType, Delegation, ModeCode } from "../utils/Types.sol"; import { CALLTYPE_SINGLE, EXECTYPE_DEFAULT } from "../utils/Constants.sol"; -import "forge-std/Test.sol"; - /** * @title DelegationMetaSwapAdapter - * @notice Acts as a middleman to orchestrate token swaps using delegations - * and an aggregator (MetaSwap). + * @notice Acts as a middleman to orchestrate token swaps using delegations and an aggregator (MetaSwap). + * @dev This contract depends on an ArgsEqualityCheckEnforcer. The root delegation must include a caveat + * with this enforcer as its first element. Its arguments indicate whether the swap should enforce the token + * whitelist ("Enforce Token Whitelist") or skip the whitelist ("Skip Token Whitelist"). The root delegator is + * responsible for including this enforcer to signal the desired behavior. */ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { using ModeLib for ModeCode; @@ -136,10 +137,11 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { ////////////////////////////// Constructor ////////////////////////////// /** - * @notice Initializes the DelegationMetaSwapAdapter contract - * @param _owner The initial owner of the contract - * @param _delegationManager the address of the trusted DelegationManager contract that will have root access to this contract - * @param _metaSwap the address of the trusted MetaSwap contract. + * @notice Initializes the DelegationMetaSwapAdapter contract. + * @param _owner The initial owner of the contract. + * @param _delegationManager The address of the trusted DelegationManager contract that will have root access to this contract. + * @param _metaSwap The address of the trusted MetaSwap contract. + * @param _argsEqualityCheckEnforcer The address of the ArgsEqualityCheckEnforcer contract. */ constructor( address _owner, @@ -164,39 +166,14 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { */ receive() external payable { } - function _validateTokens( - IERC20 _tokenFrom, - IERC20 _tokenTo, - Delegation[] memory _delegations, - bool _useTokenWhitelist - ) - private - view - { - if (_delegations[0].caveats.length == 0 || _delegations[0].caveats[0].enforcer != argsEqualityCheckEnforcer) { - revert MissingArgsEqualityCheckEnforcer(); - } - - if (_useTokenWhitelist) { - if (!isTokenAllowed[_tokenFrom]) revert TokenFromIsNotAllowed(_tokenFrom); - if (!isTokenAllowed[_tokenTo]) revert TokenToIsNotAllowed(_tokenTo); - // The Args Enforcer with this data must be the first enforcer in the delegations caveats - _delegations[0].caveats[0].args = abi.encode("Enforce Token Whitelist"); - } else { - // The Args Enforcer with this data must be the first enforcer in the delegations caveats - _delegations[0].caveats[0].args = abi.encode("Skip Token Whitelist"); - } - } - - // * @param _useTokenWhitelist Indicates whether the tokens must be validated or not. /** * @notice Executes a token swap using a delegation and transfers the swapped tokens to the root delegator. * @dev The msg.sender must be the leaf delegator * @param _apiData Encoded swap parameters, used by the aggregator. * @param _delegations Array of Delegation objects containing delegation-specific data, sorted leaf to root. + * @param _useTokenWhitelist Indicates whether the tokens must be validated or not. */ - function swapByDelegation(bytes calldata _apiData, Delegation[] memory _delegations) external { - // function swapByDelegation(bytes calldata _apiData, Delegation[] memory _delegations, bool _useTokenWhitelist) external { + function swapByDelegation(bytes calldata _apiData, Delegation[] memory _delegations, bool _useTokenWhitelist) external { (string memory aggregatorId_, IERC20 tokenFrom_, IERC20 tokenTo_, uint256 amountFrom_, bytes memory swapData_) = _decodeApiData(_apiData); uint256 delegationsLength_ = _delegations.length; @@ -204,14 +181,7 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { if (delegationsLength_ == 0) revert InvalidEmptyDelegations(); if (tokenFrom_ == tokenTo_) revert InvalidIdenticalTokens(); - // console2.log("0 _delegations[0].caveats[0].args:"); - // console2.logBytes(_delegations[0].caveats[0].args); - - _validateTokens(tokenFrom_, tokenTo_, _delegations, true); - // _validateTokens(tokenFrom_, tokenTo_, _delegations, _useTokenWhitelist); - - // console2.log("2 _delegations[0].caveats[0].args:"); - // console2.logBytes(_delegations[0].caveats[0].args); + _validateTokens(tokenFrom_, tokenTo_, _delegations, _useTokenWhitelist); if (!isAggregatorAllowed[keccak256(abi.encode(aggregatorId_))]) revert AggregatorIdIsNotAllowed(aggregatorId_); if (_delegations[0].delegator != msg.sender) revert NotLeafDelegator(); @@ -404,6 +374,42 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { emit SentTokens(_token, _recipient, _amount); } + /** + * @dev Validates that the tokens are whitelisted or not based on the _useTokenWhitelist flag. + * @dev Adds the argsCheckEnforcer args to later validate if the token whitelist must be have been used or not. + * @param _tokenFrom The input token of the swap. + * @param _tokenTo The output token of the swap. + * @param _delegations The delegation chain; the last delegation must include the ArgsEqualityCheckEnforcer. + * @param _useTokenWhitelist Flag indicating whether token whitelist checks should be enforced. + */ + function _validateTokens( + IERC20 _tokenFrom, + IERC20 _tokenTo, + Delegation[] memory _delegations, + bool _useTokenWhitelist + ) + private + view + { + // The Args Enforcer must be the first caveat in the root delegation + uint256 lastIndex_ = _delegations.length - 1; + if ( + _delegations[lastIndex_].caveats.length == 0 + || _delegations[lastIndex_].caveats[0].enforcer != argsEqualityCheckEnforcer + ) { + revert MissingArgsEqualityCheckEnforcer(); + } + + // The args are set by this contract depending on the useTokenWhitelist flag + if (_useTokenWhitelist) { + if (!isTokenAllowed[_tokenFrom]) revert TokenFromIsNotAllowed(_tokenFrom); + if (!isTokenAllowed[_tokenTo]) revert TokenToIsNotAllowed(_tokenTo); + _delegations[lastIndex_].caveats[0].args = abi.encode("Enforce Token Whitelist"); + } else { + _delegations[lastIndex_].caveats[0].args = abi.encode("Skip Token Whitelist"); + } + } + /** * @dev Internal helper to decode aggregator data from `apiData`. * @param _apiData Bytes that includes aggregatorId, tokenFrom, amountFrom, and the aggregator swap data. diff --git a/test/helpers/DelegationMetaSwapAdapter.t.sol b/test/helpers/DelegationMetaSwapAdapter.t.sol index bb2ce16f..8c6e5115 100644 --- a/test/helpers/DelegationMetaSwapAdapter.t.sol +++ b/test/helpers/DelegationMetaSwapAdapter.t.sol @@ -59,6 +59,8 @@ abstract contract DelegationMetaSwapAdapterBaseTest is BaseTest { RedeemerEnforcer public redeemerEnforcer; bytes public swapDataTokenAtoTokenB; + bytes public argsEqualityEnforcerTerms = abi.encode("Enforce Token Whitelist"); + //////////////////////// Constructor & Setup //////////////////////// constructor() { @@ -145,10 +147,12 @@ abstract contract DelegationMetaSwapAdapterBaseTest is BaseTest { } function _getCaveatsVaultDelegationNativeToken() private view returns (Caveat[] memory) { - Caveat[] memory caveats_ = new Caveat[](2); - caveats_[0] = Caveat({ args: hex"", enforcer: address(valueLteEnforcer), terms: abi.encode(uint256(10 ether)) }); + Caveat[] memory caveats_ = new Caveat[](3); + caveats_[0] = Caveat({ args: hex"", enforcer: address(argsEqualityCheckEnforcer), terms: argsEqualityEnforcerTerms }); + + caveats_[1] = Caveat({ args: hex"", enforcer: address(valueLteEnforcer), terms: abi.encode(uint256(10 ether)) }); - caveats_[1] = Caveat({ + caveats_[2] = Caveat({ args: hex"", enforcer: address(redeemerEnforcer), terms: abi.encodePacked(address(delegationMetaSwapAdapter)) @@ -157,19 +161,21 @@ abstract contract DelegationMetaSwapAdapterBaseTest is BaseTest { } function _getCaveatsVaultDelegationErc20() private view returns (Caveat[] memory) { - Caveat[] memory caveats_ = new Caveat[](4); - caveats_[0] = Caveat({ args: hex"", enforcer: address(allowedTargetsEnforcer), terms: abi.encodePacked(address(tokenA)) }); + Caveat[] memory caveats_ = new Caveat[](5); + caveats_[0] = Caveat({ args: hex"", enforcer: address(argsEqualityCheckEnforcer), terms: argsEqualityEnforcerTerms }); + + caveats_[1] = Caveat({ args: hex"", enforcer: address(allowedTargetsEnforcer), terms: abi.encodePacked(address(tokenA)) }); - caveats_[1] = + caveats_[2] = Caveat({ args: hex"", enforcer: address(allowedMethodsEnforcer), terms: abi.encodePacked(IERC20.transfer.selector) }); uint256 paramStart_ = abi.encodeWithSelector(IERC20.transfer.selector).length; address paramValue_ = address(delegationMetaSwapAdapter); // The param start and and param value are packed together, but the param value is not packed. bytes memory inputTerms_ = abi.encodePacked(paramStart_, bytes32(uint256(uint160(paramValue_)))); - caveats_[2] = Caveat({ args: hex"", enforcer: address(allowedCalldataEnforcer), terms: inputTerms_ }); + caveats_[3] = Caveat({ args: hex"", enforcer: address(allowedCalldataEnforcer), terms: inputTerms_ }); - caveats_[3] = Caveat({ + caveats_[4] = Caveat({ args: hex"", enforcer: address(redeemerEnforcer), terms: abi.encodePacked(address(delegationMetaSwapAdapter)) @@ -288,7 +294,7 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapData_); vm.prank(address(subVault.deleGator)); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_); + delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); uint256 vaultTokenAUsed_ = vaultTokenABalanceBefore_ - tokenA.balanceOf(address(vault.deleGator)); uint256 vaultTokenBObtained_ = tokenB.balanceOf(address(vault.deleGator)) - vaultTokenBBalanceBefore_; @@ -321,7 +327,7 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapData_); vm.prank(address(subVault.deleGator)); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_); + delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); // Calculate the change in balances. uint256 vaultEthUsed = vaultEthBalanceBefore - address(vault.deleGator).balance; @@ -355,7 +361,7 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapData_); vm.prank(address(subVault.deleGator)); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_); + delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); // Calculate the change in balances. uint256 vaultTokenAUsed = vaultTokenABalanceBefore - tokenA.balanceOf(address(vault.deleGator)); @@ -364,6 +370,82 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest assertEq(vaultEthObtained, amountTo, "Vault should receive the correct amount of ETH"); } + // When _useTokenWhitelist is false, token whitelist checks are skipped. + // In this test, we first mark tokenA and tokenB as NOT allowed (so they would fail if checked) + // but the swap should succeed when _useTokenWhitelist is false. + function test_canSwapByDelegationsMock_withNoTokenWhitelist() public { + _setUpMockContracts(); + // Update allowed tokens: disable both tokens. + IERC20[] memory tokens_ = new IERC20[](2); + tokens_[0] = IERC20(tokenA); + tokens_[1] = IERC20(tokenB); + bool[] memory statuses_ = new bool[](2); + statuses_[0] = false; + statuses_[1] = false; + vm.prank(owner); + delegationMetaSwapAdapter.updateAllowedTokens(tokens_, statuses_); + assertFalse(delegationMetaSwapAdapter.isTokenAllowed(tokenA), "TokenA should be disabled"); + assertFalse(delegationMetaSwapAdapter.isTokenAllowed(tokenB), "TokenB should be disabled"); + + // Setting the args enforcer terms to skip the token whitelist + argsEqualityEnforcerTerms = abi.encode("Skip Token Whitelist"); + + // Build a valid delegation chain (which includes the argsEqualityCheckEnforcer in the first caveat). + Delegation[] memory delegations_ = new Delegation[](2); + Delegation memory vaultDelegation_ = _getVaultDelegation(); + Delegation memory subVaultDelegation_ = _getSubVaultDelegation(EncoderLib._getDelegationHash(vaultDelegation_)); + delegations_[1] = vaultDelegation_; + delegations_[0] = subVaultDelegation_; + + uint256 vaultTokenABalanceBefore_ = tokenA.balanceOf(address(vault.deleGator)); + uint256 vaultTokenBBalanceBefore_ = tokenB.balanceOf(address(vault.deleGator)); + + bytes memory swapData_ = _encodeSwapData(IERC20(tokenA), IERC20(tokenB), amountFrom, amountTo, hex"", 0, address(0), true); + bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapData_); + + // Call swapByDelegation with _useTokenWhitelist set to false. + // Since whitelist checks are skipped, the swap should proceed even though tokenA and tokenB are not allowed. + vm.prank(address(subVault.deleGator)); + delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, false); + + uint256 vaultTokenAUsed_ = vaultTokenABalanceBefore_ - tokenA.balanceOf(address(vault.deleGator)); + uint256 vaultTokenBObtained_ = tokenB.balanceOf(address(vault.deleGator)) - vaultTokenBBalanceBefore_; + assertEq(vaultTokenAUsed_, amountFrom, "Vault should spend the specified amount of tokenA"); + assertEq(vaultTokenBObtained_, amountTo, "Vault should receive the correct amount of tokenB"); + } + + // The redeemer tries to swapByDelegation passing a flag different from what the delegator indicated + function test_revert_swapByDelegationsMock_withNoTokenWhitelistAndIncorrectArgs() public { + _setUpMockContracts(); + // Update allowed tokens: disable both tokens. + IERC20[] memory tokens_ = new IERC20[](2); + tokens_[0] = IERC20(tokenA); + tokens_[1] = IERC20(tokenB); + bool[] memory statuses_ = new bool[](2); + statuses_[0] = false; + statuses_[1] = false; + vm.prank(owner); + delegationMetaSwapAdapter.updateAllowedTokens(tokens_, statuses_); + + // Build a valid delegation chain (which includes the argsEqualityCheckEnforcer in the first caveat). + // The args indicate to use the token whitelist but the function the flag is set to not use the whitelist + // the difference between the expected and obtained args reverts + Delegation[] memory delegations_ = new Delegation[](2); + Delegation memory vaultDelegation_ = _getVaultDelegation(); + Delegation memory subVaultDelegation_ = _getSubVaultDelegation(EncoderLib._getDelegationHash(vaultDelegation_)); + delegations_[1] = vaultDelegation_; + delegations_[0] = subVaultDelegation_; + + bytes memory swapData_ = _encodeSwapData(IERC20(tokenA), IERC20(tokenB), amountFrom, amountTo, hex"", 0, address(0), true); + bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapData_); + + // Call swapByDelegation with _useTokenWhitelist set to false. + vm.prank(address(subVault.deleGator)); + + vm.expectRevert("ArgsEqualityCheckEnforcer:different-args-and-terms"); + delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, false); + } + /// @notice Verifies that only the current owner can initiate ownership transfer. function test_revert_transferOwnership_ifNotOwner() public { _setUpMockContracts(); @@ -541,18 +623,21 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest Delegation[] memory emptyDelegations_ = new Delegation[](0); vm.prank(address(subVault.deleGator)); vm.expectRevert(DelegationMetaSwapAdapter.InvalidEmptyDelegations.selector); - delegationMetaSwapAdapter.swapByDelegation(apiData_, emptyDelegations_); + delegationMetaSwapAdapter.swapByDelegation(apiData_, emptyDelegations_, true); } // Test that swapByDelegation reverts when called from a non-leaf delegator function test_revert_swapByDelegation_nonLeafDelegator() public { _setUpMockContracts(); bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapDataTokenAtoTokenB); + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = Caveat({ args: hex"", enforcer: address(argsEqualityCheckEnforcer), terms: argsEqualityEnforcerTerms }); + Delegation memory delegation_ = Delegation({ delegate: address(delegationMetaSwapAdapter), delegator: address(vault.deleGator), authority: ROOT_AUTHORITY, - caveats: new Caveat[](0), + caveats: caveats_, salt: 0, signature: hex"" }); @@ -563,7 +648,7 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest // Using invalid caller, must be the vault not subVault vm.prank(address(subVault.deleGator)); vm.expectRevert(DelegationMetaSwapAdapter.NotLeafDelegator.selector); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_); + delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); } // Test that swapByDelegation reverts if tokenFrom equals tokenTo. @@ -577,7 +662,7 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest delegations_[0] = _getVaultDelegation(); vm.prank(address(subVault.deleGator)); vm.expectRevert(DelegationMetaSwapAdapter.InvalidIdenticalTokens.selector); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_); + delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); } // Test that swapByDelegation reverts if tokenFrom is not allowed. @@ -597,7 +682,7 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest vm.prank(address(subVault.deleGator)); vm.expectRevert(abi.encodeWithSelector(DelegationMetaSwapAdapter.TokenFromIsNotAllowed.selector, tokenA)); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_); + delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); } // Test that swapByDelegation reverts if tokenTo is not allowed. @@ -617,7 +702,7 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest vm.prank(address(subVault.deleGator)); vm.expectRevert(abi.encodeWithSelector(DelegationMetaSwapAdapter.TokenToIsNotAllowed.selector, tokenB)); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_); + delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); } // Test that swapByDelegation reverts if the aggregator ID is not allowed. @@ -637,7 +722,7 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest vm.prank(address(subVault.deleGator)); vm.expectRevert(abi.encodeWithSelector(DelegationMetaSwapAdapter.AggregatorIdIsNotAllowed.selector, aggregatorId)); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_); + delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); } // Test that swapTokens reverts when insufficient tokens are received. @@ -678,6 +763,28 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest ); } + // When the last delegation is missing the argsEqualityCheckEnforcer, + // swapByDelegation should revert with MissingArgsEqualityCheckEnforcer. + function test_revert_swapByDelegation_missingArgsEqualityCheckEnforcer() public { + _setUpMockContracts(); + bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapDataTokenAtoTokenB); + + // Create a vault delegation and remove its caveats (so that the check fails) + Delegation memory badVaultDelegation_ = _getVaultDelegation(); + // Remove caveats so that its length is zero + delete badVaultDelegation_.caveats; + + // Build the delegation chain with the modified vault delegation. + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[1] = badVaultDelegation_; // last (root) delegation + delegations_[0] = _getSubVaultDelegation(EncoderLib._getDelegationHash(badVaultDelegation_)); + + vm.prank(address(subVault.deleGator)); + vm.expectRevert(DelegationMetaSwapAdapter.MissingArgsEqualityCheckEnforcer.selector); + // Call the new version with _useTokenWhitelist (value here is irrelevant) + delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); + } + // Test the withdraw function for an ERC20 token. function test_withdraw() public { _setUpMockContracts(); @@ -852,7 +959,7 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest // Call swapByDelegation from the subVault's perspective vm.prank(address(subVault.deleGator)); vm.expectRevert(DelegationMetaSwapAdapter.InvalidSwapFunctionSelector.selector); - delegationMetaSwapAdapter.swapByDelegation(invalidApiData_, delegations_); + delegationMetaSwapAdapter.swapByDelegation(invalidApiData_, delegations_, true); } function test_revert_swapByDelegation_tokenFromMismatch() public { @@ -866,7 +973,7 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest vm.prank(address(subVault.deleGator)); vm.expectRevert(DelegationMetaSwapAdapter.TokenFromMismath.selector); - delegationMetaSwapAdapter.swapByDelegation(validApiData_, delegations_); + delegationMetaSwapAdapter.swapByDelegation(validApiData_, delegations_, true); } function test_revert_swapByDelegation_amountFromMismatch() public { @@ -904,23 +1011,44 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest vm.prank(address(subVault.deleGator)); vm.expectRevert(DelegationMetaSwapAdapter.AmountFromMismath.selector); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_); + delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); } - // Test that the constructor emits the SetDelegationManager and SetMetaSwap events. - function test_event_constructor_SetDelegationManager_SetMetaSwap() public { + // Test that the constructor emits the constructor events + function test_event_constructor_events() public { // Use dummy addresses for testing. address dummyDelegationManager_ = address(0x123); address dummyMetaSwap_ = address(0x456); + address dummyArgsEqualityCheckEnforcer_ = address(0x456); // Expect the events to be emitted during construction. vm.expectEmit(true, true, false, true); emit DelegationMetaSwapAdapter.SetDelegationManager(IDelegationManager(dummyDelegationManager_)); vm.expectEmit(true, true, false, true); emit DelegationMetaSwapAdapter.SetMetaSwap(IMetaSwap(dummyMetaSwap_)); + vm.expectEmit(true, true, false, true); + emit DelegationMetaSwapAdapter.SetArgsEqualityCheckEnforcer(dummyArgsEqualityCheckEnforcer_); // Deploy a new instance to capture the events. new DelegationMetaSwapAdapter( - owner, IDelegationManager(dummyDelegationManager_), IMetaSwap(dummyMetaSwap_), address(argsEqualityCheckEnforcer) + owner, IDelegationManager(dummyDelegationManager_), IMetaSwap(dummyMetaSwap_), dummyArgsEqualityCheckEnforcer_ + ); + } + + // Test that allowance increases when it is zero. + function test_swapTokens_increasesAllowanceIfNeeded() public { + _setUpMockContracts(); + // Start with zero allowance for tokenA. + vm.prank(address(delegationMetaSwapAdapter)); + tokenA.approve(address(metaSwapMock), 0); + // Mint tokenA to the adapter. + vm.prank(owner); + tokenA.mint(address(delegationMetaSwapAdapter), amountFrom); + // Call swapTokens directly (simulate an internal call by using vm.prank(address(delegationMetaSwapAdapter))). + vm.prank(address(delegationMetaSwapAdapter)); + delegationMetaSwapAdapter.swapTokens( + aggregatorId, tokenA, tokenB, address(vault.deleGator), amountFrom, 0, swapDataTokenAtoTokenB ); + uint256 allowanceAfter_ = tokenA.allowance(address(delegationMetaSwapAdapter), address(metaSwapMock)); + assertEq(allowanceAfter_, type(uint256).max, "Allowance should be increased to max"); } /** @@ -1024,7 +1152,7 @@ contract DelegationMetaSwapAdapterForkTest is DelegationMetaSwapAdapterBaseTest uint256 vaultTokenFromBalanceBefore_ = tokenFrom_.balanceOf(address(vault.deleGator)); uint256 vaultTokenToBalanceBefore_ = tokenTo_.balanceOf(address(vault.deleGator)); vm.prank(address(subVault.deleGator)); - delegationMetaSwapAdapter.swapByDelegation(API_DATA_ERC20_TO_ERC20, delegations_); + delegationMetaSwapAdapter.swapByDelegation(API_DATA_ERC20_TO_ERC20, delegations_, true); uint256 vaultTokenFromUsed_ = vaultTokenFromBalanceBefore_ - tokenFrom_.balanceOf(address(vault.deleGator)); uint256 vaultTokenToObtained_ = tokenTo_.balanceOf(address(vault.deleGator)) - vaultTokenToBalanceBefore_; @@ -1052,7 +1180,7 @@ contract DelegationMetaSwapAdapterForkTest is DelegationMetaSwapAdapterBaseTest uint256 vaultTokenBBalanceBefore = tokenTo_.balanceOf(address(vault.deleGator)); vm.prank(address(subVault.deleGator)); - delegationMetaSwapAdapter.swapByDelegation(API_DATA_NATIVE_TO_ERC20, delegations_); + delegationMetaSwapAdapter.swapByDelegation(API_DATA_NATIVE_TO_ERC20, delegations_, true); // Calculate the change in balances. uint256 vaultEthUsed = vaultEthBalanceBefore - address(vault.deleGator).balance; @@ -1080,7 +1208,7 @@ contract DelegationMetaSwapAdapterForkTest is DelegationMetaSwapAdapterBaseTest uint256 vaultTokenToBalanceBefore_ = address(vault.deleGator).balance; vm.prank(address(subVault.deleGator)); - delegationMetaSwapAdapter.swapByDelegation(API_DATA_ERC20_TO_NATIVE, delegations_); + delegationMetaSwapAdapter.swapByDelegation(API_DATA_ERC20_TO_NATIVE, delegations_, true); uint256 vaultTokenFromUsed_ = vaultTokenFromBalanceBefore_ - tokenFrom_.balanceOf(address(vault.deleGator)); uint256 vaultTokenToObtained_ = address(vault.deleGator).balance - vaultTokenToBalanceBefore_; From 88b9d43a3efb5f54990e4516695f6c204a09a36c Mon Sep 17 00:00:00 2001 From: hanzel98 Date: Thu, 13 Mar 2025 09:14:18 -0600 Subject: [PATCH 3/6] chore: added deployment script --- .env.example | 1 + script/DeployCaveatEnforcers.s.sol | 1 - script/DeployDelegationMetaSwapAdapter.s.sol | 89 +++++++++++--------- 3 files changed, 48 insertions(+), 43 deletions(-) diff --git a/.env.example b/.env.example index baf94d02..cf29eeff 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,7 @@ ENTRYPOINT_ADDRESS=0x0000000071727De22E5E9d8BAf0edAc6f37da032 MULTISIG_DELEGATOR_IMPLEMENTATION_ADDRESS= META_SWAP_ADAPTER_OWNER_ADDRESS= METASWAP_ADDRESS= +ARGS_EQUALITY_CHECK_ENFORCER_ADDRESS= # Required for verifying contracts ETHERSCAN_API_KEY= diff --git a/script/DeployCaveatEnforcers.s.sol b/script/DeployCaveatEnforcers.s.sol index 28c45a3d..9a39fdb5 100644 --- a/script/DeployCaveatEnforcers.s.sol +++ b/script/DeployCaveatEnforcers.s.sol @@ -19,7 +19,6 @@ import { ERC20PeriodTransferEnforcer } from "../src/enforcers/ERC20PeriodTransfe import { ERC721BalanceGteEnforcer } from "../src/enforcers/ERC721BalanceGteEnforcer.sol"; import { ERC721TransferEnforcer } from "../src/enforcers/ERC721TransferEnforcer.sol"; import { ERC1155BalanceGteEnforcer } from "../src/enforcers/ERC1155BalanceGteEnforcer.sol"; -import { ExactCalldataBatchEnforcer } from "../src/enforcers/ExactCalldataBatchEnforcer.sol"; import { ExactCalldataEnforcer } from "../src/enforcers/ExactCalldataEnforcer.sol"; import { ExactExecutionBatchEnforcer } from "../src/enforcers/ExactExecutionBatchEnforcer.sol"; import { ExactExecutionEnforcer } from "../src/enforcers/ExactExecutionEnforcer.sol"; diff --git a/script/DeployDelegationMetaSwapAdapter.s.sol b/script/DeployDelegationMetaSwapAdapter.s.sol index df9d627f..ab23b16b 100644 --- a/script/DeployDelegationMetaSwapAdapter.s.sol +++ b/script/DeployDelegationMetaSwapAdapter.s.sol @@ -1,49 +1,54 @@ -// // SPDX-License-Identifier: MIT AND Apache-2.0 -// pragma solidity 0.8.23; +// 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 "forge-std/Script.sol"; +import { console2 } from "forge-std/console2.sol"; -// import { DelegationMetaSwapAdapter } from "../src/helpers/DelegationMetaSwapAdapter.sol"; -// import { IDelegationManager } from "../src/interfaces/IDelegationManager.sol"; -// import { IMetaSwap } from "../src/helpers/interfaces/IMetaSwap.sol"; +import { DelegationMetaSwapAdapter } from "../src/helpers/DelegationMetaSwapAdapter.sol"; +import { IDelegationManager } from "../src/interfaces/IDelegationManager.sol"; +import { IMetaSwap } from "../src/helpers/interfaces/IMetaSwap.sol"; -// /** -// * @title DeployDelegationMetaSwapAdapter -// * @notice Deploys the delegationMetaSwapAdapter contract. -// * @dev These contracts are likely already deployed on a testnet or mainnet as many are singletons. -// * @dev Fill the required variables in the .env file -// * @dev run the script with: -// * forge script script/DeployDelegationMetaSwapAdapter.s.sol --rpc-url --private-key $PRIVATE_KEY --broadcast -// */ -// contract DeployDelegationMetaSwapAdapter is Script { -// bytes32 salt; -// address deployer; -// address metaSwapAdapterOwner; -// IDelegationManager delegationManager; -// IMetaSwap metaSwap; +/** + * @title DeployDelegationMetaSwapAdapter + * @notice Deploys the delegationMetaSwapAdapter contract. + * @dev These contracts are likely already deployed on a testnet or mainnet as many are singletons. + * @dev Fill the required variables in the .env file + * @dev run the script with: + * forge script script/DeployDelegationMetaSwapAdapter.s.sol --rpc-url --private-key $PRIVATE_KEY --broadcast + */ +contract DeployDelegationMetaSwapAdapter is Script { + bytes32 salt; + address deployer; + address metaSwapAdapterOwner; + IDelegationManager delegationManager; + IMetaSwap metaSwap; + address argsEqualityCheckEnforcer; -// function setUp() public { -// salt = bytes32(abi.encodePacked(vm.envString("SALT"))); -// metaSwapAdapterOwner = vm.envAddress("META_SWAP_ADAPTER_OWNER_ADDRESS"); -// delegationManager = IDelegationManager(vm.envAddress("DELEGATION_MANAGER_ADDRESS")); -// metaSwap = IMetaSwap(vm.envAddress("METASWAP_ADDRESS")); -// deployer = msg.sender; -// console2.log("~~~"); -// console2.log("Deployer: %s", address(deployer)); -// console2.log("DelegationMetaSwapAdapter Owner %s", address(metaSwapAdapterOwner)); -// console2.log("Salt:"); -// console2.logBytes32(salt); -// } + function setUp() public { + salt = bytes32(abi.encodePacked(vm.envString("SALT"))); + metaSwapAdapterOwner = vm.envAddress("META_SWAP_ADAPTER_OWNER_ADDRESS"); + delegationManager = IDelegationManager(vm.envAddress("DELEGATION_MANAGER_ADDRESS")); + metaSwap = IMetaSwap(vm.envAddress("METASWAP_ADDRESS")); + argsEqualityCheckEnforcer = vm.envAddress("ARGS_EQUALITY_CHECK_ENFORCER_ADDRESS"); + deployer = msg.sender; + console2.log("~~~"); + console2.log("Deployer: %s", address(deployer)); + console2.log("DelegationMetaSwapAdapter Owner %s", address(metaSwapAdapterOwner)); + console2.log("Salt:"); + console2.logBytes32(salt); + } -// function run() public { -// console2.log("~~~"); -// vm.startBroadcast(); + function run() public { + console2.log("~~~"); + vm.startBroadcast(); -// address delegationMetaSwapAdapter = -// address(new DelegationMetaSwapAdapter{ salt: salt }(metaSwapAdapterOwner, delegationManager, metaSwap)); -// console2.log("DelegationMetaSwapAdapter: %s", delegationMetaSwapAdapter); + address delegationMetaSwapAdapter = address( + new DelegationMetaSwapAdapter{ salt: salt }( + metaSwapAdapterOwner, delegationManager, metaSwap, argsEqualityCheckEnforcer + ) + ); + console2.log("DelegationMetaSwapAdapter: %s", delegationMetaSwapAdapter); -// vm.stopBroadcast(); -// } -// } + vm.stopBroadcast(); + } +} From f893d1c31cc0552d11f744835586c55135dd10bb Mon Sep 17 00:00:00 2001 From: hanzel98 Date: Thu, 3 Apr 2025 14:30:56 -0600 Subject: [PATCH 4/6] fix: issue in the deployment script --- script/DeployCaveatEnforcers.s.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/script/DeployCaveatEnforcers.s.sol b/script/DeployCaveatEnforcers.s.sol index 9a39fdb5..5acffd7b 100644 --- a/script/DeployCaveatEnforcers.s.sol +++ b/script/DeployCaveatEnforcers.s.sol @@ -102,9 +102,6 @@ contract DeployCaveatEnforcers is Script { deployedAddress = address(new ERC1155BalanceGteEnforcer{ salt: salt }()); console2.log("ERC1155BalanceGteEnforcer: %s", deployedAddress); - deployedAddress = address(new ExactCalldataBatchEnforcer{ salt: salt }()); - console2.log("ExactCalldataBatchEnforcer: %s", deployedAddress); - deployedAddress = address(new ExactCalldataEnforcer{ salt: salt }()); console2.log("ExactCalldataEnforcer: %s", deployedAddress); From 6eef763cc003e15cd3e81b6d4b61ad6b6e1db807 Mon Sep 17 00:00:00 2001 From: hanzel98 Date: Mon, 7 Apr 2025 09:27:48 -0600 Subject: [PATCH 5/6] chore: improved enforce whitelist to const values --- src/helpers/DelegationMetaSwapAdapter.sol | 17 ++++++++++++----- test/helpers/DelegationMetaSwapAdapter.t.sol | 6 ++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/helpers/DelegationMetaSwapAdapter.sol b/src/helpers/DelegationMetaSwapAdapter.sol index b6ef2f74..c03e4052 100644 --- a/src/helpers/DelegationMetaSwapAdapter.sol +++ b/src/helpers/DelegationMetaSwapAdapter.sol @@ -18,7 +18,7 @@ import { CALLTYPE_SINGLE, EXECTYPE_DEFAULT } from "../utils/Constants.sol"; * @notice Acts as a middleman to orchestrate token swaps using delegations and an aggregator (MetaSwap). * @dev This contract depends on an ArgsEqualityCheckEnforcer. The root delegation must include a caveat * with this enforcer as its first element. Its arguments indicate whether the swap should enforce the token - * whitelist ("Enforce Token Whitelist") or skip the whitelist ("Skip Token Whitelist"). The root delegator is + * whitelist ("Token-Whitelist-Enforced") or not ("Token-Whitelist-Not-Enforced"). The root delegator is * responsible for including this enforcer to signal the desired behavior. */ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { @@ -28,6 +28,12 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { ////////////////////////////// State ////////////////////////////// + /// @dev Constant value used to enforce the token whitelist + string public constant WHITELIST_ENFORCED = "Token-Whitelist-Enforced"; + + /// @dev Constant value used to avoid enforcing the token whitelist + string public constant WHITELIST_NOT_ENFORCED = "Token-Whitelist-Not-Enforced"; + /// @dev The DelegationManager contract that has root access to this contract IDelegationManager public immutable delegationManager; @@ -52,7 +58,7 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { event SetMetaSwap(IMetaSwap indexed newMetaSwap); /// @dev Emitted when the Args Equality Check Enforcer contract address is set. - event SetArgsEqualityCheckEnforcer(address newArgsEqualityCheckEnforcer); + event SetArgsEqualityCheckEnforcer(address indexed newArgsEqualityCheckEnforcer); /// @dev Emitted when the contract sends tokens (or native tokens) to a recipient. event SentTokens(IERC20 indexed token, address indexed recipient, uint256 amount); @@ -139,7 +145,8 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { /** * @notice Initializes the DelegationMetaSwapAdapter contract. * @param _owner The initial owner of the contract. - * @param _delegationManager The address of the trusted DelegationManager contract that will have root access to this contract. + * @param _delegationManager The address of the trusted DelegationManager contract has privileged access to call + * executeByExecutor based on a given delegation. * @param _metaSwap The address of the trusted MetaSwap contract. * @param _argsEqualityCheckEnforcer The address of the ArgsEqualityCheckEnforcer contract. */ @@ -404,9 +411,9 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { if (_useTokenWhitelist) { if (!isTokenAllowed[_tokenFrom]) revert TokenFromIsNotAllowed(_tokenFrom); if (!isTokenAllowed[_tokenTo]) revert TokenToIsNotAllowed(_tokenTo); - _delegations[lastIndex_].caveats[0].args = abi.encode("Enforce Token Whitelist"); + _delegations[lastIndex_].caveats[0].args = abi.encode(WHITELIST_ENFORCED); } else { - _delegations[lastIndex_].caveats[0].args = abi.encode("Skip Token Whitelist"); + _delegations[lastIndex_].caveats[0].args = abi.encode(WHITELIST_NOT_ENFORCED); } } diff --git a/test/helpers/DelegationMetaSwapAdapter.t.sol b/test/helpers/DelegationMetaSwapAdapter.t.sol index 8c6e5115..12f499d6 100644 --- a/test/helpers/DelegationMetaSwapAdapter.t.sol +++ b/test/helpers/DelegationMetaSwapAdapter.t.sol @@ -59,7 +59,9 @@ abstract contract DelegationMetaSwapAdapterBaseTest is BaseTest { RedeemerEnforcer public redeemerEnforcer; bytes public swapDataTokenAtoTokenB; - bytes public argsEqualityEnforcerTerms = abi.encode("Enforce Token Whitelist"); + string public constant WHITELIST_ENFORCED = "Token-Whitelist-Enforced"; + string public constant WHITELIST_NOT_ENFORCED = "Token-Whitelist-Not-Enforced"; + bytes public argsEqualityEnforcerTerms = abi.encode(WHITELIST_ENFORCED); //////////////////////// Constructor & Setup //////////////////////// @@ -388,7 +390,7 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest assertFalse(delegationMetaSwapAdapter.isTokenAllowed(tokenB), "TokenB should be disabled"); // Setting the args enforcer terms to skip the token whitelist - argsEqualityEnforcerTerms = abi.encode("Skip Token Whitelist"); + argsEqualityEnforcerTerms = abi.encode(WHITELIST_NOT_ENFORCED); // Build a valid delegation chain (which includes the argsEqualityCheckEnforcer in the first caveat). Delegation[] memory delegations_ = new Delegation[](2); From 73b0e2e1d0ac2c9a38ac590f37e79e3889fcfcfc Mon Sep 17 00:00:00 2001 From: Hanzel Anchia Mena <33629234+hanzel98@users.noreply.github.com> Date: Mon, 7 Apr 2025 16:22:03 -0600 Subject: [PATCH 6/6] Added Swaps ApiData Signature Validation (#86) --- .env.example | 1 + script/DeployDelegationMetaSwapAdapter.s.sol | 4 +- src/helpers/DelegationMetaSwapAdapter.sol | 73 +++++++- test/helpers/DelegationMetaSwapAdapter.t.sol | 187 ++++++++++++++++--- 4 files changed, 235 insertions(+), 30 deletions(-) diff --git a/.env.example b/.env.example index cf29eeff..e2f9f29e 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,7 @@ ENTRYPOINT_ADDRESS=0x0000000071727De22E5E9d8BAf0edAc6f37da032 MULTISIG_DELEGATOR_IMPLEMENTATION_ADDRESS= META_SWAP_ADAPTER_OWNER_ADDRESS= METASWAP_ADDRESS= +SWAPS_API_SIGNER_ADDRESS= ARGS_EQUALITY_CHECK_ENFORCER_ADDRESS= # Required for verifying contracts diff --git a/script/DeployDelegationMetaSwapAdapter.s.sol b/script/DeployDelegationMetaSwapAdapter.s.sol index ab23b16b..41ce08fc 100644 --- a/script/DeployDelegationMetaSwapAdapter.s.sol +++ b/script/DeployDelegationMetaSwapAdapter.s.sol @@ -20,6 +20,7 @@ contract DeployDelegationMetaSwapAdapter is Script { bytes32 salt; address deployer; address metaSwapAdapterOwner; + address swapApiSignerEnforcer; IDelegationManager delegationManager; IMetaSwap metaSwap; address argsEqualityCheckEnforcer; @@ -29,6 +30,7 @@ contract DeployDelegationMetaSwapAdapter is Script { metaSwapAdapterOwner = vm.envAddress("META_SWAP_ADAPTER_OWNER_ADDRESS"); delegationManager = IDelegationManager(vm.envAddress("DELEGATION_MANAGER_ADDRESS")); metaSwap = IMetaSwap(vm.envAddress("METASWAP_ADDRESS")); + swapApiSignerEnforcer = vm.envAddress("SWAPS_API_SIGNER_ADDRESS"); argsEqualityCheckEnforcer = vm.envAddress("ARGS_EQUALITY_CHECK_ENFORCER_ADDRESS"); deployer = msg.sender; console2.log("~~~"); @@ -44,7 +46,7 @@ contract DeployDelegationMetaSwapAdapter is Script { address delegationMetaSwapAdapter = address( new DelegationMetaSwapAdapter{ salt: salt }( - metaSwapAdapterOwner, delegationManager, metaSwap, argsEqualityCheckEnforcer + metaSwapAdapterOwner, swapApiSignerEnforcer, delegationManager, metaSwap, argsEqualityCheckEnforcer ) ); console2.log("DelegationMetaSwapAdapter: %s", delegationMetaSwapAdapter); diff --git a/src/helpers/DelegationMetaSwapAdapter.sol b/src/helpers/DelegationMetaSwapAdapter.sol index c03e4052..d173398c 100644 --- a/src/helpers/DelegationMetaSwapAdapter.sol +++ b/src/helpers/DelegationMetaSwapAdapter.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT AND Apache-2.0 pragma solidity 0.8.23; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { Ownable2Step, Ownable } from "@openzeppelin/contracts/access/Ownable2Step.sol"; @@ -20,12 +22,22 @@ import { CALLTYPE_SINGLE, EXECTYPE_DEFAULT } from "../utils/Constants.sol"; * with this enforcer as its first element. Its arguments indicate whether the swap should enforce the token * whitelist ("Token-Whitelist-Enforced") or not ("Token-Whitelist-Not-Enforced"). The root delegator is * responsible for including this enforcer to signal the desired behavior. + * + * @dev This adapter is intended to be used with the Swaps API. Accordingly, all API requests must include a valid + * signature that incorporates an expiration timestamp. The signature is verified during swap execution to ensure + * that it is still valid. */ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { using ModeLib for ModeCode; using ExecutionLib for bytes; using SafeERC20 for IERC20; + struct SignatureData { + bytes apiData; + uint256 expiration; + bytes signature; + } + ////////////////////////////// State ////////////////////////////// /// @dev Constant value used to enforce the token whitelist @@ -43,6 +55,9 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { /// @dev The enforcer used to compare args and terms address public immutable argsEqualityCheckEnforcer; + /// @dev Address of the API signer account. + address public swapApiSigner; + /// @dev Indicates if a token is allowed to be used in the swaps mapping(IERC20 token => bool allowed) public isTokenAllowed; @@ -69,6 +84,9 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { /// @dev Emitted when the allowed aggregator ID status changes. event ChangedAggregatorIdStatus(bytes32 indexed aggregatorIdHash, string aggregatorId, bool status); + /// @dev Emitted when the Signer API is updated. + event SwapApiSignerUpdated(address indexed newSigner); + ////////////////////////////// Errors ////////////////////////////// /// @dev Error thrown when the caller is not the delegation manager @@ -77,7 +95,7 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { /// @dev Error thrown when the call is not made by this contract itself. error NotSelf(); - /// @dev Error thrown when msg.sender is not the leaf delegatior. + /// @dev Error thrown when msg.sender is not the leaf delegator. error NotLeafDelegator(); /// @dev Error thrown when an execution with an unsupported CallType is made. @@ -122,6 +140,12 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { /// @dev Error when the delegations do not include the ArgsEqualityCheckEnforcer error MissingArgsEqualityCheckEnforcer(); + /// @dev Error thrown when API signature is invalid. + error InvalidApiSignature(); + + /// @dev Error thrown when the signature expiration has passed. + error SignatureExpired(); + ////////////////////////////// Modifiers ////////////////////////////// /** @@ -145,6 +169,7 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { /** * @notice Initializes the DelegationMetaSwapAdapter contract. * @param _owner The initial owner of the contract. + * @param _swapApiSigner The initial swap API signer. * @param _delegationManager The address of the trusted DelegationManager contract has privileged access to call * executeByExecutor based on a given delegation. * @param _metaSwap The address of the trusted MetaSwap contract. @@ -152,15 +177,18 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { */ constructor( address _owner, + address _swapApiSigner, IDelegationManager _delegationManager, IMetaSwap _metaSwap, address _argsEqualityCheckEnforcer ) Ownable(_owner) { + swapApiSigner = _swapApiSigner; delegationManager = _delegationManager; metaSwap = _metaSwap; argsEqualityCheckEnforcer = _argsEqualityCheckEnforcer; + emit SwapApiSignerUpdated(_swapApiSigner); emit SetDelegationManager(_delegationManager); emit SetMetaSwap(_metaSwap); emit SetArgsEqualityCheckEnforcer(_argsEqualityCheckEnforcer); @@ -174,15 +202,27 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { receive() external payable { } /** - * @notice Executes a token swap using a delegation and transfers the swapped tokens to the root delegator. + * @notice Executes a token swap using a delegation and transfers the swapped tokens to the root delegator, after validating + * signature and expiration. * @dev The msg.sender must be the leaf delegator - * @param _apiData Encoded swap parameters, used by the aggregator. + * @param _signatureData Includes: + * - apiData Encoded swap parameters, used by the aggregator. + * - expiration Timestamp after which the signature is invalid. + * - signature Signature validating the provided apiData. * @param _delegations Array of Delegation objects containing delegation-specific data, sorted leaf to root. * @param _useTokenWhitelist Indicates whether the tokens must be validated or not. */ - function swapByDelegation(bytes calldata _apiData, Delegation[] memory _delegations, bool _useTokenWhitelist) external { + function swapByDelegation( + SignatureData calldata _signatureData, + Delegation[] memory _delegations, + bool _useTokenWhitelist + ) + external + { + _validateSignature(_signatureData); + (string memory aggregatorId_, IERC20 tokenFrom_, IERC20 tokenTo_, uint256 amountFrom_, bytes memory swapData_) = - _decodeApiData(_apiData); + _decodeApiData(_signatureData.apiData); uint256 delegationsLength_ = _delegations.length; if (delegationsLength_ == 0) revert InvalidEmptyDelegations(); @@ -276,6 +316,15 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { _sendTokens(_tokenTo, obtainedAmount_, _recipient); } + /** + * @notice Updates the address authorized to sign API requests. + * @param _newSigner The new authorized signer address. + */ + function setSwapApiSigner(address _newSigner) external onlyOwner { + swapApiSigner = _newSigner; + emit SwapApiSignerUpdated(_newSigner); + } + /** * @notice Executes one calls on behalf of this contract, * authorized by the DelegationManager. @@ -469,4 +518,18 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step { return _token.balanceOf(address(this)); } + + /** + * @dev Validates the expiration and signature of the provided apiData. + * @param _signatureData Contains the apiData, the expiration and signature. + */ + function _validateSignature(SignatureData memory _signatureData) private view { + if (block.timestamp > _signatureData.expiration) revert SignatureExpired(); + + bytes32 messageHash_ = keccak256(abi.encodePacked(_signatureData.apiData, _signatureData.expiration)); + bytes32 ethSignedMessageHash_ = MessageHashUtils.toEthSignedMessageHash(messageHash_); + + address recoveredSigner_ = ECDSA.recover(ethSignedMessageHash_, _signatureData.signature); + if (recoveredSigner_ != swapApiSigner) revert InvalidApiSignature(); + } } diff --git a/test/helpers/DelegationMetaSwapAdapter.t.sol b/test/helpers/DelegationMetaSwapAdapter.t.sol index 12f499d6..edbac019 100644 --- a/test/helpers/DelegationMetaSwapAdapter.t.sol +++ b/test/helpers/DelegationMetaSwapAdapter.t.sol @@ -63,6 +63,9 @@ abstract contract DelegationMetaSwapAdapterBaseTest is BaseTest { string public constant WHITELIST_NOT_ENFORCED = "Token-Whitelist-Not-Enforced"; bytes public argsEqualityEnforcerTerms = abi.encode(WHITELIST_ENFORCED); + uint256 public swapSignerPrivateKey; + address public swapApiSignerAddress; + //////////////////////// Constructor & Setup //////////////////////// constructor() { @@ -79,10 +82,31 @@ abstract contract DelegationMetaSwapAdapterBaseTest is BaseTest { valueLteEnforcer = new ValueLteEnforcer(); redeemerEnforcer = new RedeemerEnforcer(); argsEqualityCheckEnforcer = new ArgsEqualityCheckEnforcer(); + + (swapApiSignerAddress, swapSignerPrivateKey) = makeAddrAndKey("SWAP_API"); } //////////////////////// Internal / Private Helpers //////////////////////// + /** + * @dev Generates a valid signature for _apiData with a given _expiration. + */ + function _getValidSignature(bytes memory _apiData, uint256 _expiration) internal returns (bytes memory) { + bytes32 messageHash = keccak256(abi.encodePacked(_apiData, _expiration)); + bytes32 ethSignedMessageHash = MessageHashUtils.toEthSignedMessageHash(messageHash); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(swapSignerPrivateKey, ethSignedMessageHash); + return abi.encodePacked(r, s, v); + } + + /** + * @dev Builds and returns a SignatureData struct from the given apiData. + */ + function _buildSigData(bytes memory apiData) internal returns (DelegationMetaSwapAdapter.SignatureData memory) { + uint256 expiration = block.timestamp + 1000; + bytes memory signature = _getValidSignature(apiData, expiration); + return DelegationMetaSwapAdapter.SignatureData({ apiData: apiData, expiration: expiration, signature: signature }); + } + /** * @dev Internal helper to decode aggregator data from `apiData`. * Typically used in fork-based tests. @@ -294,9 +318,10 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest bytes memory swapData_ = _encodeSwapData(IERC20(tokenA), IERC20(tokenB), amountFrom, amountTo, hex"", 0, address(0), false); bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapData_); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); vm.prank(address(subVault.deleGator)); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); uint256 vaultTokenAUsed_ = vaultTokenABalanceBefore_ - tokenA.balanceOf(address(vault.deleGator)); uint256 vaultTokenBObtained_ = tokenB.balanceOf(address(vault.deleGator)) - vaultTokenBBalanceBefore_; @@ -327,9 +352,10 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest // Prepare the swapData – note that tokenA is ETH (address(0)) bytes memory swapData_ = _encodeSwapData(IERC20(tokenA), IERC20(tokenB), amountFrom, amountTo, hex"", 0, address(0), true); bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapData_); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); vm.prank(address(subVault.deleGator)); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); // Calculate the change in balances. uint256 vaultEthUsed = vaultEthBalanceBefore - address(vault.deleGator).balance; @@ -361,9 +387,10 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest // Prepare the swapData – tokenTo is now ETH (address(0)). Note that _feeTo is false. bytes memory swapData_ = _encodeSwapData(IERC20(tokenA), IERC20(tokenB), amountFrom, amountTo, hex"", 0, address(0), false); bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapData_); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); vm.prank(address(subVault.deleGator)); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); // Calculate the change in balances. uint256 vaultTokenAUsed = vaultTokenABalanceBefore - tokenA.balanceOf(address(vault.deleGator)); @@ -407,8 +434,9 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest // Call swapByDelegation with _useTokenWhitelist set to false. // Since whitelist checks are skipped, the swap should proceed even though tokenA and tokenB are not allowed. + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); vm.prank(address(subVault.deleGator)); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, false); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, false); uint256 vaultTokenAUsed_ = vaultTokenABalanceBefore_ - tokenA.balanceOf(address(vault.deleGator)); uint256 vaultTokenBObtained_ = tokenB.balanceOf(address(vault.deleGator)) - vaultTokenBBalanceBefore_; @@ -440,12 +468,13 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest bytes memory swapData_ = _encodeSwapData(IERC20(tokenA), IERC20(tokenB), amountFrom, amountTo, hex"", 0, address(0), true); bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapData_); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); // Call swapByDelegation with _useTokenWhitelist set to false. vm.prank(address(subVault.deleGator)); vm.expectRevert("ArgsEqualityCheckEnforcer:different-args-and-terms"); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, false); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, false); } /// @notice Verifies that only the current owner can initiate ownership transfer. @@ -623,9 +652,11 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest _setUpMockContracts(); bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapDataTokenAtoTokenB); Delegation[] memory emptyDelegations_ = new Delegation[](0); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); + vm.prank(address(subVault.deleGator)); vm.expectRevert(DelegationMetaSwapAdapter.InvalidEmptyDelegations.selector); - delegationMetaSwapAdapter.swapByDelegation(apiData_, emptyDelegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, emptyDelegations_, true); } // Test that swapByDelegation reverts when called from a non-leaf delegator @@ -646,11 +677,12 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest delegation_ = signDelegation(vault, delegation_); Delegation[] memory delegations_ = new Delegation[](1); delegations_[0] = delegation_; + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); // Using invalid caller, must be the vault not subVault vm.prank(address(subVault.deleGator)); vm.expectRevert(DelegationMetaSwapAdapter.NotLeafDelegator.selector); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); } // Test that swapByDelegation reverts if tokenFrom equals tokenTo. @@ -662,9 +694,11 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapDataIdentical_); Delegation[] memory delegations_ = new Delegation[](1); delegations_[0] = _getVaultDelegation(); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); + vm.prank(address(subVault.deleGator)); vm.expectRevert(DelegationMetaSwapAdapter.InvalidIdenticalTokens.selector); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); } // Test that swapByDelegation reverts if tokenFrom is not allowed. @@ -682,9 +716,11 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest Delegation[] memory delegations_ = new Delegation[](1); delegations_[0] = _getVaultDelegation(); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); + vm.prank(address(subVault.deleGator)); vm.expectRevert(abi.encodeWithSelector(DelegationMetaSwapAdapter.TokenFromIsNotAllowed.selector, tokenA)); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); } // Test that swapByDelegation reverts if tokenTo is not allowed. @@ -702,9 +738,11 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest Delegation[] memory delegations_ = new Delegation[](1); delegations_[0] = _getVaultDelegation(); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); + vm.prank(address(subVault.deleGator)); vm.expectRevert(abi.encodeWithSelector(DelegationMetaSwapAdapter.TokenToIsNotAllowed.selector, tokenB)); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); } // Test that swapByDelegation reverts if the aggregator ID is not allowed. @@ -722,9 +760,11 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest Delegation[] memory delegations_ = new Delegation[](1); delegations_[0] = _getVaultDelegation(); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); + vm.prank(address(subVault.deleGator)); vm.expectRevert(abi.encodeWithSelector(DelegationMetaSwapAdapter.AggregatorIdIsNotAllowed.selector, aggregatorId)); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); } // Test that swapTokens reverts when insufficient tokens are received. @@ -781,10 +821,12 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest delegations_[1] = badVaultDelegation_; // last (root) delegation delegations_[0] = _getSubVaultDelegation(EncoderLib._getDelegationHash(badVaultDelegation_)); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); + vm.prank(address(subVault.deleGator)); vm.expectRevert(DelegationMetaSwapAdapter.MissingArgsEqualityCheckEnforcer.selector); // Call the new version with _useTokenWhitelist (value here is irrelevant) - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); } // Test the withdraw function for an ERC20 token. @@ -958,10 +1000,12 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest Delegation[] memory delegations_ = new Delegation[](1); delegations_[0] = _getVaultDelegation(); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(invalidApiData_); + // Call swapByDelegation from the subVault's perspective vm.prank(address(subVault.deleGator)); vm.expectRevert(DelegationMetaSwapAdapter.InvalidSwapFunctionSelector.selector); - delegationMetaSwapAdapter.swapByDelegation(invalidApiData_, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); } function test_revert_swapByDelegation_tokenFromMismatch() public { @@ -972,10 +1016,11 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest Delegation[] memory delegations_ = new Delegation[](1); delegations_[0] = _getVaultDelegation(); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(validApiData_); vm.prank(address(subVault.deleGator)); vm.expectRevert(DelegationMetaSwapAdapter.TokenFromMismath.selector); - delegationMetaSwapAdapter.swapByDelegation(validApiData_, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); } function test_revert_swapByDelegation_amountFromMismatch() public { @@ -1011,27 +1056,48 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest Delegation[] memory delegations_ = new Delegation[](1); delegations_[0] = _getVaultDelegation(); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); + vm.prank(address(subVault.deleGator)); vm.expectRevert(DelegationMetaSwapAdapter.AmountFromMismath.selector); - delegationMetaSwapAdapter.swapByDelegation(apiData_, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); } - // Test that the constructor emits the constructor events + // Test that the constructor emits the constructor events and assigns their values function test_event_constructor_events() public { // Use dummy addresses for testing. + address dummySwapApiSignerAddress_ = address(0x999); address dummyDelegationManager_ = address(0x123); address dummyMetaSwap_ = address(0x456); address dummyArgsEqualityCheckEnforcer_ = address(0x456); + // Expect the events to be emitted during construction. vm.expectEmit(true, true, false, true); + emit DelegationMetaSwapAdapter.SwapApiSignerUpdated(dummySwapApiSignerAddress_); + vm.expectEmit(true, true, false, true); emit DelegationMetaSwapAdapter.SetDelegationManager(IDelegationManager(dummyDelegationManager_)); vm.expectEmit(true, true, false, true); emit DelegationMetaSwapAdapter.SetMetaSwap(IMetaSwap(dummyMetaSwap_)); vm.expectEmit(true, true, false, true); emit DelegationMetaSwapAdapter.SetArgsEqualityCheckEnforcer(dummyArgsEqualityCheckEnforcer_); // Deploy a new instance to capture the events. - new DelegationMetaSwapAdapter( - owner, IDelegationManager(dummyDelegationManager_), IMetaSwap(dummyMetaSwap_), dummyArgsEqualityCheckEnforcer_ + DelegationMetaSwapAdapter adapter_ = new DelegationMetaSwapAdapter( + owner, + dummySwapApiSignerAddress_, + IDelegationManager(dummyDelegationManager_), + IMetaSwap(dummyMetaSwap_), + dummyArgsEqualityCheckEnforcer_ + ); + assertEq(adapter_.owner(), owner, "Constructor did not set owner correctly"); + assertEq( + address(adapter_.delegationManager()), dummyDelegationManager_, "Constructor did not set delegationManager correctly" + ); + assertEq(address(adapter_.swapApiSigner()), dummySwapApiSignerAddress_, "Constructor did not set swapApiSigner correctly"); + assertEq(address(adapter_.metaSwap()), dummyMetaSwap_, "Constructor did not set metaSwap correctly"); + assertEq( + adapter_.argsEqualityCheckEnforcer(), + dummyArgsEqualityCheckEnforcer_, + "Constructor did not set ArgsEqualityCheckEnforcer correctly" ); } @@ -1053,6 +1119,69 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest assertEq(allowanceAfter_, type(uint256).max, "Allowance should be increased to max"); } + /// @notice Tests that the owner can update the swap API signer via setSwapApiSigner and that the event is emitted. + function test_setSwapApiSigner_updatesStateAndEmitsEvent() public { + _setUpMockContracts(); + address newSigner_ = makeAddr("NewSwapSigner"); + vm.prank(owner); + vm.expectEmit(true, true, false, true); + emit DelegationMetaSwapAdapter.SwapApiSignerUpdated(newSigner_); + delegationMetaSwapAdapter.setSwapApiSigner(newSigner_); + assertEq(delegationMetaSwapAdapter.swapApiSigner(), newSigner_, "Swap API signer was not updated"); + } + + /// @notice Tests that a non-owner calling setSwapApiSigner reverts. + function test_revert_setSwapApiSigner_ifNotOwner() public { + _setUpMockContracts(); + address newSigner_ = makeAddr("NewSwapSigner"); + vm.prank(address(subVault.deleGator)); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(subVault.deleGator))); + delegationMetaSwapAdapter.setSwapApiSigner(newSigner_); + } + + /// @notice Tests that swapByDelegation reverts with SignatureExpired when the signature expiration has passed. + function test_revert_swapByDelegation_signatureExpired() public { + _setUpMockContracts(); + bytes memory swapData_ = _encodeSwapData(IERC20(tokenA), IERC20(tokenB), amountFrom, amountTo, hex"", 0, address(0), true); + bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapData_); + // Set expiration in the past. + uint256 expiredTime = block.timestamp - 1; + bytes memory signature = _getValidSignature(apiData_, expiredTime); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = + DelegationMetaSwapAdapter.SignatureData({ apiData: apiData_, expiration: expiredTime, signature: signature }); + + Delegation[] memory delegations_ = new Delegation[](2); + Delegation memory vaultDelegation_ = _getVaultDelegation(); + Delegation memory subVaultDelegation_ = _getSubVaultDelegation(EncoderLib._getDelegationHash(vaultDelegation_)); + delegations_[1] = vaultDelegation_; + delegations_[0] = subVaultDelegation_; + + vm.prank(address(subVault.deleGator)); + vm.expectRevert(DelegationMetaSwapAdapter.SignatureExpired.selector); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); + } + + /// @notice Tests that swapByDelegation reverts with InvalidApiSignature when the signature is invalid. + function test_revert_swapByDelegation_invalidApiSignature() public { + _setUpMockContracts(); + bytes memory swapData_ = _encodeSwapData(IERC20(tokenA), IERC20(tokenB), amountFrom, amountTo, hex"", 0, address(0), true); + bytes memory apiData_ = _encodeApiData(aggregatorId, IERC20(tokenA), amountFrom, swapData_); + + // Changing the signer private key so the signer is different + swapSignerPrivateKey = 11111; + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(apiData_); + + Delegation[] memory delegations_ = new Delegation[](2); + Delegation memory vaultDelegation_ = _getVaultDelegation(); + Delegation memory subVaultDelegation_ = _getSubVaultDelegation(EncoderLib._getDelegationHash(vaultDelegation_)); + delegations_[1] = vaultDelegation_; + delegations_[0] = subVaultDelegation_; + + vm.prank(address(subVault.deleGator)); + vm.expectRevert(DelegationMetaSwapAdapter.InvalidApiSignature.selector); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); + } + /** * @dev Deploys and configures a MetaSwapMock that can be used to test the delegationMetaSwapAdapter contract */ @@ -1076,7 +1205,11 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest metaSwapMock = IMetaSwap(address(new MetaSwapMock(IERC20(tokenA), IERC20(tokenB)))); delegationMetaSwapAdapter = new DelegationMetaSwapAdapter( - owner, IDelegationManager(address(delegationManager)), metaSwapMock, address(argsEqualityCheckEnforcer) + owner, + swapApiSignerAddress, + IDelegationManager(address(delegationManager)), + metaSwapMock, + address(argsEqualityCheckEnforcer) ); vm.startPrank(owner); @@ -1153,8 +1286,11 @@ contract DelegationMetaSwapAdapterForkTest is DelegationMetaSwapAdapterBaseTest uint256 vaultTokenFromBalanceBefore_ = tokenFrom_.balanceOf(address(vault.deleGator)); uint256 vaultTokenToBalanceBefore_ = tokenTo_.balanceOf(address(vault.deleGator)); + + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(API_DATA_ERC20_TO_ERC20); + vm.prank(address(subVault.deleGator)); - delegationMetaSwapAdapter.swapByDelegation(API_DATA_ERC20_TO_ERC20, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); uint256 vaultTokenFromUsed_ = vaultTokenFromBalanceBefore_ - tokenFrom_.balanceOf(address(vault.deleGator)); uint256 vaultTokenToObtained_ = tokenTo_.balanceOf(address(vault.deleGator)) - vaultTokenToBalanceBefore_; @@ -1180,9 +1316,10 @@ contract DelegationMetaSwapAdapterForkTest is DelegationMetaSwapAdapterBaseTest // Record vault's ETH and tokenB balances before swap. uint256 vaultEthBalanceBefore = address(vault.deleGator).balance; uint256 vaultTokenBBalanceBefore = tokenTo_.balanceOf(address(vault.deleGator)); + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(API_DATA_NATIVE_TO_ERC20); vm.prank(address(subVault.deleGator)); - delegationMetaSwapAdapter.swapByDelegation(API_DATA_NATIVE_TO_ERC20, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); // Calculate the change in balances. uint256 vaultEthUsed = vaultEthBalanceBefore - address(vault.deleGator).balance; @@ -1208,9 +1345,10 @@ contract DelegationMetaSwapAdapterForkTest is DelegationMetaSwapAdapterBaseTest uint256 vaultTokenFromBalanceBefore_ = tokenFrom_.balanceOf(address(vault.deleGator)); uint256 vaultTokenToBalanceBefore_ = address(vault.deleGator).balance; + DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData(API_DATA_ERC20_TO_NATIVE); vm.prank(address(subVault.deleGator)); - delegationMetaSwapAdapter.swapByDelegation(API_DATA_ERC20_TO_NATIVE, delegations_, true); + delegationMetaSwapAdapter.swapByDelegation(sigData_, delegations_, true); uint256 vaultTokenFromUsed_ = vaultTokenFromBalanceBefore_ - tokenFrom_.balanceOf(address(vault.deleGator)); uint256 vaultTokenToObtained_ = address(vault.deleGator).balance - vaultTokenToBalanceBefore_; @@ -1234,8 +1372,9 @@ contract DelegationMetaSwapAdapterForkTest is DelegationMetaSwapAdapterBaseTest { // Overriding values entryPoint = ENTRY_POINT_FORK; - delegationMetaSwapAdapter = - new DelegationMetaSwapAdapter(owner, DELEGATION_MANAGER_FORK, META_SWAP_FORK, address(argsEqualityCheckEnforcer)); + delegationMetaSwapAdapter = new DelegationMetaSwapAdapter( + owner, swapApiSignerAddress, DELEGATION_MANAGER_FORK, META_SWAP_FORK, address(argsEqualityCheckEnforcer) + ); delegationManager = DelegationManager(address(DELEGATION_MANAGER_FORK)); hybridDeleGatorImpl = HYBRID_DELEGATOR_IMPL_FORK;