From fe3b26a5cb6ec76592d40f67e38d9c615e1be5c3 Mon Sep 17 00:00:00 2001 From: Chef Snoopy Date: Fri, 27 Mar 2026 15:55:01 +0800 Subject: [PATCH] feat: Add SwapRouter02ExecutorMultiRefactor --- ...e-DutchOrder-BaseExecuteSingleWithFee.snap | 2 +- .../Base-DutchOrder-ExecuteBatch.snap | 2 +- ...utchOrder-ExecuteBatchMultipleOutputs.snap | 2 +- ...teBatchMultipleOutputsDifferentTokens.snap | 2 +- ...e-DutchOrder-ExecuteBatchNativeOutput.snap | 2 +- .../Base-DutchOrder-ExecuteSingle.snap | 2 +- ...-DutchOrder-ExecuteSingleNativeOutput.snap | 2 +- ...se-DutchOrder-ExecuteSingleValidation.snap | 2 +- ...veDutchOrder-BaseExecuteSingleWithFee.snap | 2 +- ...Base-ExclusiveDutchOrder-ExecuteBatch.snap | 2 +- ...utchOrder-ExecuteBatchMultipleOutputs.snap | 2 +- ...teBatchMultipleOutputsDifferentTokens.snap | 2 +- ...veDutchOrder-ExecuteBatchNativeOutput.snap | 2 +- ...ase-ExclusiveDutchOrder-ExecuteSingle.snap | 2 +- ...eDutchOrder-ExecuteSingleNativeOutput.snap | 2 +- ...iveDutchOrder-ExecuteSingleValidation.snap | 2 +- ...OrderReactor-BaseExecuteSingleWithFee.snap | 2 +- .../Base-LimitOrderReactor-ExecuteBatch.snap | 2 +- ...erReactor-ExecuteBatchMultipleOutputs.snap | 2 +- ...teBatchMultipleOutputsDifferentTokens.snap | 2 +- ...OrderReactor-ExecuteBatchNativeOutput.snap | 2 +- .../Base-LimitOrderReactor-ExecuteSingle.snap | 2 +- ...rderReactor-ExecuteSingleNativeOutput.snap | 2 +- ...tOrderReactor-ExecuteSingleValidation.snap | 2 +- .../DirectFillerFillMacroSingleOrder.snap | 2 +- ...rectFillerFillMacroSingleOrderWithFee.snap | 2 +- .../DirectFillerFillMacroTestEth1Output.snap | 2 +- .../DirectFillerFillMacroTestEth2Outputs.snap | 2 +- ...ectFillerFillMacroThreeOrdersWithFees.snap | 2 +- .../DirectFillerFillMacroTwoOrders.snap | 2 +- ...tputTest3OrdersWithEthAndERC20Outputs.snap | 2 +- .forge-snapshots/EthOutputTestEthOutput.snap | 2 +- ...omparisonTest-InterfaceAndProtocolFee.snap | 2 +- ...Test-InterfaceAndProtocolFeeEthOutput.snap | 2 +- ...colFeesGasComparisonTest-InterfaceFee.snap | 2 +- ...sComparisonTest-InterfaceFeeEthOutput.snap | 2 +- .../ProtocolFeesGasComparisonTest-NoFees.snap | 2 +- ...FeesGasComparisonTest-NoFeesEthOutput.snap | 2 +- .../SwapRouter02ExecutorExecute.snap | 2 +- ...outer02ExecutorExecuteAlreadyApproved.snap | 2 +- .../testExclusiveFillerSucceeds.snap | 2 +- foundry.toml | 2 +- .../SwapRouter02ExecutorMultiRefactor.sol | 165 +++++++++++++ .../SwapRouter02ExecutorMultiRefactor.t.sol | 230 ++++++++++++++++++ test/util/DeployHHPermit2.sol | 2 +- 45 files changed, 438 insertions(+), 43 deletions(-) create mode 100644 src/sample-executors/SwapRouter02ExecutorMultiRefactor.sol create mode 100644 test/executors/SwapRouter02ExecutorMultiRefactor.t.sol diff --git a/.forge-snapshots/Base-DutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-DutchOrder-BaseExecuteSingleWithFee.snap index 1715399..a833e36 100644 --- a/.forge-snapshots/Base-DutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-DutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -227437 \ No newline at end of file +227406 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteBatch.snap b/.forge-snapshots/Base-DutchOrder-ExecuteBatch.snap index 752f93f..99a2b7a 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -222646 \ No newline at end of file +222594 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputs.snap index 1de862c..01d8202 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -233204 \ No newline at end of file +233154 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap index d5d5649..a1c686e 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -290148 \ No newline at end of file +290100 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-DutchOrder-ExecuteBatchNativeOutput.snap index 49002c8..23cf495 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -213248 \ No newline at end of file +213192 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteSingle.snap b/.forge-snapshots/Base-DutchOrder-ExecuteSingle.snap index 0e2da46..d4a2f61 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -185136 \ No newline at end of file +185110 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-DutchOrder-ExecuteSingleNativeOutput.snap index bb460a3..ff6cebe 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -166195 \ No newline at end of file +166167 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteSingleValidation.snap b/.forge-snapshots/Base-DutchOrder-ExecuteSingleValidation.snap index 5305530..9150571 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -194387 \ No newline at end of file +194362 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap index bba2a02..e37499e 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -228115 \ No newline at end of file +228084 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap index 55a493f..897648f 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -223968 \ No newline at end of file +223916 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap index 7336c85..ea428e4 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -234545 \ No newline at end of file +234495 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap index e2b855f..c43f740 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -291515 \ No newline at end of file +291467 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap index 675a2c5..6e37def 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -214576 \ No newline at end of file +214520 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap index 5d1c9c4..d7ffd2c 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -185813 \ No newline at end of file +185787 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap index aa41697..03a6df5 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -166877 \ No newline at end of file +166849 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap index 1f08717..be1636d 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -195068 \ No newline at end of file +195043 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-LimitOrderReactor-BaseExecuteSingleWithFee.snap index 234988e..8a3eeb1 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -224455 \ No newline at end of file +224424 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatch.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatch.snap index b586102..8d1fde1 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatch.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatch.snap @@ -1 +1 @@ -226171 \ No newline at end of file +226119 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputs.snap index 7887c9c..bc8aaaf 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -235672 \ No newline at end of file +235622 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputsDifferentTokens.snap index 832bb1c..49de256 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -291580 \ No newline at end of file +291532 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchNativeOutput.snap index 67608a9..a78f20b 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -211979 \ No newline at end of file +211923 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingle.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingle.snap index f7f9769..1db2864 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingle.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingle.snap @@ -1 +1 @@ -182155 \ No newline at end of file +182129 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleNativeOutput.snap index 2f217e3..b18e43a 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -163202 \ No newline at end of file +163174 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleValidation.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleValidation.snap index bfa0354..81b2bdf 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleValidation.snap @@ -1 +1 @@ -191393 \ No newline at end of file +191368 \ No newline at end of file diff --git a/.forge-snapshots/DirectFillerFillMacroSingleOrder.snap b/.forge-snapshots/DirectFillerFillMacroSingleOrder.snap index 0f20b01..a25efc6 100644 --- a/.forge-snapshots/DirectFillerFillMacroSingleOrder.snap +++ b/.forge-snapshots/DirectFillerFillMacroSingleOrder.snap @@ -1 +1 @@ -156088 \ No newline at end of file +156058 \ No newline at end of file diff --git a/.forge-snapshots/DirectFillerFillMacroSingleOrderWithFee.snap b/.forge-snapshots/DirectFillerFillMacroSingleOrderWithFee.snap index 2956aee..e75defa 100644 --- a/.forge-snapshots/DirectFillerFillMacroSingleOrderWithFee.snap +++ b/.forge-snapshots/DirectFillerFillMacroSingleOrderWithFee.snap @@ -1 +1 @@ -201739 \ No newline at end of file +201704 \ No newline at end of file diff --git a/.forge-snapshots/DirectFillerFillMacroTestEth1Output.snap b/.forge-snapshots/DirectFillerFillMacroTestEth1Output.snap index 08f5629..0efe174 100644 --- a/.forge-snapshots/DirectFillerFillMacroTestEth1Output.snap +++ b/.forge-snapshots/DirectFillerFillMacroTestEth1Output.snap @@ -1 +1 @@ -164539 \ No newline at end of file +164508 \ No newline at end of file diff --git a/.forge-snapshots/DirectFillerFillMacroTestEth2Outputs.snap b/.forge-snapshots/DirectFillerFillMacroTestEth2Outputs.snap index c576f53..8952332 100644 --- a/.forge-snapshots/DirectFillerFillMacroTestEth2Outputs.snap +++ b/.forge-snapshots/DirectFillerFillMacroTestEth2Outputs.snap @@ -1 +1 @@ -192352 \ No newline at end of file +192288 \ No newline at end of file diff --git a/.forge-snapshots/DirectFillerFillMacroThreeOrdersWithFees.snap b/.forge-snapshots/DirectFillerFillMacroThreeOrdersWithFees.snap index c1318cf..e437163 100644 --- a/.forge-snapshots/DirectFillerFillMacroThreeOrdersWithFees.snap +++ b/.forge-snapshots/DirectFillerFillMacroThreeOrdersWithFees.snap @@ -1 +1 @@ -481843 \ No newline at end of file +481734 \ No newline at end of file diff --git a/.forge-snapshots/DirectFillerFillMacroTwoOrders.snap b/.forge-snapshots/DirectFillerFillMacroTwoOrders.snap index 7ba04d3..905df81 100644 --- a/.forge-snapshots/DirectFillerFillMacroTwoOrders.snap +++ b/.forge-snapshots/DirectFillerFillMacroTwoOrders.snap @@ -1 +1 @@ -286504 \ No newline at end of file +286443 \ No newline at end of file diff --git a/.forge-snapshots/EthOutputTest3OrdersWithEthAndERC20Outputs.snap b/.forge-snapshots/EthOutputTest3OrdersWithEthAndERC20Outputs.snap index ed26747..4dbc2fd 100644 --- a/.forge-snapshots/EthOutputTest3OrdersWithEthAndERC20Outputs.snap +++ b/.forge-snapshots/EthOutputTest3OrdersWithEthAndERC20Outputs.snap @@ -1 +1 @@ -387763 \ No newline at end of file +387680 \ No newline at end of file diff --git a/.forge-snapshots/EthOutputTestEthOutput.snap b/.forge-snapshots/EthOutputTestEthOutput.snap index 8b77e07..ea293ab 100644 --- a/.forge-snapshots/EthOutputTestEthOutput.snap +++ b/.forge-snapshots/EthOutputTestEthOutput.snap @@ -1 +1 @@ -172864 \ No newline at end of file +172838 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap index fe2e5ca..f007078 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap @@ -1 +1 @@ -206304 \ No newline at end of file +206277 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap index 56c0eef..968617a 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap @@ -1 +1 @@ -184591 \ No newline at end of file +184558 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap index 86537ea..609a919 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap @@ -1 +1 @@ -191078 \ No newline at end of file +191056 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap index a707cdf..8eaaeb7 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap @@ -1 +1 @@ -164175 \ No newline at end of file +164149 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap index 80ad019..3a69181 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap @@ -1 +1 @@ -169007 \ No newline at end of file +168983 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap index 35b3816..0ebe4ca 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap @@ -1 +1 @@ -141954 \ No newline at end of file +141928 \ No newline at end of file diff --git a/.forge-snapshots/SwapRouter02ExecutorExecute.snap b/.forge-snapshots/SwapRouter02ExecutorExecute.snap index c55199d..ae53544 100644 --- a/.forge-snapshots/SwapRouter02ExecutorExecute.snap +++ b/.forge-snapshots/SwapRouter02ExecutorExecute.snap @@ -1 +1 @@ -249557 \ No newline at end of file +249540 \ No newline at end of file diff --git a/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap b/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap index a063e04..59592ef 100644 --- a/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap +++ b/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap @@ -1 +1 @@ -156771 \ No newline at end of file +156754 \ No newline at end of file diff --git a/.forge-snapshots/testExclusiveFillerSucceeds.snap b/.forge-snapshots/testExclusiveFillerSucceeds.snap index dc97639..69a2f3d 100644 --- a/.forge-snapshots/testExclusiveFillerSucceeds.snap +++ b/.forge-snapshots/testExclusiveFillerSucceeds.snap @@ -1 +1 @@ -195398 \ No newline at end of file +195377 \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 8abe3ab..ba6c891 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -#solc = "0.8.20" +solc = "0.8.26" auto_detect_solc = true src = 'src' out = 'out' diff --git a/src/sample-executors/SwapRouter02ExecutorMultiRefactor.sol b/src/sample-executors/SwapRouter02ExecutorMultiRefactor.sol new file mode 100644 index 0000000..a5c2b42 --- /dev/null +++ b/src/sample-executors/SwapRouter02ExecutorMultiRefactor.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.26; + +import {Owned} from "solmate/src/auth/Owned.sol"; +import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; +import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {WETH} from "solmate/src/tokens/WETH.sol"; +import {IReactorCallback} from "../interfaces/IReactorCallback.sol"; +import {IReactor} from "../interfaces/IReactor.sol"; +import {CurrencyLibrary} from "../lib/CurrencyLibrary.sol"; +import {ResolvedOrder, SignedOrder} from "../base/ReactorStructs.sol"; +import {ISwapRouter02} from "../external/ISwapRouter02.sol"; + +/// @notice A fill contract that uses SwapRouter02 to execute trades; supports multiple whitelisted reactors +contract SwapRouter02ExecutorMultiRefactor is IReactorCallback, Owned { + using SafeTransferLib for ERC20; + using CurrencyLibrary for address; + + /// @notice thrown if reactorCallback is called with a non-whitelisted filler + error CallerNotWhitelisted(); + /// @notice thrown if the reactor is not whitelisted + error ReactorNotWhitelisted(); + /// @notice thrown if callback is not tied to the latest execute/executeBatch transient hash + error InvalidTransientHash(); + + ISwapRouter02 private immutable swapRouter02; + address private immutable whitelistedCaller; + WETH private immutable weth; + + /// @dev uint256 internal constant TRANSIENT_CALLBACK_HASH_SLOT = + /// uint256(keccak256("SwapRouter02ExecutorMultiRefactor.transientHash")) - 1; + uint256 internal constant TRANSIENT_CALLBACK_HASH_SLOT = + 0x2f0bac3a76a2fba8f3f4772f250e00386eb67437599f5c1bacd6b53f5ec962e8; + + /// @notice reactors allowed to call reactorCallback and to be used in execute / executeBatch + mapping(address => bool) public reactorWhitelist; + /// @notice Emitted when owner updates reactor whitelist status + event ReactorWhitelisted(address indexed reactor, bool whitelisted); + + modifier onlyWhitelistedCaller() { + if (msg.sender != whitelistedCaller) { + revert CallerNotWhitelisted(); + } + _; + } + + /// @dev reactor must be a whitelisted reactor address + modifier onlyReactor(address reactor) { + if (!reactorWhitelist[reactor]) { + revert ReactorNotWhitelisted(); + } + _; + } + + constructor(address _whitelistedCaller, address _owner, ISwapRouter02 _swapRouter02) Owned(_owner) { + whitelistedCaller = _whitelistedCaller; + swapRouter02 = _swapRouter02; + weth = WETH(payable(_swapRouter02.WETH9())); + } + + /// @notice Add or remove a reactor from the whitelist (only owner) + function setReactorWhitelisted(IReactor reactor, bool whitelisted) external onlyOwner { + reactorWhitelist[address(reactor)] = whitelisted; + emit ReactorWhitelisted(address(reactor), whitelisted); + } + + /// @notice assume that we already have all output tokens + function execute(IReactor reactor, SignedOrder calldata order, bytes calldata callbackData) + external + onlyWhitelistedCaller + onlyReactor(address(reactor)) + { + _saveTransientHash(keccak256(abi.encode(address(reactor), callbackData))); + reactor.executeWithCallback(order, callbackData); + } + + /// @notice assume that we already have all output tokens + function executeBatch(IReactor reactor, SignedOrder[] calldata orders, bytes calldata callbackData) + external + onlyWhitelistedCaller + onlyReactor(address(reactor)) + { + _saveTransientHash(keccak256(abi.encode(address(reactor), callbackData))); + reactor.executeBatchWithCallback(orders, callbackData); + } + + /// @notice fill UniswapX orders using SwapRouter02 + /// @param callbackData It has the below encoded: + /// address[] memory tokensToApproveForSwapRouter02: Max approve these tokens to swapRouter02 + /// address[] memory tokensToApproveForReactor: Max approve these tokens to reactor (msg.sender) + /// bytes[] memory multicallData: Pass into swapRouter02.multicall() + function reactorCallback(ResolvedOrder[] calldata, bytes calldata callbackData) external onlyReactor(msg.sender) { + if (_loadTransientHash() != keccak256(abi.encode(msg.sender, callbackData))) { + revert InvalidTransientHash(); + } + // Clear transient hash before external interactions. + _saveTransientHash(bytes32(0)); + + ( + address[] memory tokensToApproveForSwapRouter02, + address[] memory tokensToApproveForReactor, + bytes[] memory multicallData + ) = abi.decode(callbackData, (address[], address[], bytes[])); + + address reactorAddr = msg.sender; + + unchecked { + for (uint256 i = 0; i < tokensToApproveForSwapRouter02.length; i++) { + ERC20(tokensToApproveForSwapRouter02[i]).safeApprove(address(swapRouter02), type(uint256).max); + } + + for (uint256 i = 0; i < tokensToApproveForReactor.length; i++) { + ERC20(tokensToApproveForReactor[i]).safeApprove(reactorAddr, type(uint256).max); + } + } + + swapRouter02.multicall(type(uint256).max, multicallData); + + // transfer any native balance to the reactor + // it will refund any excess + if (address(this).balance > 0) { + CurrencyLibrary.transferNative(reactorAddr, address(this).balance); + } + } + + /// @notice This function can be used to convert ERC20s to ETH that remains in this contract + /// @param tokensToApprove Max approve these tokens to swapRouter02 + /// @param multicallData Pass into swapRouter02.multicall() + function multicall(ERC20[] calldata tokensToApprove, bytes[] calldata multicallData) external onlyOwner { + for (uint256 i = 0; i < tokensToApprove.length; i++) { + tokensToApprove[i].safeApprove(address(swapRouter02), type(uint256).max); + } + swapRouter02.multicall(type(uint256).max, multicallData); + } + + /// @notice Unwraps the contract's WETH9 balance and sends it to the recipient as ETH. Can only be called by owner. + /// @param recipient The address receiving ETH + function unwrapWETH(address recipient) external onlyOwner { + uint256 balanceWETH = weth.balanceOf(address(this)); + + weth.withdraw(balanceWETH); + SafeTransferLib.safeTransferETH(recipient, address(this).balance); + } + + /// @notice Transfer all ETH in this contract to the recipient. Can only be called by owner. + /// @param recipient The recipient of the ETH + function withdrawETH(address recipient) external onlyOwner { + SafeTransferLib.safeTransferETH(recipient, address(this).balance); + } + + /// @notice Necessary for this contract to receive ETH when calling unwrapWETH() + receive() external payable {} + + function _saveTransientHash(bytes32 hash) internal { + assembly ("memory-safe") { + tstore(TRANSIENT_CALLBACK_HASH_SLOT, hash) + } + } + + function _loadTransientHash() internal view returns (bytes32 hash) { + assembly ("memory-safe") { + hash := tload(TRANSIENT_CALLBACK_HASH_SLOT) + } + } +} diff --git a/test/executors/SwapRouter02ExecutorMultiRefactor.t.sol b/test/executors/SwapRouter02ExecutorMultiRefactor.t.sol new file mode 100644 index 0000000..14ea3bb --- /dev/null +++ b/test/executors/SwapRouter02ExecutorMultiRefactor.t.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.26; + +import {Test} from "forge-std/Test.sol"; +import {SwapRouter02ExecutorMultiRefactor} from "../../src/sample-executors/SwapRouter02ExecutorMultiRefactor.sol"; +import {DutchOrderReactor, DutchOrder, DutchInput} from "../../src/reactors/DutchOrderReactor.sol"; +import {MockERC20} from "../util/mock/MockERC20.sol"; +import {WETH} from "solmate/src/tokens/WETH.sol"; +import {MockSwapRouter} from "../util/mock/MockSwapRouter.sol"; +import {SignedOrder, ResolvedOrder, OutputToken, InputToken, OrderInfo} from "../../src/base/ReactorStructs.sol"; +import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; +import {DeployPermit2} from "../util/DeployPermit2.sol"; +import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol"; +import {OutputsBuilder} from "../util/OutputsBuilder.sol"; +import {PermitSignature} from "../util/PermitSignature.sol"; +import {ISwapRouter02, ExactInputParams} from "../../src/external/ISwapRouter02.sol"; +import {IReactor} from "../../src/interfaces/IReactor.sol"; + +contract SwapRouter02ExecutorMultiRefactorTest is Test, PermitSignature, DeployPermit2 { + using OrderInfoBuilder for OrderInfo; + + uint256 internal fillerPrivateKey; + uint256 internal swapperPrivateKey; + MockERC20 internal tokenIn; + MockERC20 internal tokenOut; + WETH internal weth; + address internal filler; + address internal swapper; + SwapRouter02ExecutorMultiRefactor internal executor; + MockSwapRouter internal mockSwapRouter; + DutchOrderReactor internal reactor1; + DutchOrderReactor internal reactor2; + IPermit2 internal permit2; + + uint256 internal constant ONE = 10 ** 18; + uint24 internal constant FEE = 3000; + address internal constant PROTOCOL_FEE_OWNER = address(80085); + + event ReactorWhitelisted(address indexed reactor, bool whitelisted); + + receive() external payable {} + + function setUp() public { + vm.warp(1000); + vm.chainId(31337); + + tokenIn = new MockERC20("Input", "IN", 18); + tokenOut = new MockERC20("Output", "OUT", 18); + weth = new WETH(); + + fillerPrivateKey = 0x12341234; + filler = vm.addr(fillerPrivateKey); + swapperPrivateKey = 0x12341235; + swapper = vm.addr(swapperPrivateKey); + + mockSwapRouter = new MockSwapRouter(address(weth)); + permit2 = IPermit2(deployPermit2()); + reactor1 = new DutchOrderReactor(permit2, PROTOCOL_FEE_OWNER); + reactor2 = new DutchOrderReactor(permit2, PROTOCOL_FEE_OWNER); + executor = + new SwapRouter02ExecutorMultiRefactor(address(this), address(this), ISwapRouter02(address(mockSwapRouter))); + + tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); + } + + function testSetReactorWhitelistedEmitsEvent() public { + vm.expectEmit(true, false, false, true); + emit ReactorWhitelisted(address(reactor1), true); + executor.setReactorWhitelisted(IReactor(address(reactor1)), true); + + assertTrue(executor.reactorWhitelist(address(reactor1))); + } + + function testExecuteRevertsIfReactorNotWhitelisted() public { + DutchOrder memory order = _buildOrder(address(reactor1), 0, ONE, ONE); + tokenIn.mint(swapper, ONE); + tokenOut.mint(address(mockSwapRouter), ONE); + + bytes memory callbackData = _buildCallbackData(ONE, address(executor)); + + vm.expectRevert(SwapRouter02ExecutorMultiRefactor.ReactorNotWhitelisted.selector); + executor.execute( + IReactor(address(reactor1)), + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), + callbackData + ); + } + + function testExecuteWithWhitelistedReactor() public { + executor.setReactorWhitelisted(IReactor(address(reactor1)), true); + + DutchOrder memory order = DutchOrder({ + info: OrderInfoBuilder.init(address(reactor1)).withSwapper(swapper).withDeadline(block.timestamp + 100), + decayStartTime: block.timestamp - 100, + decayEndTime: block.timestamp + 100, + input: DutchInput(tokenIn, ONE, ONE), + outputs: OutputsBuilder.singleDutch(address(tokenOut), ONE, 0, address(swapper)) + }); + tokenIn.mint(swapper, ONE); + tokenOut.mint(address(mockSwapRouter), ONE); + + bytes memory callbackData = _buildCallbackData(ONE, address(executor)); + + executor.execute( + IReactor(address(reactor1)), + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), + callbackData + ); + + assertEq(tokenIn.balanceOf(swapper), 0); + assertEq(tokenOut.balanceOf(swapper), ONE / 2); + assertEq(tokenOut.balanceOf(address(executor)), ONE / 2); + } + + function testExecuteBatchWithWhitelistedReactor() public { + executor.setReactorWhitelisted(IReactor(address(reactor1)), true); + + tokenIn.mint(swapper, ONE * 10); + tokenOut.mint(address(mockSwapRouter), ONE * 10); + + SignedOrder[] memory signedOrders = new SignedOrder[](2); + DutchOrder memory order1 = _buildOrder(address(reactor1), 0, ONE, ONE); + DutchOrder memory order2 = _buildOrder(address(reactor1), 1, ONE * 3, ONE * 2); + signedOrders[0] = SignedOrder(abi.encode(order1), signOrder(swapperPrivateKey, address(permit2), order1)); + signedOrders[1] = SignedOrder(abi.encode(order2), signOrder(swapperPrivateKey, address(permit2), order2)); + + bytes memory callbackData = _buildCallbackData(ONE * 4, address(executor)); + + executor.executeBatch(IReactor(address(reactor1)), signedOrders, callbackData); + + assertEq(tokenOut.balanceOf(swapper), 3 ether); + assertEq(tokenIn.balanceOf(swapper), 6 ether); + assertEq(tokenOut.balanceOf(address(mockSwapRouter)), 6 ether); + assertEq(tokenIn.balanceOf(address(mockSwapRouter)), 4 ether); + assertEq(tokenOut.balanceOf(address(executor)), ONE); + assertEq(tokenIn.balanceOf(address(executor)), 0); + } + + function testReactorCallbackRevertsIfSenderNotWhitelistedReactor() public { + OutputToken[] memory outputs = new OutputToken[](1); + outputs[0] = OutputToken(address(tokenOut), ONE, swapper); + ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](1); + resolvedOrders[0] = ResolvedOrder( + OrderInfoBuilder.init(address(reactor1)).withSwapper(swapper).withDeadline(block.timestamp + 100), + InputToken(tokenIn, ONE, ONE), + outputs, + hex"1234", + keccak256(abi.encode(1)) + ); + + bytes memory callbackData = _buildCallbackData(ONE, address(executor)); + vm.prank(address(reactor1)); + vm.expectRevert(SwapRouter02ExecutorMultiRefactor.ReactorNotWhitelisted.selector); + executor.reactorCallback(resolvedOrders, callbackData); + } + + function testReactorCallbackRevertsWithoutTransientHash() public { + executor.setReactorWhitelisted(IReactor(address(reactor2)), true); + + OutputToken[] memory outputs = new OutputToken[](1); + outputs[0] = OutputToken(address(tokenOut), ONE, swapper); + ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](1); + resolvedOrders[0] = ResolvedOrder( + OrderInfoBuilder.init(address(reactor2)).withSwapper(swapper).withDeadline(block.timestamp + 100), + InputToken(tokenIn, ONE, ONE), + outputs, + hex"1234", + keccak256(abi.encode(2)) + ); + + tokenIn.mint(address(executor), ONE); + tokenOut.mint(address(mockSwapRouter), ONE); + bytes memory callbackData = _buildCallbackData(ONE, address(executor)); + + vm.prank(address(reactor2)); + vm.expectRevert(SwapRouter02ExecutorMultiRefactor.InvalidTransientHash.selector); + executor.reactorCallback(resolvedOrders, callbackData); + } + + function testNotWhitelistedCaller() public { + executor.setReactorWhitelisted(IReactor(address(reactor1)), true); + + DutchOrder memory order = _buildOrder(address(reactor1), 0, ONE, ONE); + tokenIn.mint(swapper, ONE); + tokenOut.mint(address(mockSwapRouter), ONE); + bytes memory callbackData = _buildCallbackData(ONE, address(executor)); + + vm.prank(address(0xbeef)); + vm.expectRevert(SwapRouter02ExecutorMultiRefactor.CallerNotWhitelisted.selector); + executor.execute( + IReactor(address(reactor1)), + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), + callbackData + ); + } + + function _buildOrder(address reactorAddr, uint256 nonce, uint256 inputAmount, uint256 outputAmount) + internal + view + returns (DutchOrder memory) + { + return DutchOrder({ + info: OrderInfoBuilder.init(reactorAddr).withSwapper(swapper).withDeadline(block.timestamp + 100) + .withNonce(nonce), + decayStartTime: block.timestamp - 100, + decayEndTime: block.timestamp + 100, + input: DutchInput(tokenIn, inputAmount, inputAmount), + outputs: OutputsBuilder.singleDutch(address(tokenOut), outputAmount, outputAmount, swapper) + }); + } + + function _buildCallbackData(uint256 amountIn, address recipient) internal view returns (bytes memory) { + address[] memory tokensToApproveForSwapRouter02 = new address[](1); + tokensToApproveForSwapRouter02[0] = address(tokenIn); + + address[] memory tokensToApproveForReactor = new address[](1); + tokensToApproveForReactor[0] = address(tokenOut); + + bytes[] memory multicallData = new bytes[](1); + ExactInputParams memory exactInputParams = ExactInputParams({ + path: abi.encodePacked(tokenIn, FEE, tokenOut), + recipient: recipient, + amountIn: amountIn, + amountOutMinimum: 0 + }); + multicallData[0] = abi.encodeWithSelector(ISwapRouter02.exactInput.selector, exactInputParams); + + return abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData); + } +} diff --git a/test/util/DeployHHPermit2.sol b/test/util/DeployHHPermit2.sol index 37b8cdb..02de8ee 100644 --- a/test/util/DeployHHPermit2.sol +++ b/test/util/DeployHHPermit2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.20; +pragma solidity ^0.8.20; contract DeployHHPermit2 { address public permit2;