diff --git a/.forge-snapshots/Base-DutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-DutchOrder-BaseExecuteSingleWithFee.snap index 6b1f7466..dac02a99 100644 --- a/.forge-snapshots/Base-DutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-DutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -138311 \ No newline at end of file +150417 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteBatch.snap b/.forge-snapshots/Base-DutchOrder-ExecuteBatch.snap index 92acb20e..333372f2 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -169496 \ No newline at end of file +196879 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputs.snap index ddbd1e18..8e513b1a 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -176377 \ No newline at end of file +206650 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap index eac9a1fc..7db95251 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -207790 \ No newline at end of file +260307 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-DutchOrder-ExecuteBatchNativeOutput.snap index b8586b42..3705f47f 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -175959 \ No newline at end of file +190447 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteSingle.snap b/.forge-snapshots/Base-DutchOrder-ExecuteSingle.snap index 3de8e52d..787d6f15 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -124916 \ No newline at end of file +148194 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-DutchOrder-ExecuteSingleNativeOutput.snap index 3b3a0155..27e5bfc7 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -127164 \ No newline at end of file +133774 \ No newline at end of file diff --git a/.forge-snapshots/Base-DutchOrder-ExecuteSingleValidation.snap b/.forge-snapshots/Base-DutchOrder-ExecuteSingleValidation.snap index ba0c2bd4..d15c3eda 100644 --- a/.forge-snapshots/Base-DutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-DutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -134226 \ No newline at end of file +157505 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap index 53228066..bda45bd8 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -158419 \ No newline at end of file +182186 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap index 6e3ea784..f6f06f29 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatch.snap @@ -1 +1 @@ -169874 \ No newline at end of file +197283 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap index 88740904..9817be4d 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -176749 \ No newline at end of file +207049 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap index d0f94573..cecddc06 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -208184 \ No newline at end of file +260720 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap index c1c256e4..fd57bd6d 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -176329 \ No newline at end of file +190845 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap index ffd29591..2f1f2ead 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingle.snap @@ -1 +1 @@ -125107 \ No newline at end of file +148401 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap index 96ac7318..270a81d9 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -127352 \ No newline at end of file +133986 \ No newline at end of file diff --git a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap index ed31609f..433adecc 100644 --- a/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-ExclusiveDutchOrder-ExecuteSingleValidation.snap @@ -1 +1 @@ -134422 \ No newline at end of file +157716 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-BaseExecuteSingleWithFee.snap b/.forge-snapshots/Base-LimitOrderReactor-BaseExecuteSingleWithFee.snap index d49c15c1..a241e9b7 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-BaseExecuteSingleWithFee.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-BaseExecuteSingleWithFee.snap @@ -1 +1 @@ -123620 \ No newline at end of file +146554 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatch.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatch.snap index 7bca8775..c470e6ed 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatch.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatch.snap @@ -1 +1 @@ -161701 \ No newline at end of file +189056 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputs.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputs.snap index 1ea1ba52..f49195f0 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputs.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputs.snap @@ -1 +1 @@ -167646 \ No newline at end of file +197889 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputsDifferentTokens.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputsDifferentTokens.snap index 2b1f4445..757ce6f7 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputsDifferentTokens.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchMultipleOutputsDifferentTokens.snap @@ -1 +1 @@ -198131 \ No newline at end of file +250617 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchNativeOutput.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchNativeOutput.snap index 0e91fbfe..f6644d41 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchNativeOutput.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteBatchNativeOutput.snap @@ -1 +1 @@ -168164 \ No newline at end of file +182618 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingle.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingle.snap index 6315f547..e0b0c878 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingle.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingle.snap @@ -1 +1 @@ -121070 \ No newline at end of file +124433 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleNativeOutput.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleNativeOutput.snap index 68dd3c0a..70574434 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleNativeOutput.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleNativeOutput.snap @@ -1 +1 @@ -123319 \ No newline at end of file +129913 \ No newline at end of file diff --git a/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleValidation.snap b/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleValidation.snap index f051bcd6..ff76e7f8 100644 --- a/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleValidation.snap +++ b/.forge-snapshots/Base-LimitOrderReactor-ExecuteSingleValidation.snap @@ -1 +1 @@ -130381 \ No newline at end of file +153643 \ No newline at end of file diff --git a/.forge-snapshots/DirectFillerFillMacroSingleOrder.snap b/.forge-snapshots/DirectFillerFillMacroSingleOrder.snap index e3e91ac8..788e6ee9 100644 --- a/.forge-snapshots/DirectFillerFillMacroSingleOrder.snap +++ b/.forge-snapshots/DirectFillerFillMacroSingleOrder.snap @@ -1 +1 @@ -140223 \ No newline at end of file +136213 \ No newline at end of file diff --git a/.forge-snapshots/DirectFillerFillMacroSingleOrderWithFee.snap b/.forge-snapshots/DirectFillerFillMacroSingleOrderWithFee.snap index 3ec9d9a9..a3be623b 100644 --- a/.forge-snapshots/DirectFillerFillMacroSingleOrderWithFee.snap +++ b/.forge-snapshots/DirectFillerFillMacroSingleOrderWithFee.snap @@ -1 +1 @@ -181893 \ No newline at end of file +175064 \ No newline at end of file diff --git a/.forge-snapshots/DirectFillerFillMacroTestEth1Output.snap b/.forge-snapshots/DirectFillerFillMacroTestEth1Output.snap index 65d118c6..1ae0df83 100644 --- a/.forge-snapshots/DirectFillerFillMacroTestEth1Output.snap +++ b/.forge-snapshots/DirectFillerFillMacroTestEth1Output.snap @@ -1 +1 @@ -147989 \ No newline at end of file +147399 \ No newline at end of file diff --git a/.forge-snapshots/DirectFillerFillMacroTestEth2Outputs.snap b/.forge-snapshots/DirectFillerFillMacroTestEth2Outputs.snap index 88608e75..ef0b44cc 100644 --- a/.forge-snapshots/DirectFillerFillMacroTestEth2Outputs.snap +++ b/.forge-snapshots/DirectFillerFillMacroTestEth2Outputs.snap @@ -1 +1 @@ -171264 \ No newline at end of file +170622 \ No newline at end of file diff --git a/.forge-snapshots/DirectFillerFillMacroThreeOrdersWithFees.snap b/.forge-snapshots/DirectFillerFillMacroThreeOrdersWithFees.snap index 13fead7c..f70c13b3 100644 --- a/.forge-snapshots/DirectFillerFillMacroThreeOrdersWithFees.snap +++ b/.forge-snapshots/DirectFillerFillMacroThreeOrdersWithFees.snap @@ -1 +1 @@ -454545 \ No newline at end of file +435187 \ No newline at end of file diff --git a/.forge-snapshots/DirectFillerFillMacroTwoOrders.snap b/.forge-snapshots/DirectFillerFillMacroTwoOrders.snap index 788d9a5a..e757a1e3 100644 --- a/.forge-snapshots/DirectFillerFillMacroTwoOrders.snap +++ b/.forge-snapshots/DirectFillerFillMacroTwoOrders.snap @@ -1 +1 @@ -264858 \ No newline at end of file +255972 \ No newline at end of file diff --git a/.forge-snapshots/EthOutputTest3OrdersWithEthAndERC20Outputs.snap b/.forge-snapshots/EthOutputTest3OrdersWithEthAndERC20Outputs.snap index 910e0453..065c5f54 100644 --- a/.forge-snapshots/EthOutputTest3OrdersWithEthAndERC20Outputs.snap +++ b/.forge-snapshots/EthOutputTest3OrdersWithEthAndERC20Outputs.snap @@ -1 +1 @@ -326649 \ No newline at end of file +363514 \ No newline at end of file diff --git a/.forge-snapshots/EthOutputTestEthOutput.snap b/.forge-snapshots/EthOutputTestEthOutput.snap index 0359a37d..7aa60b92 100644 --- a/.forge-snapshots/EthOutputTestEthOutput.snap +++ b/.forge-snapshots/EthOutputTestEthOutput.snap @@ -1 +1 @@ -149627 \ No newline at end of file +156241 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap index 48e32586..e88d7024 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee.snap @@ -1 +1 @@ -152826 \ No newline at end of file +176915 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap index bc50d75c..857c8ba9 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput.snap @@ -1 +1 @@ -144858 \ No newline at end of file +162961 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap index 563bf60d..a07a3f15 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFee.snap @@ -1 +1 @@ -142255 \ No newline at end of file +166046 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap index 22302608..9b951ac5 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput.snap @@ -1 +1 @@ -134441 \ No newline at end of file +146909 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap index e20b98c8..b769768a 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFees.snap @@ -1 +1 @@ -125972 \ No newline at end of file +149267 \ No newline at end of file diff --git a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap index b75bb8c7..d37ab675 100644 --- a/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap +++ b/.forge-snapshots/ProtocolFeesGasComparisonTest-NoFeesEthOutput.snap @@ -1 +1 @@ -118312 \ No newline at end of file +124947 \ No newline at end of file diff --git a/.forge-snapshots/SwapRouter02ExecutorExecute.snap b/.forge-snapshots/SwapRouter02ExecutorExecute.snap new file mode 100644 index 00000000..33bb7d57 --- /dev/null +++ b/.forge-snapshots/SwapRouter02ExecutorExecute.snap @@ -0,0 +1 @@ +262973 \ No newline at end of file diff --git a/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap b/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap new file mode 100644 index 00000000..f78360f3 --- /dev/null +++ b/.forge-snapshots/SwapRouter02ExecutorExecuteAlreadyApproved.snap @@ -0,0 +1 @@ +118092 \ No newline at end of file diff --git a/.forge-snapshots/testExclusiveFillerSucceeds.snap b/.forge-snapshots/testExclusiveFillerSucceeds.snap index 9415a50e..9a90a3d8 100644 --- a/.forge-snapshots/testExclusiveFillerSucceeds.snap +++ b/.forge-snapshots/testExclusiveFillerSucceeds.snap @@ -1 +1 @@ -150778 \ No newline at end of file +174066 \ No newline at end of file diff --git a/README.md b/README.md index beb3d090..ed9fc0d3 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Reactors process orders using the following steps: - Resolve the order into inputs and outputs - Pull input tokens from the swapper to the fillContract using permit2 `permitWitnessTransferFrom` with the order as witness - Call `reactorCallback` on the fillContract -- Verify that the output tokens were received by the output recipients +- Transfer output tokens from the fillContract to the output recipients Reactors implement the [IReactor](./src/interfaces/IReactor.sol) interface which abstracts the specifics of the order specification. This allows for different reactor implementations with different order formats to be used with the same interface, allowing for shared infrastructure and easy extension by fillers. @@ -30,14 +30,14 @@ Current reactor implementations: ### Fill Contracts -Order fillContracts _fill_ UniswapX orders. They specify the filler's strategy for fulfilling orders and are called by the reactor with `reactorCallback`. +Order fillContracts _fill_ UniswapX orders. They specify the filler's strategy for fulfilling orders and are called by the reactor with `reactorCallback` when using `executeWithCallback` or `executeBatchWithCallback`. Some sample fillContract implementations are provided in this repository: - [SwapRouter02Executor](./src/sample-executors/SwapRouter02Executor.sol): A fillContract that uses UniswapV2 and UniswapV3 via the SwapRouter02 router ### Direct Fill -If a filler wants to fill orders using funds on-hand rather than a fillContract, they can do so gas efficiently using the `directFill` macro by specifying `address(1)` as the fillContract. This will pull tokens from the filler using `msg.sender` to satisfy the order outputs. +If a filler wants to simply fill orders using funds held by an address rather than using a fillContract strategy, they can do so gas efficiently by using `execute` or `executeBatch`. These functions cause the reactor to skip the `reactorCallback` and simply pull tokens from the filler using `msg.sender`. # Integrating with UniswapX Jump to the docs for [Creating a Filler Integration](https://docs.uniswap.org/contracts/uniswapx/guides/createfiller). @@ -66,6 +66,10 @@ forge test FOUNDRY_PROFILE=integration forge test ``` +# Fee-on-Transfer Disclaimer + +Note that UniswapX handles fee-on-transfer tokens by transferring the amount specified to the recipient. This means that the actual amount received by the recipient will be _after_ fees. + # Audit This codebase was audited by [ABDK](./audit/ABDK.pdf). diff --git a/src/interfaces/IReactor.sol b/src/interfaces/IReactor.sol index e250e915..04527726 100644 --- a/src/interfaces/IReactor.sol +++ b/src/interfaces/IReactor.sol @@ -6,19 +6,20 @@ import {IReactorCallback} from "./IReactorCallback.sol"; /// @notice Interface for order execution reactors interface IReactor { - /// @notice Execute a single order using the given fill specification + /// @notice Execute a single order /// @param order The order definition and valid signature to execute - /// @param fillContract The contract which will fill the order - /// @param fillData The fillData to pass to the fillContract callback - function execute(SignedOrder calldata order, IReactorCallback fillContract, bytes calldata fillData) - external - payable; + function execute(SignedOrder calldata order) external payable; - /// @notice Execute the given orders at once with the specified fill specification + /// @notice Execute a single order using the given callback data + /// @param order The order definition and valid signature to execute + function executeWithCallback(SignedOrder calldata order, bytes calldata callbackData) external payable; + + /// @notice Execute the given orders at once + /// @param orders The order definitions and valid signatures to execute + function executeBatch(SignedOrder[] calldata orders) external payable; + + /// @notice Execute the given orders at once using a callback with the given callback data /// @param orders The order definitions and valid signatures to execute - /// @param fillContract The contract which will fill the order - /// @param fillData The fillData to pass to the fillContract callback - function executeBatch(SignedOrder[] calldata orders, IReactorCallback fillContract, bytes calldata fillData) - external - payable; + /// @param callbackData The callbackData to pass to the callback + function executeBatchWithCallback(SignedOrder[] calldata orders, bytes calldata callbackData) external payable; } diff --git a/src/interfaces/IReactorCallback.sol b/src/interfaces/IReactorCallback.sol index b691d720..a4dc87d4 100644 --- a/src/interfaces/IReactorCallback.sol +++ b/src/interfaces/IReactorCallback.sol @@ -7,8 +7,7 @@ import {ResolvedOrder} from "../base/ReactorStructs.sol"; interface IReactorCallback { /// @notice Called by the reactor during the execution of an order /// @param resolvedOrders Has inputs and outputs - /// @param filler The address who called execute on the reactor - /// @param fillData The fillData specified for an order execution + /// @param callbackData The callbackData specified for an order execution /// @dev Must have approved each token and amount in outputs to the msg.sender - function reactorCallback(ResolvedOrder[] memory resolvedOrders, address filler, bytes memory fillData) external; + function reactorCallback(ResolvedOrder[] memory resolvedOrders, bytes memory callbackData) external; } diff --git a/src/lens/OrderQuoter.sol b/src/lens/OrderQuoter.sol index ea9bc074..8dcf27f9 100644 --- a/src/lens/OrderQuoter.sol +++ b/src/lens/OrderQuoter.sol @@ -21,7 +21,7 @@ contract OrderQuoter is IReactorCallback { /// @param sig The order signature /// @return result The ResolvedOrder function quote(bytes memory order, bytes memory sig) external returns (ResolvedOrder memory result) { - try IReactor(getReactor(order)).execute(SignedOrder(order, sig), this, bytes("")) {} + try IReactor(getReactor(order)).executeWithCallback(SignedOrder(order, sig), bytes("")) {} catch (bytes memory reason) { result = parseRevertReason(reason); } @@ -52,9 +52,7 @@ contract OrderQuoter is IReactorCallback { /// @notice Reactor callback function /// @dev reverts with the resolved order as reason /// @param resolvedOrders The resolved orders - /// @param filler The filler of the order - function reactorCallback(ResolvedOrder[] memory resolvedOrders, address filler, bytes memory) external view { - require(filler == address(this)); + function reactorCallback(ResolvedOrder[] memory resolvedOrders, bytes memory) external pure { if (resolvedOrders.length != 1) { revert OrdersLengthIncorrect(); } diff --git a/src/lib/CurrencyLibrary.sol b/src/lib/CurrencyLibrary.sol index e0c6bb90..06181d27 100644 --- a/src/lib/CurrencyLibrary.sol +++ b/src/lib/CurrencyLibrary.sol @@ -7,6 +7,7 @@ import {SafeCast} from "openzeppelin-contracts/utils/math/SafeCast.sol"; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; address constant NATIVE = 0x0000000000000000000000000000000000000000; +uint256 constant TRANSFER_NATIVE_GAS_LIMIT = 6900; /// @title CurrencyLibrary /// @dev This library allows for transferring native ETH and ERC20s via direct filler OR fill contract. @@ -28,33 +29,27 @@ library CurrencyLibrary { } } - /// @notice Transfer currency to recipient + /// @notice Transfer currency from the caller to recipient + /// @dev for native outputs we will already have the currency in local balance /// @param currency The currency to transfer /// @param recipient The recipient of the currency /// @param amount The amount of currency to transfer - function transfer(address currency, address recipient, uint256 amount) internal { + function transferFill(address currency, address recipient, uint256 amount) internal { if (isNative(currency)) { - (bool success,) = recipient.call{value: amount}(""); - if (!success) revert NativeTransferFailed(); + // we will have received native assets directly so can directly transfer + transferNative(recipient, amount); } else { - ERC20(currency).safeTransfer(recipient, amount); + // else the caller must have approved the token for the fill + ERC20(currency).safeTransferFrom(msg.sender, recipient, amount); } } - /// @notice Transfer currency from msg.sender to the recipient - /// @dev if currency is ETH, the value must have been sent in the execute call and is transferred directly - /// @dev if currency is token, the value is transferred from msg.sender via permit2 - /// @param currency The currency to transfer + /// @notice Transfer native currency to recipient /// @param recipient The recipient of the currency /// @param amount The amount of currency to transfer - /// @param permit2 The deployed permit2 address - function transferFromDirectFiller(address currency, address recipient, uint256 amount, IPermit2 permit2) internal { - if (isNative(currency)) { - (bool success,) = recipient.call{value: amount}(""); - if (!success) revert NativeTransferFailed(); - } else { - permit2.transferFrom(msg.sender, recipient, SafeCast.toUint160(amount), currency); - } + function transferNative(address recipient, uint256 amount) internal { + (bool success,) = recipient.call{value: amount, gas: TRANSFER_NATIVE_GAS_LIMIT}(""); + if (!success) revert NativeTransferFailed(); } /// @notice returns true if currency is native diff --git a/src/lib/ExpectedBalanceLib.sol b/src/lib/ExpectedBalanceLib.sol deleted file mode 100644 index a71170f6..00000000 --- a/src/lib/ExpectedBalanceLib.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {OrderInfo, ResolvedOrder, OutputToken} from "../base/ReactorStructs.sol"; -import {CurrencyLibrary} from "./CurrencyLibrary.sol"; - -struct ExpectedBalance { - address recipient; - address token; - uint256 expectedBalance; -} - -library ExpectedBalanceLib { - using CurrencyLibrary for address; - - error InsufficientOutput(uint256 actualBalance, uint256 expectedBalance); - - /// @notice fetches expected post-fill balances for all recipient-token output pairs - /// @param orders The orders to fetch balances for - function getExpectedBalances(ResolvedOrder[] memory orders) - internal - view - returns (ExpectedBalance[] memory expectedBalances) - { - // TODO: surely there are optimizations / cleanup we can do here - // with EIP-1153 can use transient storage mappings - - // get the total number of outputs - // note this is an upper bound on the length of the resulting array - // because (recipient, token) pairs are deduplicated - unchecked { - uint256 outputCount = 0; - for (uint256 i = 0; i < orders.length; i++) { - outputCount += orders[i].outputs.length; - } - expectedBalances = new ExpectedBalance[](outputCount); - } - - uint256 nextOutputIdx = 0; - - // for each unique output (recipient, token) pair, add an entry to expectedBalances that - // includes the user's initial balance + expected output - for (uint256 i = 0; i < orders.length;) { - ResolvedOrder memory order = orders[i]; - - for (uint256 j = 0; j < order.outputs.length;) { - OutputToken memory output = order.outputs[j]; - - // check if the given output (address, token) pair already exists in expectedBalances - // update it if so - bool found = false; - for (uint256 k = 0; k < nextOutputIdx;) { - ExpectedBalance memory addressBalance = expectedBalances[k]; - if (addressBalance.recipient == output.recipient && addressBalance.token == output.token) { - found = true; - addressBalance.expectedBalance += output.amount; - break; - } - - unchecked { - k++; - } - } - - if (!found) { - uint256 balance = output.token.balanceOf(output.recipient); - expectedBalances[nextOutputIdx] = ExpectedBalance({ - recipient: output.recipient, - token: output.token, - expectedBalance: balance + output.amount - }); - unchecked { - nextOutputIdx++; - } - } - unchecked { - j++; - } - } - unchecked { - i++; - } - } - - assembly { - // update array size to the actual number of unique recipient/token pairs - // since the array was initialized with an upper bound of the total number of outputs - // note: this leaves a few unused memory slots, but free memory pointer - // still points to the next fresh piece of memory - mstore(expectedBalances, nextOutputIdx) - } - } - - /// @notice Asserts expected balances are satisfied - /// @param expectedBalances The expected balances to check - function check(ExpectedBalance[] memory expectedBalances) internal view { - for (uint256 i = 0; i < expectedBalances.length; i++) { - ExpectedBalance memory expected = expectedBalances[i]; - uint256 balance = expected.token.balanceOf(expected.recipient); - if (balance < expected.expectedBalance) { - revert InsufficientOutput(balance, expected.expectedBalance); - } - } - } -} diff --git a/src/reactors/BaseReactor.sol b/src/reactors/BaseReactor.sol index 8fee689e..46205f5c 100644 --- a/src/reactors/BaseReactor.sol +++ b/src/reactors/BaseReactor.sol @@ -8,7 +8,6 @@ import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {ReactorEvents} from "../base/ReactorEvents.sol"; import {ResolvedOrderLib} from "../lib/ResolvedOrderLib.sol"; import {CurrencyLibrary, NATIVE} from "../lib/CurrencyLibrary.sol"; -import {ExpectedBalance, ExpectedBalanceLib} from "../lib/ExpectedBalanceLib.sol"; import {IReactorCallback} from "../interfaces/IReactorCallback.sol"; import {IReactor} from "../interfaces/IReactor.sol"; import {ProtocolFees} from "../base/ProtocolFees.sol"; @@ -19,8 +18,6 @@ import {SignedOrder, ResolvedOrder, OutputToken} from "../base/ReactorStructs.so abstract contract BaseReactor is IReactor, ReactorEvents, ProtocolFees, ReentrancyGuard { using SafeTransferLib for ERC20; using ResolvedOrderLib for ResolvedOrder; - using ExpectedBalanceLib for ResolvedOrder[]; - using ExpectedBalanceLib for ExpectedBalance[]; using CurrencyLibrary for address; // Occurs when an output = ETH and the reactor does contain enough ETH but @@ -30,16 +27,21 @@ abstract contract BaseReactor is IReactor, ReactorEvents, ProtocolFees, Reentran /// @notice permit2 address used for token transfers and signature verification IPermit2 public immutable permit2; - /// @notice special fillContract address used to indicate a direct fill - /// @dev direct fills transfer tokens directly from the filler to the swapper - IReactorCallback public constant DIRECT_FILL = IReactorCallback(address(1)); - constructor(IPermit2 _permit2, address _protocolFeeOwner) ProtocolFees(_protocolFeeOwner) { permit2 = _permit2; } /// @inheritdoc IReactor - function execute(SignedOrder calldata order, IReactorCallback fillContract, bytes calldata fillData) + function execute(SignedOrder calldata order) external payable override nonReentrant { + ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](1); + resolvedOrders[0] = resolve(order); + + _prepare(resolvedOrders); + _fill(resolvedOrders); + } + + /// @inheritdoc IReactor + function executeWithCallback(SignedOrder calldata order, bytes calldata callbackData) external payable override @@ -48,70 +50,90 @@ abstract contract BaseReactor is IReactor, ReactorEvents, ProtocolFees, Reentran ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](1); resolvedOrders[0] = resolve(order); - _fill(resolvedOrders, fillContract, fillData); + _prepare(resolvedOrders); + IReactorCallback(msg.sender).reactorCallback(resolvedOrders, callbackData); + _fill(resolvedOrders); + } + + /// @inheritdoc IReactor + function executeBatch(SignedOrder[] calldata orders) external payable override nonReentrant { + uint256 ordersLength = orders.length; + ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](ordersLength); + + unchecked { + for (uint256 i = 0; i < ordersLength; i++) { + resolvedOrders[i] = resolve(orders[i]); + } + } + + _prepare(resolvedOrders); + _fill(resolvedOrders); } /// @inheritdoc IReactor - function executeBatch(SignedOrder[] calldata orders, IReactorCallback fillContract, bytes calldata fillData) + function executeBatchWithCallback(SignedOrder[] calldata orders, bytes calldata callbackData) external payable override nonReentrant { - ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](orders.length); + uint256 ordersLength = orders.length; + ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](ordersLength); unchecked { - for (uint256 i = 0; i < orders.length; i++) { + for (uint256 i = 0; i < ordersLength; i++) { resolvedOrders[i] = resolve(orders[i]); } } - _fill(resolvedOrders, fillContract, fillData); + + _prepare(resolvedOrders); + IReactorCallback(msg.sender).reactorCallback(resolvedOrders, callbackData); + _fill(resolvedOrders); } - /// @notice validates and fills a list of orders, marking it as filled - function _fill(ResolvedOrder[] memory orders, IReactorCallback fillContract, bytes calldata fillData) internal { - bool directFill = fillContract == DIRECT_FILL; + /// @notice validates, injects fees, and transfers input tokens in preparation for order fill + /// @param orders The orders to prepare + function _prepare(ResolvedOrder[] memory orders) internal { + uint256 ordersLength = orders.length; unchecked { - for (uint256 i = 0; i < orders.length; i++) { + for (uint256 i = 0; i < ordersLength; i++) { ResolvedOrder memory order = orders[i]; _injectFees(order); order.validate(msg.sender); - transferInputTokens(order, directFill ? msg.sender : address(fillContract)); - - // Batch fills are all-or-nothing so emit fill events now to save a loop - emit Fill(orders[i].hash, msg.sender, order.info.swapper, order.info.nonce); + transferInputTokens(order, msg.sender); } } - - if (directFill) { - _processDirectFill(orders); - } else { - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - fillContract.reactorCallback(orders, msg.sender, fillData); - expectedBalances.check(); - } } - /// @notice Process transferring tokens from a direct filler to the recipients - /// @dev in the case of ETH outputs, ETH should be provided as value in the execute call - /// @param orders The orders to process - function _processDirectFill(ResolvedOrder[] memory orders) internal { + /// @notice fills a list of orders, ensuring all outputs are satisfied + /// @param orders The orders to fill + function _fill(ResolvedOrder[] memory orders) internal { + uint256 ordersLength = orders.length; + // attempt to transfer all currencies to all recipients unchecked { - for (uint256 i = 0; i < orders.length; i++) { - ResolvedOrder memory order = orders[i]; - for (uint256 j = 0; j < order.outputs.length; j++) { - OutputToken memory output = order.outputs[j]; - output.token.transferFromDirectFiller(output.recipient, output.amount, permit2); + // transfer output tokens to their respective recipients + for (uint256 i = 0; i < ordersLength; i++) { + ResolvedOrder memory resolvedOrder = orders[i]; + uint256 outputsLength = resolvedOrder.outputs.length; + for (uint256 j = 0; j < outputsLength; j++) { + OutputToken memory output = resolvedOrder.outputs[j]; + output.token.transferFill(output.recipient, output.amount); } - } - // refund any remaining ETH to the filler. Only occurs when filler sends more ETH than required to - // `execute()` or `executeBatch()`, or when there is excess contract balance remaining from others - // incorrectly calling execute/executeBatch without direct filler method but with a msg.value - if (address(this).balance > 0) { - NATIVE.transfer(msg.sender, address(this).balance); + emit Fill(orders[i].hash, msg.sender, resolvedOrder.info.swapper, resolvedOrder.info.nonce); } } + + // refund any remaining ETH to the filler. Only occurs when filler sends more ETH than required to + // `execute()` or `executeBatch()`, or when there is excess contract balance remaining from others + // incorrectly calling execute/executeBatch without direct filler method but with a msg.value + if (address(this).balance > 0) { + CurrencyLibrary.transferNative(msg.sender, address(this).balance); + } + } + + receive() external payable { + // receive native asset to support native output } /// @notice Resolve order-type specific requirements into a generic order with the final inputs and outputs. diff --git a/src/sample-executors/SwapRouter02Executor.sol b/src/sample-executors/SwapRouter02Executor.sol index 7e978705..b8c99707 100644 --- a/src/sample-executors/SwapRouter02Executor.sol +++ b/src/sample-executors/SwapRouter02Executor.sol @@ -8,7 +8,7 @@ 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, OutputToken} from "../base/ReactorStructs.sol"; +import {ResolvedOrder, OutputToken, SignedOrder} from "../base/ReactorStructs.sol"; import {ISwapRouter02} from "../external/ISwapRouter02.sol"; /// @notice A fill contract that uses SwapRouter02 to execute trades @@ -26,6 +26,20 @@ contract SwapRouter02Executor is IReactorCallback, Owned { IReactor private immutable reactor; WETH private immutable weth; + modifier onlyWhitelistedCaller() { + if (msg.sender != whitelistedCaller) { + revert CallerNotWhitelisted(); + } + _; + } + + modifier onlyReactor() { + if (msg.sender != address(reactor)) { + revert MsgSenderNotReactor(); + } + _; + } + constructor(address _whitelistedCaller, IReactor _reactor, address _owner, ISwapRouter02 _swapRouter02) Owned(_owner) { @@ -35,37 +49,44 @@ contract SwapRouter02Executor is IReactorCallback, Owned { weth = WETH(payable(_swapRouter02.WETH9())); } - /// @param resolvedOrders The orders to fill - /// @param filler This filler must be `whitelistedCaller` - /// @param fillData It has the below encoded: + /// @notice assume that we already have all output tokens + function execute(SignedOrder calldata order, bytes calldata callbackData) external onlyWhitelistedCaller { + reactor.executeWithCallback(order, callbackData); + } + + /// @notice assume that we already have all output tokens + function executeBatch(SignedOrder[] calldata orders, bytes calldata callbackData) external onlyWhitelistedCaller { + 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 /// bytes[] memory multicallData: Pass into swapRouter02.multicall() - function reactorCallback(ResolvedOrder[] calldata resolvedOrders, address filler, bytes calldata fillData) - external - { - if (msg.sender != address(reactor)) { - revert MsgSenderNotReactor(); - } - if (filler != whitelistedCaller) { - revert CallerNotWhitelisted(); - } + function reactorCallback(ResolvedOrder[] calldata, bytes calldata callbackData) external onlyReactor { + ( + address[] memory tokensToApproveForSwapRouter02, + address[] memory tokensToApproveForReactor, + bytes[] memory multicallData + ) = abi.decode(callbackData, (address[], address[], bytes[])); - (address[] memory tokensToApproveForSwapRouter02, bytes[] memory multicallData) = - abi.decode(fillData, (address[], bytes[])); + unchecked { + for (uint256 i = 0; i < tokensToApproveForSwapRouter02.length; i++) { + ERC20(tokensToApproveForSwapRouter02[i]).safeApprove(address(swapRouter02), type(uint256).max); + } - 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(address(reactor), type(uint256).max); + } } swapRouter02.multicall(type(uint256).max, multicallData); - for (uint256 i = 0; i < resolvedOrders.length; i++) { - ResolvedOrder memory order = resolvedOrders[i]; - for (uint256 j = 0; j < order.outputs.length; j++) { - OutputToken memory output = order.outputs[j]; - output.token.transfer(output.recipient, output.amount); - } + // transfer any native balance to the reactor + // it will refund any excess + if (address(this).balance > 0) { + CurrencyLibrary.transferNative(address(reactor), address(this).balance); } } diff --git a/test/base/BaseReactor.t.sol b/test/base/BaseReactor.t.sol index 935334ba..b9b3b2bc 100644 --- a/test/base/BaseReactor.t.sol +++ b/test/base/BaseReactor.t.sol @@ -16,6 +16,7 @@ import {ArrayBuilder} from "../util/ArrayBuilder.sol"; import {MockFeeController} from "../util/mock/MockFeeController.sol"; import {MockERC20} from "../util/mock/MockERC20.sol"; import {MockFillContract} from "../util/mock/MockFillContract.sol"; +import {MockFillContractDoubleExecution} from "../util/mock/MockFillContractDoubleExecution.sol"; import {NATIVE} from "../../src/lib/CurrencyLibrary.sol"; abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPermit2 { @@ -48,12 +49,13 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer swapperPrivateKey = 0x12341234; swapper = vm.addr(swapperPrivateKey); permit2 = IPermit2(deployPermit2()); - fillContract = new MockFillContract(); additionalValidationContract = new MockValidationContract(); additionalValidationContract.setValid(true); feeRecipient = makeAddr("feeRecipient"); feeController = new MockFeeController(feeRecipient); - createReactor(); + reactor = createReactor(); + + fillContract = new MockFillContract(address(reactor)); } function name() public virtual returns (string memory) {} @@ -110,12 +112,12 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer uint256 fillContractOutputBalanceStart ) = _checkpointBalances(); - // TODO: expand to allow for custom fillData in 3rd param + // TODO: expand to allow for custom callbackData in 3rd param vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(this), swapper, order.info.nonce); + emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); // execute order _snapStart("ExecuteSingle"); - reactor.execute(signedOrder, fillContract, bytes("")); + fillContract.execute(signedOrder); snapEnd(); assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); @@ -151,12 +153,12 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer uint256 fillContractOutputBalanceStart ) = _checkpointBalances(); - // TODO: expand to allow for custom fillData in 3rd param + // TODO: expand to allow for custom callbackData in 3rd param vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(this), swapper, order.info.nonce); + emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); // execute order _snapStart("ExecuteSingleValidation"); - reactor.execute(signedOrder, fillContract, bytes("")); + fillContract.execute(signedOrder); snapEnd(); assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); @@ -189,7 +191,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer (SignedOrder memory signedOrder,) = createAndSignOrder(order); vm.expectRevert(MockValidationContract.MockValidationError.selector); - reactor.execute(signedOrder, fillContract, bytes("")); + fillContract.execute(signedOrder); } /// @dev Execute with an invalid reactor @@ -219,7 +221,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer (SignedOrder memory signedOrder,) = createAndSignOrder(order); vm.expectRevert(ResolvedOrderLib.InvalidReactor.selector); - reactor.execute(signedOrder, fillContract, bytes("")); + fillContract.execute(signedOrder); } /// @dev Execute with a deadline already passed @@ -244,7 +246,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer // cannot enforce selector as some reactors early throw in this case vm.expectRevert(); - reactor.execute(signedOrder, fillContract, bytes("")); + fillContract.execute(signedOrder); } /// @dev Basic execute test for native currency, checks balance before and after @@ -269,12 +271,12 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer uint256 fillContractOutputBalanceStart = address(fillContract).balance; (uint256 swapperInputBalanceStart, uint256 fillContractInputBalanceStart,,) = _checkpointBalances(); - // TODO: expand to allow for custom fillData in 3rd param + // TODO: expand to allow for custom callbackData in 3rd param vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(this), swapper, order.info.nonce); + emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); // execute order _snapStart("ExecuteSingleNativeOutput"); - reactor.execute(signedOrder, fillContract, bytes("")); + fillContract.execute(signedOrder); snapEnd(); assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); @@ -320,12 +322,12 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer (SignedOrder[] memory signedOrders, bytes32[] memory orderHashes) = createAndSignBatchOrders(orders); vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[0], address(this), swapper, orders[0].info.nonce); + emit Fill(orderHashes[0], address(fillContract), swapper, orders[0].info.nonce); vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[1], address(this), swapper, orders[1].info.nonce); + emit Fill(orderHashes[1], address(fillContract), swapper, orders[1].info.nonce); _snapStart("ExecuteBatch"); - reactor.executeBatch(signedOrders, fillContract, bytes("")); + fillContract.executeBatch(signedOrders); snapEnd(); assertEq(tokenOut.balanceOf(swapper), totalOutputAmount); @@ -368,12 +370,12 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer (SignedOrder[] memory signedOrders, bytes32[] memory orderHashes) = createAndSignBatchOrders(orders); vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[0], address(this), swapper, orders[0].info.nonce); + emit Fill(orderHashes[0], address(fillContract), swapper, orders[0].info.nonce); vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[1], address(this), swapper, orders[1].info.nonce); + emit Fill(orderHashes[1], address(fillContract), swapper, orders[1].info.nonce); _snapStart("ExecuteBatchNativeOutput"); - reactor.executeBatch(signedOrders, fillContract, bytes("")); + fillContract.executeBatch(signedOrders); snapEnd(); assertEq(swapper.balance, totalOutputAmount); @@ -420,12 +422,12 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer (SignedOrder[] memory signedOrders, bytes32[] memory orderHashes) = createAndSignBatchOrders(orders); vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[0], address(this), swapper, orders[0].info.nonce); + emit Fill(orderHashes[0], address(fillContract), swapper, orders[0].info.nonce); vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[1], address(this), swapper, orders[1].info.nonce); + emit Fill(orderHashes[1], address(fillContract), swapper, orders[1].info.nonce); _snapStart("ExecuteBatchMultipleOutputs"); - reactor.executeBatch(signedOrders, fillContract, bytes("")); + fillContract.executeBatch(signedOrders); snapEnd(); assertEq(tokenOut.balanceOf(swapper), totalOutputAmount); @@ -477,12 +479,12 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer (SignedOrder[] memory signedOrders, bytes32[] memory orderHashes) = createAndSignBatchOrders(orders); vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[0], address(this), swapper, orders[0].info.nonce); + emit Fill(orderHashes[0], address(fillContract), swapper, orders[0].info.nonce); vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[1], address(this), swapper, orders[1].info.nonce); + emit Fill(orderHashes[1], address(fillContract), swapper, orders[1].info.nonce); _snapStart("ExecuteBatchMultipleOutputsDifferentTokens"); - reactor.executeBatch(signedOrders, fillContract, bytes("")); + fillContract.executeBatch(signedOrders); snapEnd(); assertEq(tokenOut.balanceOf(swapper), totalOutputAmount1); @@ -516,8 +518,8 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer ) = _checkpointBalances(); vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(this), swapper, order.info.nonce); - reactor.execute(signedOrder, fillContract, bytes("")); + emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); + fillContract.execute(signedOrder); assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + inputAmount); @@ -535,7 +537,7 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer signedOrder.sig = oldSignature; vm.expectRevert(InvalidSigner.selector); - reactor.execute(signedOrder, fillContract, bytes("")); + fillContract.execute(signedOrder); } /// @dev Base test preventing nonce reuse @@ -566,8 +568,8 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer ) = _checkpointBalances(); vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(this), swapper, order.info.nonce); - reactor.execute(signedOrder, fillContract, bytes("")); + emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); + fillContract.execute(signedOrder); assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + inputAmount); @@ -578,7 +580,51 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer order.info.deadline = block.timestamp + 101; (signedOrder, orderHash) = createAndSignOrder(order); vm.expectRevert(InvalidNonce.selector); - reactor.execute(signedOrder, fillContract, bytes("")); + fillContract.execute(signedOrder); + } + + /// @dev Test executing two orders on two reactors at once + /// @dev executing the second order inside the callback of the first's execution + function testBaseExecuteTwoReactorsAtOnce() public { + BaseReactor otherReactor = createReactor(); + MockFillContractDoubleExecution doubleExecutionFillContract = + new MockFillContractDoubleExecution(address(reactor), address(otherReactor)); + // Seed both swapper and fillContract with enough tokens (important for dutch order) + tokenIn.mint(address(swapper), 2 ether); + tokenOut.mint(address(doubleExecutionFillContract), 2 ether); + tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); + + ResolvedOrder memory order1 = ResolvedOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000), + input: InputToken(tokenIn, 1 ether, 1 ether), + outputs: OutputsBuilder.single(address(tokenOut), 1 ether, swapper), + sig: hex"00", + hash: bytes32(0) + }); + + ResolvedOrder memory order2 = ResolvedOrder({ + info: OrderInfoBuilder.init(address(otherReactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) + .withNonce(1234), + input: InputToken(tokenIn, 1 ether, 1 ether), + outputs: OutputsBuilder.single(address(tokenOut), 1 ether, swapper), + sig: hex"00", + hash: bytes32(0) + }); + + (SignedOrder memory signedOrder1, bytes32 orderHash1) = createAndSignOrder(order1); + (SignedOrder memory signedOrder2, bytes32 orderHash2) = createAndSignOrder(order2); + + (uint256 swapperInputBalanceStart,, uint256 swapperOutputBalanceStart,) = _checkpointBalances(); + + // TODO: expand to allow for custom callbackData in 3rd param + vm.expectEmit(true, true, true, true, address(otherReactor)); + emit Fill(orderHash2, address(doubleExecutionFillContract), swapper, order2.info.nonce); + vm.expectEmit(true, true, true, true, address(reactor)); + emit Fill(orderHash1, address(doubleExecutionFillContract), swapper, order1.info.nonce); + doubleExecutionFillContract.execute(signedOrder1, signedOrder2); + + assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - 2 ether); + assertEq(tokenOut.balanceOf(address(swapper)), swapperOutputBalanceStart + 2 ether); } /// @dev Basic execute test with protocol fee, checks balance before and after @@ -612,10 +658,10 @@ abstract contract BaseReactorTest is GasSnapshot, ReactorEvents, Test, DeployPer ) = _checkpointBalances(); vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(this), swapper, order.info.nonce); + emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); // execute order _snapStart("BaseExecuteSingleWithFee"); - reactor.execute(signedOrder, fillContract, bytes("")); + fillContract.execute(signedOrder); snapEnd(); uint256 feeAmount = uint256(outputAmount) * feeBps / 10000; diff --git a/test/base/EthOutput.t.sol b/test/base/EthOutput.t.sol index 153eb0ba..ff95fb83 100644 --- a/test/base/EthOutput.t.sol +++ b/test/base/EthOutput.t.sol @@ -15,7 +15,6 @@ import {ProtocolFees} from "../../src/base/ProtocolFees.sol"; import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; import {PermitSignature} from "../util/PermitSignature.sol"; import {BaseReactor} from "../../src/reactors/BaseReactor.sol"; -import {ExpectedBalanceLib} from "../../src/lib/ExpectedBalanceLib.sol"; import {DutchOrderLib} from "../../src/lib/DutchOrderLib.sol"; import {IReactorCallback} from "../../src/interfaces/IReactorCallback.sol"; @@ -40,13 +39,13 @@ contract EthOutputMockFillContractTest is Test, DeployPermit2, PermitSignature, function setUp() public { tokenIn1 = new MockERC20("tokenIn1", "IN1", 18); tokenOut1 = new MockERC20("tokenOut1", "OUT1", 18); - fillContract = new MockFillContract(); swapperPrivateKey1 = 0x12341234; swapper1 = vm.addr(swapperPrivateKey1); swapperPrivateKey2 = 0x12341235; swapper2 = vm.addr(swapperPrivateKey2); permit2 = IPermit2(deployPermit2()); reactor = new DutchOrderReactor(permit2, PROTOCOL_FEE_OWNER); + fillContract = new MockFillContract(address(reactor)); tokenIn1.forceApprove(swapper1, address(permit2), type(uint256).max); tokenIn1.forceApprove(swapper2, address(permit2), type(uint256).max); } @@ -65,11 +64,7 @@ contract EthOutputMockFillContractTest is Test, DeployPermit2, PermitSignature, outputs: OutputsBuilder.singleDutch(NATIVE, ONE, 0, swapper1) }); snapStart("EthOutputTestEthOutput"); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - fillContract, - bytes("") - ); + fillContract.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order))); snapEnd(); assertEq(tokenIn1.balanceOf(address(fillContract)), ONE); // There is 0.5 ETH remaining in the fillContract as output has decayed to 0.5 ETH @@ -118,7 +113,7 @@ contract EthOutputMockFillContractTest is Test, DeployPermit2, PermitSignature, signedOrders[1] = SignedOrder(abi.encode(order2), signOrder(swapperPrivateKey2, address(permit2), order2)); signedOrders[2] = SignedOrder(abi.encode(order3), signOrder(swapperPrivateKey2, address(permit2), order3)); snapStart("EthOutputTest3OrdersWithEthAndERC20Outputs"); - reactor.executeBatch(signedOrders, fillContract, bytes("")); + fillContract.executeBatch(signedOrders); snapEnd(); assertEq(tokenOut1.balanceOf(swapper1), 3 * ONE); assertEq(swapper1.balance, 2 * ONE); @@ -168,7 +163,7 @@ contract EthOutputMockFillContractTest is Test, DeployPermit2, PermitSignature, signedOrders[1] = SignedOrder(abi.encode(order2), signOrder(swapperPrivateKey2, address(permit2), order2)); signedOrders[2] = SignedOrder(abi.encode(order3), signOrder(swapperPrivateKey2, address(permit2), order3)); vm.expectRevert(CurrencyLibrary.NativeTransferFailed.selector); - reactor.executeBatch(signedOrders, fillContract, bytes("")); + fillContract.executeBatch(signedOrders); } // Same as `test3OrdersWithEthAndERC20Outputs` but the fillContract does not have enough ETH. The reactor DOES @@ -212,7 +207,7 @@ contract EthOutputMockFillContractTest is Test, DeployPermit2, PermitSignature, signedOrders[1] = SignedOrder(abi.encode(order2), signOrder(swapperPrivateKey2, address(permit2), order2)); signedOrders[2] = SignedOrder(abi.encode(order3), signOrder(swapperPrivateKey2, address(permit2), order3)); vm.expectRevert(CurrencyLibrary.NativeTransferFailed.selector); - reactor.executeBatch(signedOrders, fillContract, bytes("")); + fillContract.executeBatch(signedOrders); } } @@ -256,15 +251,9 @@ contract EthOutputDirectFillerTest is Test, PermitSignature, GasSnapshot, Deploy tokenIn1.forceApprove(swapper2, address(permit2), type(uint256).max); tokenIn2.forceApprove(swapper2, address(permit2), type(uint256).max); tokenIn3.forceApprove(swapper2, address(permit2), type(uint256).max); - tokenOut1.forceApprove(directFiller, address(permit2), type(uint256).max); - tokenOut2.forceApprove(directFiller, address(permit2), type(uint256).max); - tokenOut3.forceApprove(directFiller, address(permit2), type(uint256).max); - vm.prank(directFiller); - permit2.approve(address(tokenOut1), address(reactor), type(uint160).max, type(uint48).max); - vm.prank(directFiller); - permit2.approve(address(tokenOut2), address(reactor), type(uint160).max, type(uint48).max); - vm.prank(directFiller); - permit2.approve(address(tokenOut3), address(reactor), type(uint160).max, type(uint48).max); + tokenOut1.forceApprove(directFiller, address(reactor), type(uint256).max); + tokenOut2.forceApprove(directFiller, address(reactor), type(uint256).max); + tokenOut3.forceApprove(directFiller, address(reactor), type(uint256).max); } // Fill 1 order with requested output = 2 ETH. @@ -286,9 +275,7 @@ contract EthOutputDirectFillerTest is Test, PermitSignature, GasSnapshot, Deploy vm.prank(directFiller); snapStart("DirectFillerFillMacroTestEth1Output"); reactor.execute{value: outputAmount}( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - IReactorCallback(address(1)), - bytes("") + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)) ); snapEnd(); assertEq(tokenIn1.balanceOf(directFiller), inputAmount); @@ -312,9 +299,7 @@ contract EthOutputDirectFillerTest is Test, PermitSignature, GasSnapshot, Deploy vm.prank(directFiller); reactor.execute{value: outputAmount * 2}( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - IReactorCallback(address(1)), - bytes("") + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)) ); // check directFiller received refund assertEq(directFiller.balance, outputAmount); @@ -341,9 +326,7 @@ contract EthOutputDirectFillerTest is Test, PermitSignature, GasSnapshot, Deploy vm.prank(directFiller); vm.expectRevert(CurrencyLibrary.NativeTransferFailed.selector); reactor.execute{value: outputAmount - 1}( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - IReactorCallback(address(1)), - bytes("") + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)) ); } @@ -375,7 +358,7 @@ contract EthOutputDirectFillerTest is Test, PermitSignature, GasSnapshot, Deploy vm.prank(directFiller); snapStart("DirectFillerFillMacroTestEth2Outputs"); - reactor.executeBatch{value: ONE * 3}(signedOrders, IReactorCallback(address(1)), bytes("")); + reactor.executeBatch{value: ONE * 3}(signedOrders); snapEnd(); assertEq(tokenIn1.balanceOf(directFiller), 2 * inputAmount); assertEq(swapper1.balance, 3 * ONE); @@ -423,7 +406,7 @@ contract EthOutputDirectFillerTest is Test, PermitSignature, GasSnapshot, Deploy signedOrders[2] = SignedOrder(abi.encode(order3), signOrder(swapperPrivateKey2, address(permit2), order3)); vm.prank(directFiller); - reactor.executeBatch{value: ONE * 5}(signedOrders, IReactorCallback(address(1)), bytes("")); + reactor.executeBatch{value: ONE * 5}(signedOrders); assertEq(tokenOut1.balanceOf(swapper1), 3 * ONE); assertEq(swapper1.balance, 2 * ONE); assertEq(swapper2.balance, 3 * ONE); @@ -461,6 +444,6 @@ contract EthOutputDirectFillerTest is Test, PermitSignature, GasSnapshot, Deploy vm.prank(directFiller); vm.expectRevert(CurrencyLibrary.NativeTransferFailed.selector); - reactor.executeBatch{value: ONE * 5 / 2}(signedOrders, IReactorCallback(address(1)), bytes("")); + reactor.executeBatch{value: ONE * 5 / 2}(signedOrders); } } diff --git a/test/base/ProtocolFees.t.sol b/test/base/ProtocolFees.t.sol index 7144f8c3..930a60e0 100644 --- a/test/base/ProtocolFees.t.sol +++ b/test/base/ProtocolFees.t.sol @@ -439,10 +439,10 @@ contract ProtocolFeesGasComparisonTest is Test, PermitSignature, DeployPermit2, swapperPrivateKey1 = 0x12341234; swapper1 = vm.addr(swapperPrivateKey1); - fillContract = new MockFillContract(); feeController = new MockFeeController(PROTOCOL_FEE_RECIPIENT); permit2 = IPermit2(deployPermit2()); reactor = new ExclusiveDutchOrderReactor(permit2, PROTOCOL_FEE_OWNER); + fillContract = new MockFillContract(address(reactor)); vm.prank(PROTOCOL_FEE_OWNER); reactor.setProtocolFeeController(address(feeController)); @@ -475,11 +475,7 @@ contract ProtocolFeesGasComparisonTest is Test, PermitSignature, DeployPermit2, outputs: dutchOutputs }); snapStart("ProtocolFeesGasComparisonTest-NoFees"); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - fillContract, - bytes("") - ); + fillContract.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order))); snapEnd(); assertEq(tokenIn1.balanceOf(address(fillContract)), 2 ether); assertEq(tokenOut1.balanceOf(address(swapper1)), 2 ether); @@ -503,11 +499,7 @@ contract ProtocolFeesGasComparisonTest is Test, PermitSignature, DeployPermit2, outputs: dutchOutputs }); snapStart("ProtocolFeesGasComparisonTest-InterfaceFee"); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - fillContract, - bytes("") - ); + fillContract.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order))); snapEnd(); assertEq(tokenIn1.balanceOf(address(fillContract)), 2 ether); assertEq(tokenOut1.balanceOf(address(swapper1)), 2 ether); @@ -535,11 +527,7 @@ contract ProtocolFeesGasComparisonTest is Test, PermitSignature, DeployPermit2, outputs: dutchOutputs }); snapStart("ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee"); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - fillContract, - bytes("") - ); + fillContract.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order))); snapEnd(); // fillContract had 1 tokenIn1 preminted to it assertEq(tokenIn1.balanceOf(address(fillContract)), 2 ether); @@ -568,11 +556,7 @@ contract ProtocolFeesGasComparisonTest is Test, PermitSignature, DeployPermit2, outputs: dutchOutputs }); snapStart("ProtocolFeesGasComparisonTest-NoFeesEthOutput"); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - fillContract, - bytes("") - ); + fillContract.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order))); snapEnd(); assertEq(tokenIn1.balanceOf(address(fillContract)), 2 ether); assertEq(swapper1.balance, 2 ether); @@ -596,11 +580,7 @@ contract ProtocolFeesGasComparisonTest is Test, PermitSignature, DeployPermit2, outputs: dutchOutputs }); snapStart("ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput"); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - fillContract, - bytes("") - ); + fillContract.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order))); snapEnd(); assertEq(tokenIn1.balanceOf(address(fillContract)), 2 ether); assertEq(swapper1.balance, 2 ether); @@ -628,11 +608,7 @@ contract ProtocolFeesGasComparisonTest is Test, PermitSignature, DeployPermit2, outputs: dutchOutputs }); snapStart("ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput"); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - fillContract, - bytes("") - ); + fillContract.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order))); snapEnd(); // fillContract had 1 tokenIn1 preminted to it assertEq(tokenIn1.balanceOf(address(fillContract)), 2 ether); diff --git a/test/executors/SwapRouter02Executor.t.sol b/test/executors/SwapRouter02Executor.t.sol index a0398676..7c6014e8 100644 --- a/test/executors/SwapRouter02Executor.t.sol +++ b/test/executors/SwapRouter02Executor.t.sol @@ -75,6 +75,9 @@ contract SwapRouter02ExecutorTest is Test, PermitSignature, GasSnapshot, DeployP 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), @@ -83,7 +86,7 @@ contract SwapRouter02ExecutorTest is Test, PermitSignature, GasSnapshot, DeployP amountOutMinimum: 0 }); multicallData[0] = abi.encodeWithSelector(ISwapRouter02.exactInput.selector, exactInputParams); - bytes memory fillData = abi.encode(tokensToApproveForSwapRouter02, multicallData); + bytes memory callbackData = abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData); ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](1); bytes memory sig = hex"1234"; @@ -97,10 +100,11 @@ contract SwapRouter02ExecutorTest is Test, PermitSignature, GasSnapshot, DeployP tokenIn.mint(address(swapRouter02Executor), ONE); tokenOut.mint(address(mockSwapRouter), ONE); vm.prank(address(reactor)); - swapRouter02Executor.reactorCallback(resolvedOrders, address(this), fillData); + swapRouter02Executor.reactorCallback(resolvedOrders, callbackData); assertEq(tokenIn.balanceOf(address(mockSwapRouter)), ONE); - assertEq(tokenOut.balanceOf(address(swapRouter02Executor)), 0); - assertEq(tokenOut.balanceOf(address(swapper)), ONE); + assertEq(tokenOut.balanceOf(address(swapRouter02Executor)), ONE); + assertEq(tokenOut.balanceOf(address(swapper)), 0); + assertEq(tokenOut.allowance(address(swapRouter02Executor), address(reactor)), type(uint256).max); } // Output will resolve to 0.5. Input = 1. SwapRouter exchanges at 1 to 1 rate. @@ -120,6 +124,9 @@ contract SwapRouter02ExecutorTest is Test, PermitSignature, GasSnapshot, DeployP 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), @@ -129,11 +136,12 @@ contract SwapRouter02ExecutorTest is Test, PermitSignature, GasSnapshot, DeployP }); multicallData[0] = abi.encodeWithSelector(ISwapRouter02.exactInput.selector, exactInputParams); - reactor.execute( + snapStart("SwapRouter02ExecutorExecute"); + swapRouter02Executor.execute( SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), - swapRouter02Executor, - abi.encode(tokensToApproveForSwapRouter02, multicallData) + abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData) ); + snapEnd(); assertEq(tokenIn.balanceOf(swapper), 0); assertEq(tokenIn.balanceOf(address(swapRouter02Executor)), 0); @@ -141,6 +149,64 @@ contract SwapRouter02ExecutorTest is Test, PermitSignature, GasSnapshot, DeployP assertEq(tokenOut.balanceOf(address(swapRouter02Executor)), ONE / 2); } + function testExecuteAlreadyApproved() public { + DutchOrder memory order = DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).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, 2 * ONE); + tokenOut.mint(address(mockSwapRouter), 2 * ONE); + + 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: address(swapRouter02Executor), + amountIn: ONE, + amountOutMinimum: 0 + }); + multicallData[0] = abi.encodeWithSelector(ISwapRouter02.exactInput.selector, exactInputParams); + + swapRouter02Executor.execute( + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), + abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData) + ); + + DutchOrder memory order2 = DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100).withNonce( + 1234 + ), + decayStartTime: block.timestamp - 100, + decayEndTime: block.timestamp + 100, + input: DutchInput(tokenIn, ONE, ONE), + outputs: OutputsBuilder.singleDutch(address(tokenOut), ONE, 0, address(swapper)) + }); + + tokensToApproveForSwapRouter02 = new address[](0); + tokensToApproveForReactor = new address[](0); + + snapStart("SwapRouter02ExecutorExecuteAlreadyApproved"); + swapRouter02Executor.execute( + SignedOrder(abi.encode(order2), signOrder(swapperPrivateKey, address(permit2), order2)), + abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData) + ); + snapEnd(); + + assertEq(tokenIn.balanceOf(swapper), 0); + assertEq(tokenIn.balanceOf(address(swapRouter02Executor)), 0); + assertEq(tokenOut.balanceOf(swapper), ONE); + assertEq(tokenOut.balanceOf(address(swapRouter02Executor)), ONE); + } + // Requested output = 2 & input = 1. SwapRouter swaps at 1 to 1 rate, so there will // there will be an overflow error when reactor tries to transfer 2 outputToken out of fill contract. function testExecuteInsufficientOutput() public { @@ -159,6 +225,9 @@ contract SwapRouter02ExecutorTest is Test, PermitSignature, GasSnapshot, DeployP 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), @@ -168,11 +237,10 @@ contract SwapRouter02ExecutorTest is Test, PermitSignature, GasSnapshot, DeployP }); multicallData[0] = abi.encodeWithSelector(ISwapRouter02.exactInput.selector, exactInputParams); - vm.expectRevert("TRANSFER_FAILED"); - reactor.execute( + vm.expectRevert("TRANSFER_FROM_FAILED"); + swapRouter02Executor.execute( SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), - swapRouter02Executor, - abi.encode(tokensToApproveForSwapRouter02, multicallData) + abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData) ); } @@ -214,6 +282,9 @@ contract SwapRouter02ExecutorTest is Test, PermitSignature, GasSnapshot, DeployP 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), @@ -223,8 +294,8 @@ contract SwapRouter02ExecutorTest is Test, PermitSignature, GasSnapshot, DeployP }); multicallData[0] = abi.encodeWithSelector(ISwapRouter02.exactInput.selector, exactInputParams); - reactor.executeBatch( - signedOrders, swapRouter02Executor, abi.encode(tokensToApproveForSwapRouter02, multicallData) + swapRouter02Executor.executeBatch( + signedOrders, abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData) ); assertEq(tokenOut.balanceOf(swapper), 3 ether); assertEq(tokenIn.balanceOf(swapper), 6 ether); @@ -260,15 +331,13 @@ contract SwapRouter02ExecutorTest is Test, PermitSignature, GasSnapshot, DeployP vm.prank(address(0xbeef)); vm.expectRevert(SwapRouter02Executor.CallerNotWhitelisted.selector); - reactor.execute( + swapRouter02Executor.execute( SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), - swapRouter02Executor, abi.encode(tokensToApproveForSwapRouter02, multicallData) ); } // Very similar to `testReactorCallback`, but do not vm.prank the reactor when calling `reactorCallback`, so reverts - // in function testMsgSenderNotReactor() public { OutputToken[] memory outputs = new OutputToken[](1); outputs[0].token = address(tokenOut); @@ -284,7 +353,7 @@ contract SwapRouter02ExecutorTest is Test, PermitSignature, GasSnapshot, DeployP amountOutMinimum: 0 }); multicallData[0] = abi.encodeWithSelector(ISwapRouter02.exactInput.selector, exactInputParams); - bytes memory fillData = abi.encode(tokensToApproveForSwapRouter02, multicallData); + bytes memory callbackData = abi.encode(tokensToApproveForSwapRouter02, multicallData); ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](1); bytes memory sig = hex"1234"; @@ -298,7 +367,7 @@ contract SwapRouter02ExecutorTest is Test, PermitSignature, GasSnapshot, DeployP tokenIn.mint(address(swapRouter02Executor), ONE); tokenOut.mint(address(mockSwapRouter), ONE); vm.expectRevert(SwapRouter02Executor.MsgSenderNotReactor.selector); - swapRouter02Executor.reactorCallback(resolvedOrders, address(this), fillData); + swapRouter02Executor.reactorCallback(resolvedOrders, callbackData); } function testUnwrapWETH() public { diff --git a/test/fill-macros/DirectTakerFillMacro.t.sol b/test/fill-macros/DirectTakerFillMacro.t.sol index 45feeb48..0ddf6551 100644 --- a/test/fill-macros/DirectTakerFillMacro.t.sol +++ b/test/fill-macros/DirectTakerFillMacro.t.sol @@ -59,15 +59,9 @@ contract DirectFillerFillMacroTest is Test, PermitSignature, GasSnapshot, Deploy tokenIn1.forceApprove(swapper2, address(permit2), type(uint256).max); tokenIn2.forceApprove(swapper2, address(permit2), type(uint256).max); tokenIn3.forceApprove(swapper2, address(permit2), type(uint256).max); - tokenOut1.forceApprove(directFiller, address(permit2), type(uint256).max); - tokenOut2.forceApprove(directFiller, address(permit2), type(uint256).max); - tokenOut3.forceApprove(directFiller, address(permit2), type(uint256).max); - vm.prank(directFiller); - permit2.approve(address(tokenOut1), address(reactor), type(uint160).max, type(uint48).max); - vm.prank(directFiller); - permit2.approve(address(tokenOut2), address(reactor), type(uint160).max, type(uint48).max); - vm.prank(directFiller); - permit2.approve(address(tokenOut3), address(reactor), type(uint160).max, type(uint48).max); + tokenOut1.forceApprove(directFiller, address(reactor), type(uint256).max); + tokenOut2.forceApprove(directFiller, address(reactor), type(uint256).max); + tokenOut3.forceApprove(directFiller, address(reactor), type(uint256).max); } // Execute a single order made by swapper1, input = 1 tokenIn1 and outputs = [2 tokenOut1]. @@ -88,16 +82,37 @@ contract DirectFillerFillMacroTest is Test, PermitSignature, GasSnapshot, Deploy vm.prank(directFiller); snapStart("DirectFillerFillMacroSingleOrder"); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - IReactorCallback(address(1)), - bytes("") - ); + reactor.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order))); snapEnd(); assertEq(tokenOut1.balanceOf(swapper1), outputAmount); assertEq(tokenIn1.balanceOf(directFiller), inputAmount); } + // Execute a single order made by swapper1, input = 1 tokenIn1 and outputs = [2 tokenOut1]. + function testFillDataPassed() public { + uint256 inputAmount = 10 ** 18; + uint256 outputAmount = 2 * inputAmount; + + tokenIn1.mint(address(swapper1), inputAmount); + tokenOut1.mint(directFiller, outputAmount); + + DutchOrder memory order = DutchOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper1).withDeadline(block.timestamp + 100), + decayStartTime: block.timestamp, + decayEndTime: block.timestamp + 100, + input: DutchInput(tokenIn1, inputAmount, inputAmount), + outputs: OutputsBuilder.singleDutch(address(tokenOut1), outputAmount, outputAmount, swapper1) + }); + + vm.prank(directFiller); + // throws because attempting to call reactorCallback on an EOA + // and solidity high level calls add a codesize check + vm.expectRevert(); + reactor.executeWithCallback( + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), hex"" + ); + } + // The same as testSingleOrder, but with a 10% fee. function testSingleOrderWithFee() public { address feeRecipient = address(1); @@ -126,11 +141,7 @@ contract DirectFillerFillMacroTest is Test, PermitSignature, GasSnapshot, Deploy vm.prank(directFiller); snapStart("DirectFillerFillMacroSingleOrderWithFee"); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - IReactorCallback(address(1)), - bytes("") - ); + reactor.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order))); snapEnd(); assertEq(tokenOut1.balanceOf(swapper1), outputAmount); assertEq(tokenOut1.balanceOf(address(feeRecipient)), outputAmount * feeBps / 10000); @@ -169,7 +180,7 @@ contract DirectFillerFillMacroTest is Test, PermitSignature, GasSnapshot, Deploy signedOrders[1] = SignedOrder(abi.encode(order2), signOrder(swapperPrivateKey2, address(permit2), order2)); vm.prank(directFiller); snapStart("DirectFillerFillMacroTwoOrders"); - reactor.executeBatch(signedOrders, IReactorCallback(address(1)), bytes("")); + reactor.executeBatch(signedOrders); snapEnd(); assertEq(tokenOut1.balanceOf(swapper1), 2 * ONE); @@ -240,7 +251,7 @@ contract DirectFillerFillMacroTest is Test, PermitSignature, GasSnapshot, Deploy signedOrders[2] = SignedOrder(abi.encode(order3), signOrder(swapperPrivateKey2, address(permit2), order3)); vm.prank(directFiller); snapStart("DirectFillerFillMacroThreeOrdersWithFees"); - reactor.executeBatch(signedOrders, IReactorCallback(address(1)), bytes("")); + reactor.executeBatch(signedOrders); snapEnd(); assertEq(tokenOut1.balanceOf(swapper1), ONE); @@ -273,17 +284,13 @@ contract DirectFillerFillMacroTest is Test, PermitSignature, GasSnapshot, Deploy vm.prank(directFiller); vm.expectRevert(bytes("TRANSFER_FROM_FAILED")); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - IReactorCallback(address(1)), - bytes("") - ); + reactor.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order))); } // Same test as `testSingleOrder`, but filler lacks approval for tokenOut1 function testFillerLacksApproval() public { vm.prank(directFiller); - permit2.approve(address(tokenOut1), address(reactor), 0, type(uint48).max); + tokenOut1.approve(address(reactor), 0); uint256 inputAmount = 10 ** 18; uint256 outputAmount = 2 * inputAmount; @@ -300,11 +307,7 @@ contract DirectFillerFillMacroTest is Test, PermitSignature, GasSnapshot, Deploy }); vm.prank(directFiller); - vm.expectRevert(abi.encodeWithSignature("InsufficientAllowance(uint256)", 0)); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order)), - IReactorCallback(address(1)), - bytes("") - ); + vm.expectRevert("TRANSFER_FROM_FAILED"); + reactor.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey1, address(permit2), order))); } } diff --git a/test/integration/SwapRouter02ExecutorIntegration.t.sol b/test/integration/SwapRouter02ExecutorIntegration.t.sol index 489f098f..978a3a0c 100644 --- a/test/integration/SwapRouter02ExecutorIntegration.t.sol +++ b/test/integration/SwapRouter02ExecutorIntegration.t.sol @@ -80,6 +80,9 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { }); address[] memory tokensToApproveForSwapRouter02 = new address[](1); tokensToApproveForSwapRouter02[0] = address(WETH); + + address[] memory tokensToApproveForReactor = new address[](1); + tokensToApproveForReactor[0] = address(DAI); bytes[] memory multicallData1 = new bytes[](1); bytes[] memory multicallData2 = new bytes[](1); @@ -87,10 +90,9 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { address(WETH), address(DAI), 500, address(swapRouter02Executor), 2 * ONE, 3000 * ONE, 0 ); multicallData1[0] = abi.encodeWithSelector(ISwapRouter02.exactInputSingle.selector, params1); - dloReactor.execute( + swapRouter02Executor.execute( SignedOrder(abi.encode(order1), signOrder(swapperPrivateKey, address(PERMIT2), order1)), - swapRouter02Executor, - abi.encode(tokensToApproveForSwapRouter02, multicallData1) + abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData1) ); assertEq(WETH.balanceOf(swapper), ONE); assertEq(DAI.balanceOf(swapper), 3000 * ONE); @@ -99,10 +101,9 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { ExactInputSingleParams memory params2 = ExactInputSingleParams(address(WETH), address(DAI), 500, address(swapRouter02Executor), ONE, 1600 * ONE, 0); multicallData2[0] = abi.encodeWithSelector(ISwapRouter02.exactInputSingle.selector, params2); - dloReactor.execute( + swapRouter02Executor.execute( SignedOrder(abi.encode(order2), signOrder(swapperPrivateKey, address(PERMIT2), order2)), - swapRouter02Executor, - abi.encode(new address[](0), multicallData2) + abi.encode(new address[](0), new address[](0), multicallData2) ); assertEq(WETH.balanceOf(swapper), 0); assertEq(DAI.balanceOf(swapper), 4600 * ONE); @@ -122,6 +123,9 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { address[] memory tokensToApproveForSwapRouter02 = new address[](1); tokensToApproveForSwapRouter02[0] = address(WETH); + + address[] memory tokensToApproveForReactor = new address[](1); + tokensToApproveForReactor[0] = address(DAI); bytes[] memory multicallData = new bytes[](1); address[] memory path = new address[](2); path[0] = address(WETH); @@ -129,10 +133,9 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { multicallData[0] = abi.encodeWithSelector( ISwapRouter02.swapExactTokensForTokens.selector, 2 * ONE, 3000 * ONE, path, address(swapRouter02Executor) ); - dloReactor.execute( + swapRouter02Executor.execute( SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order)), - swapRouter02Executor, - abi.encode(tokensToApproveForSwapRouter02, multicallData) + abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData) ); assertEq(WETH.balanceOf(swapper), ONE); assertEq(DAI.balanceOf(swapper), 3000 * ONE); @@ -153,6 +156,9 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { address[] memory tokensToApproveForSwapRouter02 = new address[](1); tokensToApproveForSwapRouter02[0] = address(WETH); + + address[] memory tokensToApproveForReactor = new address[](1); + tokensToApproveForReactor[0] = address(USDT); bytes[] memory multicallData = new bytes[](1); address[] memory path = new address[](2); path[0] = address(WETH); @@ -160,10 +166,9 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { multicallData[0] = abi.encodeWithSelector( ISwapRouter02.swapExactTokensForTokens.selector, 2 * ONE, output, path, address(swapRouter02Executor) ); - dloReactor.execute( + swapRouter02Executor.execute( SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order)), - swapRouter02Executor, - abi.encode(tokensToApproveForSwapRouter02, multicallData) + abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData) ); assertEq(WETH.balanceOf(swapper), ONE); assertEq(USDT.balanceOf(swapper), output); @@ -189,6 +194,9 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { address[] memory tokensToApproveForSwapRouter02 = new address[](1); tokensToApproveForSwapRouter02[0] = address(USDT); + + address[] memory tokensToApproveForReactor = new address[](1); + tokensToApproveForReactor[0] = address(WETH); bytes[] memory multicallData = new bytes[](1); address[] memory path = new address[](2); path[0] = address(USDT); @@ -196,10 +204,9 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { multicallData[0] = abi.encodeWithSelector( ISwapRouter02.swapExactTokensForTokens.selector, input, output, path, address(swapRouter02Executor) ); - dloReactor.execute( + swapRouter02Executor.execute( SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order)), - swapRouter02Executor, - abi.encode(tokensToApproveForSwapRouter02, multicallData) + abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData) ); assertEq(USDT.balanceOf(swapper), 0); assertEq(WETH.balanceOf(swapper), 4 * ONE); @@ -218,6 +225,9 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { address[] memory tokensToApproveForSwapRouter02 = new address[](1); tokensToApproveForSwapRouter02[0] = address(WETH); + + address[] memory tokensToApproveForReactor = new address[](1); + tokensToApproveForReactor[0] = address(DAI); bytes[] memory multicallData = new bytes[](1); address[] memory path = new address[](2); path[0] = address(WETH); @@ -226,10 +236,9 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { ISwapRouter02.swapExactTokensForTokens.selector, 2 * ONE, 4000 * ONE, path, address(swapRouter02Executor) ); vm.expectRevert("Too little received"); - dloReactor.execute( + swapRouter02Executor.execute( SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order)), - swapRouter02Executor, - abi.encode(tokensToApproveForSwapRouter02, multicallData) + abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData) ); } @@ -245,6 +254,7 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { ERC20[] memory tokensToApproveForSwapRouter02 = new ERC20[](2); tokensToApproveForSwapRouter02[0] = DAI; tokensToApproveForSwapRouter02[1] = UNI; + bytes[] memory multicallData = new bytes[](3); address[] memory daiToEthPath = new address[](2); daiToEthPath[0] = address(DAI); @@ -282,6 +292,8 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { address[] memory tokensToApproveForSwapRouter02 = new address[](1); tokensToApproveForSwapRouter02[0] = address(DAI); + + address[] memory tokensToApproveForReactor = new address[](0); bytes[] memory multicallData = new bytes[](2); address[] memory path = new address[](2); path[0] = address(DAI); @@ -298,10 +310,9 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { vm.prank(0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643); DAI.transfer(swapper, 2000 * ONE); - dloReactor.execute( + swapRouter02Executor.execute( SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order)), - swapRouter02Executor, - abi.encode(tokensToApproveForSwapRouter02, multicallData) + abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData) ); assertEq(DAI.balanceOf(swapper), 0); assertEq(DAI.balanceOf(address(swapRouter02Executor)), 0); @@ -321,6 +332,8 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { address[] memory tokensToApproveForSwapRouter02 = new address[](1); tokensToApproveForSwapRouter02[0] = address(DAI); + + address[] memory tokensToApproveForReactor = new address[](0); bytes[] memory multicallData = new bytes[](2); address[] memory path = new address[](2); path[0] = address(DAI); @@ -339,10 +352,9 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { DAI.transfer(swapper, 2000 * ONE); vm.expectRevert("Too little received"); - dloReactor.execute( + swapRouter02Executor.execute( SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order)), - swapRouter02Executor, - abi.encode(tokensToApproveForSwapRouter02, multicallData) + abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData) ); } @@ -379,6 +391,8 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { address[] memory tokensToApproveForSwapRouter02 = new address[](1); tokensToApproveForSwapRouter02[0] = address(DAI); + + address[] memory tokensToApproveForReactor = new address[](0); bytes[] memory multicallData = new bytes[](2); address[] memory path = new address[](2); path[0] = address(DAI); @@ -388,8 +402,8 @@ contract SwapRouter02IntegrationTest is Test, PermitSignature { ); multicallData[1] = abi.encodeWithSelector(ISwapRouter02.unwrapWETH9.selector, 0, address(swapRouter02Executor)); - dloReactor.executeBatch( - signedOrders, swapRouter02Executor, abi.encode(tokensToApproveForSwapRouter02, multicallData) + swapRouter02Executor.executeBatch( + signedOrders, abi.encode(tokensToApproveForSwapRouter02, tokensToApproveForReactor, multicallData) ); assertEq(swapper.balance, ONE); assertEq(swapper2.balance, ONE / 2); diff --git a/test/lib/ExpectedBalanceLib.t.sol b/test/lib/ExpectedBalanceLib.t.sol deleted file mode 100644 index ece24629..00000000 --- a/test/lib/ExpectedBalanceLib.t.sol +++ /dev/null @@ -1,387 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {MockERC20} from "../util/mock/MockERC20.sol"; -import {OutputsBuilder} from "../util/OutputsBuilder.sol"; -import {OrderInfo, ResolvedOrder, OutputToken} from "../../src/base/ReactorStructs.sol"; -import {ExpectedBalanceLib, ExpectedBalance} from "../../src/lib/ExpectedBalanceLib.sol"; -import {NATIVE} from "../../src/lib/CurrencyLibrary.sol"; -import {MockExpectedBalanceLib} from "../util/mock/MockExpectedBalanceLib.sol"; -import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol"; - -contract ExpectedBalanceTest is Test { - using ExpectedBalanceLib for ResolvedOrder[]; - using ExpectedBalanceLib for ExpectedBalance[]; - using OrderInfoBuilder for OrderInfo; - - struct TestGetExpectedBalanceConfig { - address recipient; - uint64 amount; - uint64 preBalance; - bool useToken1; - } - - address recipient1; - address recipient2; - MockERC20 token1; - MockERC20 token2; - MockExpectedBalanceLib mockExpectedBalanceLib; - - function setUp() public { - recipient1 = makeAddr("recipient1"); - recipient2 = makeAddr("recipient2"); - token1 = new MockERC20("Mock1", "MOCK1", 18); - token2 = new MockERC20("Mock2", "MOCK2", 18); - mockExpectedBalanceLib = new MockExpectedBalanceLib(); - } - - function testGetExpectedBalanceSingle(uint256 amount) public { - ResolvedOrder[] memory orders = new ResolvedOrder[](1); - orders[0].outputs = OutputsBuilder.single(address(token1), amount, recipient1); - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertEq(expectedBalances.length, 1); - assertEq(expectedBalances[0].token, address(token1)); - assertEq(expectedBalances[0].recipient, recipient1); - assertEq(expectedBalances[0].expectedBalance, amount); - } - - function testGetExpectedBalanceSingleNative(uint256 amount) public { - ResolvedOrder[] memory orders = new ResolvedOrder[](1); - orders[0].outputs = OutputsBuilder.single(NATIVE, amount, recipient1); - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertEq(expectedBalances.length, 1); - assertEq(expectedBalances[0].token, NATIVE); - assertEq(expectedBalances[0].recipient, recipient1); - assertEq(expectedBalances[0].expectedBalance, amount); - } - - function testGetExpectedBalanceSingleWithPreBalance(uint128 preAmount, uint128 amount) public { - ResolvedOrder[] memory orders = new ResolvedOrder[](1); - token1.mint(recipient1, preAmount); - orders[0].outputs = OutputsBuilder.single(address(token1), amount, recipient1); - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertEq(expectedBalances.length, 1); - assertEq(expectedBalances[0].token, address(token1)); - assertEq(expectedBalances[0].recipient, recipient1); - assertEq(expectedBalances[0].expectedBalance, uint256(amount) + preAmount); - } - - function testGetExpectedBalanceSingleWithPreBalanceNative(uint128 preAmount, uint128 amount) public { - ResolvedOrder[] memory orders = new ResolvedOrder[](1); - vm.deal(recipient1, preAmount); - orders[0].outputs = OutputsBuilder.single(NATIVE, amount, recipient1); - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertEq(expectedBalances.length, 1); - assertEq(expectedBalances[0].token, NATIVE); - assertEq(expectedBalances[0].recipient, recipient1); - assertEq(expectedBalances[0].expectedBalance, uint256(amount) + preAmount); - } - - function testGetExpectedBalanceMultiOutput(uint128 amount1, uint128 amount2) public { - ResolvedOrder[] memory orders = new ResolvedOrder[](1); - uint256[] memory amounts = new uint256[](2); - amounts[0] = amount1; - amounts[1] = amount2; - orders[0].outputs = OutputsBuilder.multiple(address(token1), amounts, recipient1); - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertEq(expectedBalances.length, 1); - assertEq(expectedBalances[0].token, address(token1)); - assertEq(expectedBalances[0].recipient, recipient1); - assertEq(expectedBalances[0].expectedBalance, uint256(amount1) + amount2); - } - - function testGetExpectedBalanceMultiOutputSomeDuplicate(uint128 amount) public { - ResolvedOrder[] memory orders = new ResolvedOrder[](1); - orders[0].outputs = new OutputToken[](3); - orders[0].outputs[0] = OutputToken(NATIVE, amount, recipient1); - orders[0].outputs[1] = OutputToken(NATIVE, amount, recipient2); - orders[0].outputs[2] = OutputToken(NATIVE, amount, recipient2); - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertEq(expectedBalances.length, 2); - assertEq(expectedBalances[0].token, NATIVE); - assertEq(expectedBalances[0].recipient, recipient1); - assertEq(expectedBalances[0].expectedBalance, amount); - assertEq(expectedBalances[1].token, NATIVE); - assertEq(expectedBalances[1].recipient, recipient2); - assertEq(expectedBalances[1].expectedBalance, uint256(amount) * 2); - } - - function testGetExpectedBalanceMultiOutputWithPreBalance(uint128 preAmount, uint64 amount1, uint64 amount2) - public - { - ResolvedOrder[] memory orders = new ResolvedOrder[](1); - token1.mint(recipient1, preAmount); - uint256[] memory amounts = new uint256[](2); - amounts[0] = amount1; - amounts[1] = amount2; - orders[0].outputs = OutputsBuilder.multiple(address(token1), amounts, recipient1); - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertEq(expectedBalances.length, 1); - assertEq(expectedBalances[0].token, address(token1)); - assertEq(expectedBalances[0].recipient, recipient1); - assertEq(expectedBalances[0].expectedBalance, uint256(preAmount) + amount1 + amount2); - } - - function testGetExpectedBalanceMultiOutputMultiToken(uint256 amount) public { - ResolvedOrder[] memory orders = new ResolvedOrder[](1); - orders[0].outputs = new OutputToken[](2); - orders[0].outputs[0] = OutputToken(address(token1), amount, recipient1); - orders[0].outputs[1] = OutputToken(address(token2), amount, recipient1); - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertEq(expectedBalances.length, 2); - assertEq(expectedBalances[0].token, address(token1)); - assertEq(expectedBalances[0].recipient, recipient1); - assertEq(expectedBalances[0].expectedBalance, amount); - assertEq(expectedBalances[1].token, address(token2)); - assertEq(expectedBalances[1].recipient, recipient1); - assertEq(expectedBalances[1].expectedBalance, amount); - } - - function testGetExpectedBalanceMultipleRecipients(uint128 amount1, uint128 amount2) public { - ResolvedOrder[] memory orders = new ResolvedOrder[](1); - orders[0].outputs = new OutputToken[](2); - orders[0].outputs[0] = OutputToken(address(token1), amount1, recipient1); - orders[0].outputs[1] = OutputToken(address(token1), amount2, recipient2); - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertEq(expectedBalances.length, 2); - assertEq(expectedBalances[0].token, address(token1)); - assertEq(expectedBalances[0].recipient, recipient1); - assertEq(expectedBalances[0].expectedBalance, amount1); - assertEq(expectedBalances[1].token, address(token1)); - assertEq(expectedBalances[1].recipient, recipient2); - assertEq(expectedBalances[1].expectedBalance, amount2); - } - - function testGetExpectedBalanceMultipleRecipientsWithPreBalance(uint128 preAmount, uint128 amount1, uint128 amount2) - public - { - ResolvedOrder[] memory orders = new ResolvedOrder[](1); - token1.mint(recipient1, preAmount); - token1.mint(recipient2, preAmount); - orders[0].outputs = new OutputToken[](2); - orders[0].outputs[0] = OutputToken(address(token1), amount1, recipient1); - orders[0].outputs[1] = OutputToken(address(token1), amount2, recipient2); - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertEq(expectedBalances.length, 2); - assertEq(expectedBalances[0].token, address(token1)); - assertEq(expectedBalances[0].recipient, recipient1); - assertEq(expectedBalances[0].expectedBalance, uint256(preAmount) + amount1); - assertEq(expectedBalances[1].token, address(token1)); - assertEq(expectedBalances[1].recipient, recipient2); - assertEq(expectedBalances[1].expectedBalance, uint256(preAmount) + amount2); - } - - function testGetExpectedBalanceMultiOrder(uint128 amount1, uint128 amount2) public { - ResolvedOrder[] memory orders = new ResolvedOrder[](2); - orders[0].outputs = OutputsBuilder.single(address(token1), amount1, recipient1); - orders[1].outputs = OutputsBuilder.single(address(token1), amount2, recipient1); - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertEq(expectedBalances.length, 1); - assertEq(expectedBalances[0].token, address(token1)); - assertEq(expectedBalances[0].recipient, recipient1); - assertEq(expectedBalances[0].expectedBalance, uint256(amount1) + amount2); - } - - function testGetExpectedBalanceMultiOrderMultiOutput(uint64 amount1, uint64 amount2) public { - ResolvedOrder[] memory orders = new ResolvedOrder[](2); - uint256[] memory amounts = new uint256[](2); - amounts[0] = amount1; - amounts[1] = amount2; - orders[0].outputs = OutputsBuilder.multiple(address(token1), amounts, recipient1); - orders[1].outputs = OutputsBuilder.multiple(address(token1), amounts, recipient1); - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertEq(expectedBalances.length, 1); - assertEq(expectedBalances[0].token, address(token1)); - assertEq(expectedBalances[0].recipient, recipient1); - assertEq(expectedBalances[0].expectedBalance, uint256(amount1) + amount2 + amount1 + amount2); - } - - function testGetExpectedBalanceMultiOrderMultiRecipients(uint128 amount1, uint128 amount2) public { - ResolvedOrder[] memory orders = new ResolvedOrder[](2); - orders[0].outputs = OutputsBuilder.single(address(token1), amount1, recipient1); - orders[1].outputs = OutputsBuilder.single(address(token1), amount2, recipient2); - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertEq(expectedBalances.length, 2); - assertEq(expectedBalances[0].token, address(token1)); - assertEq(expectedBalances[0].recipient, recipient1); - assertEq(expectedBalances[0].expectedBalance, uint256(amount1)); - assertEq(expectedBalances[1].token, address(token1)); - assertEq(expectedBalances[1].recipient, recipient2); - assertEq(expectedBalances[1].expectedBalance, uint256(amount2)); - } - - // asserting no reverts and sane handling on arbitrary output lists - function testGetExpectedBalanceManyOutputs(TestGetExpectedBalanceConfig[] memory test) public { - ResolvedOrder[] memory orders = new ResolvedOrder[](1); - orders[0].outputs = new OutputToken[](test.length); - for (uint256 i = 0; i < test.length; i++) { - TestGetExpectedBalanceConfig memory config = test[i]; - address token = config.useToken1 ? address(token1) : address(token2); - MockERC20(token).mint(config.recipient, config.preBalance); - orders[0].outputs[i] = OutputToken(token, config.amount, config.recipient); - } - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertFuzzSanity(test, expectedBalances); - } - - // asserting no reverts and sane handling on arbitrary output lists - function testGetExpectedBalanceManyOrders(TestGetExpectedBalanceConfig[] memory test) public { - ResolvedOrder[] memory orders = new ResolvedOrder[](test.length); - for (uint256 i = 0; i < test.length; i++) { - TestGetExpectedBalanceConfig memory config = test[i]; - address token = config.useToken1 ? address(token1) : address(token2); - MockERC20(token).mint(config.recipient, config.preBalance); - orders[i].outputs = OutputsBuilder.single(token, config.amount, config.recipient); - } - - ExpectedBalance[] memory expectedBalances = orders.getExpectedBalances(); - assertFuzzSanity(test, expectedBalances); - } - - function assertFuzzSanity(TestGetExpectedBalanceConfig[] memory test, ExpectedBalance[] memory expectedBalances) - internal - { - assertLe(expectedBalances.length, test.length); - - // assert each recipient in test with nonzero value has an entry - uint256 totalSum1; - uint256 totalSum2; - for (uint256 i = 0; i < test.length; i++) { - TestGetExpectedBalanceConfig memory config = test[i]; - config.useToken1 - ? totalSum1 += uint256(config.amount) + config.preBalance - : totalSum2 += uint256(config.amount) + config.preBalance; - - if (config.amount == 0) { - continue; - } - bool found = false; - for (uint256 j = 0; j < expectedBalances.length; j++) { - address token = config.useToken1 ? address(token1) : address(token2); - if (expectedBalances[j].recipient == config.recipient && expectedBalances[j].token == token) { - assertGe(expectedBalances[j].expectedBalance, uint256(config.amount) + config.preBalance); - found = true; - break; - } - } - assertTrue(found); - } - - // assert totalSums - uint256 sum1; - uint256 sum2; - for (uint256 i = 0; i < expectedBalances.length; i++) { - if (expectedBalances[i].token == address(token1)) { - sum1 += expectedBalances[i].expectedBalance; - } else { - sum2 += expectedBalances[i].expectedBalance; - } - } - assertEq(sum1, totalSum1); - assertEq(sum2, totalSum2); - } - - // check tests - - function testCheck(uint256 expected, uint256 balance) public { - vm.assume(balance >= expected); - ExpectedBalance[] memory expectedBalances = new ExpectedBalance[](1); - expectedBalances[0] = ExpectedBalance(recipient1, address(token1), expected); - token1.mint(recipient1, balance); - mockExpectedBalanceLib.check(expectedBalances); - } - - function testCheckNative(uint256 expected, uint256 balance) public { - vm.assume(balance >= expected); - ExpectedBalance[] memory expectedBalances = new ExpectedBalance[](1); - expectedBalances[0] = ExpectedBalance(recipient1, NATIVE, expected); - vm.deal(recipient1, balance); - mockExpectedBalanceLib.check(expectedBalances); - } - - function testCheckMany(uint128 expected, uint128 balance) public { - vm.assume(balance >= expected); - ExpectedBalance[] memory expectedBalances = new ExpectedBalance[](4); - expectedBalances[0] = ExpectedBalance(recipient1, address(token1), expected); - expectedBalances[1] = ExpectedBalance(recipient1, address(token2), expected); - expectedBalances[2] = ExpectedBalance(recipient2, address(token1), expected); - expectedBalances[3] = ExpectedBalance(recipient2, address(token2), expected); - token1.mint(recipient1, balance); - token2.mint(recipient1, balance); - token1.mint(recipient2, balance); - token2.mint(recipient2, balance); - mockExpectedBalanceLib.check(expectedBalances); - } - - function testCheckInsufficientOutput(uint256 expected, uint256 balance) public { - vm.assume(balance < expected); - ExpectedBalance[] memory expectedBalances = new ExpectedBalance[](1); - expectedBalances[0] = ExpectedBalance(recipient1, address(token1), expected); - token1.mint(recipient1, balance); - vm.expectRevert(abi.encodeWithSelector(ExpectedBalanceLib.InsufficientOutput.selector, balance, expected)); - mockExpectedBalanceLib.check(expectedBalances); - } - - function testCheckInsufficientOutputNative(uint256 expected, uint256 balance) public { - vm.assume(balance < expected); - ExpectedBalance[] memory expectedBalances = new ExpectedBalance[](1); - expectedBalances[0] = ExpectedBalance(recipient1, NATIVE, expected); - vm.deal(recipient1, balance); - vm.expectRevert(abi.encodeWithSelector(ExpectedBalanceLib.InsufficientOutput.selector, balance, expected)); - mockExpectedBalanceLib.check(expectedBalances); - } - - function testCheckInsufficientOutputFirstOfMany(uint128 expected, uint128 balance) public { - vm.assume(balance < expected); - ExpectedBalance[] memory expectedBalances = new ExpectedBalance[](3); - expectedBalances[0] = ExpectedBalance(recipient1, address(token1), expected); - expectedBalances[1] = ExpectedBalance(recipient2, address(token1), expected); - expectedBalances[2] = ExpectedBalance(recipient2, address(token2), expected); - token1.mint(recipient1, balance); - token1.mint(recipient2, expected); - token2.mint(recipient2, expected); - vm.expectRevert(abi.encodeWithSelector(ExpectedBalanceLib.InsufficientOutput.selector, balance, expected)); - mockExpectedBalanceLib.check(expectedBalances); - } - - function testCheckInsufficientOutputMiddleOfMany(uint128 expected, uint128 balance) public { - vm.assume(balance < expected); - ExpectedBalance[] memory expectedBalances = new ExpectedBalance[](3); - expectedBalances[0] = ExpectedBalance(recipient1, address(token1), expected); - expectedBalances[1] = ExpectedBalance(recipient2, address(token1), expected); - expectedBalances[2] = ExpectedBalance(recipient2, address(token2), expected); - token1.mint(recipient1, expected); - token1.mint(recipient2, balance); - token2.mint(recipient2, expected); - vm.expectRevert(abi.encodeWithSelector(ExpectedBalanceLib.InsufficientOutput.selector, balance, expected)); - mockExpectedBalanceLib.check(expectedBalances); - } - - function testCheckInsufficientOutputLastOfMany(uint128 expected, uint128 balance) public { - vm.assume(balance < expected); - ExpectedBalance[] memory expectedBalances = new ExpectedBalance[](3); - expectedBalances[0] = ExpectedBalance(recipient1, address(token1), expected); - expectedBalances[1] = ExpectedBalance(recipient2, address(token1), expected); - expectedBalances[2] = ExpectedBalance(recipient2, address(token2), expected); - token1.mint(recipient1, expected); - token1.mint(recipient2, expected); - token2.mint(recipient2, balance); - vm.expectRevert(abi.encodeWithSelector(ExpectedBalanceLib.InsufficientOutput.selector, balance, expected)); - mockExpectedBalanceLib.check(expectedBalances); - } -} diff --git a/test/reactors/DutchOrderReactor.t.sol b/test/reactors/DutchOrderReactor.t.sol index 335daa01..0d39ef36 100644 --- a/test/reactors/DutchOrderReactor.t.sol +++ b/test/reactors/DutchOrderReactor.t.sol @@ -14,7 +14,7 @@ import { } from "../../src/reactors/DutchOrderReactor.sol"; import {OrderInfo, InputToken, OutputToken, SignedOrder} from "../../src/base/ReactorStructs.sol"; import {DutchDecayLib} from "../../src/lib/DutchDecayLib.sol"; -import {ExpectedBalanceLib} from "../../src/lib/ExpectedBalanceLib.sol"; +import {CurrencyLibrary} from "../../src/lib/CurrencyLibrary.sol"; import {NATIVE} from "../../src/lib/CurrencyLibrary.sol"; import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol"; import {MockDutchOrderReactor} from "../util/mock/MockDutchOrderReactor.sol"; @@ -401,8 +401,7 @@ contract DutchOrderReactorExecuteTest is PermitSignature, DeployPermit2, BaseRea } function createReactor() public override returns (BaseReactor) { - reactor = new DutchOrderReactor(permit2, PROTOCOL_FEE_OWNER); - return reactor; + return new DutchOrderReactor(permit2, PROTOCOL_FEE_OWNER); } /// @dev Create and return a basic single Dutch limit order along with its signature, orderHash, and orderInfo @@ -502,7 +501,7 @@ contract DutchOrderReactorExecuteTest is PermitSignature, DeployPermit2, BaseRea emit Fill(orders[1].hash(), address(this), swapper, orders[1].info.nonce); vm.expectEmit(false, false, false, true); emit Fill(orders[2].hash(), address(this), swapper2, orders[2].info.nonce); - reactor.executeBatch(signedOrders, fillContract, bytes("")); + fillContract.executeBatch(signedOrders); assertEq(tokenOut.balanceOf(swapper), 6 ether); assertEq(tokenOut.balanceOf(swapper2), 12 ether); assertEq(tokenIn.balanceOf(address(fillContract)), 6 ether); @@ -539,13 +538,13 @@ contract DutchOrderReactorExecuteTest is PermitSignature, DeployPermit2, BaseRea }); vm.expectRevert(); - reactor.executeBatch(generateSignedOrders(orders), fillContract, bytes("")); + fillContract.executeBatch(generateSignedOrders(orders)); } // Execute 2 dutch orders, but executor does not send enough output tokens to the recipient // should fail with InsufficientOutput error from balance checks function testExecuteBatchInsufficientOutputSent() public { - MockFillContractWithOutputOverride fill = new MockFillContractWithOutputOverride(); + MockFillContractWithOutputOverride fill = new MockFillContractWithOutputOverride(address(reactor)); uint256 inputAmount = 10 ** 18; uint256 outputAmount = 2 * inputAmount; @@ -572,14 +571,14 @@ contract DutchOrderReactorExecuteTest is PermitSignature, DeployPermit2, BaseRea }); fill.setOutputAmount(outputAmount); - vm.expectRevert(abi.encodeWithSelector(ExpectedBalanceLib.InsufficientOutput.selector, 4 ether, 6 ether)); - reactor.executeBatch(generateSignedOrders(orders), fill, bytes("")); + vm.expectRevert("TRANSFER_FROM_FAILED"); + fill.executeBatch(generateSignedOrders(orders)); } // Execute 2 dutch orders, but executor does not send enough output ETH to the recipient // should fail with InsufficientOutput error from balance checks function testExecuteBatchInsufficientOutputSentNative() public { - MockFillContractWithOutputOverride fill = new MockFillContractWithOutputOverride(); + MockFillContractWithOutputOverride fill = new MockFillContractWithOutputOverride(address(reactor)); uint256 inputAmount = 10 ** 18; uint256 outputAmount = inputAmount; @@ -606,8 +605,8 @@ contract DutchOrderReactorExecuteTest is PermitSignature, DeployPermit2, BaseRea }); fill.setOutputAmount(outputAmount / 2); - vm.expectRevert(abi.encodeWithSelector(ExpectedBalanceLib.InsufficientOutput.selector, 1 ether, 2 ether)); - reactor.executeBatch(generateSignedOrders(orders), fill, bytes("")); + vm.expectRevert(CurrencyLibrary.NativeTransferFailed.selector); + fill.executeBatch(generateSignedOrders(orders)); } function generateSignedOrders(DutchOrder[] memory orders) private view returns (SignedOrder[] memory result) { diff --git a/test/reactors/ExclusiveDutchOrderReactor.t.sol b/test/reactors/ExclusiveDutchOrderReactor.t.sol index 893a9876..6396564e 100644 --- a/test/reactors/ExclusiveDutchOrderReactor.t.sol +++ b/test/reactors/ExclusiveDutchOrderReactor.t.sol @@ -13,8 +13,7 @@ import { } from "../../src/reactors/ExclusiveDutchOrderReactor.sol"; import {OrderInfo, InputToken, SignedOrder, OutputToken} from "../../src/base/ReactorStructs.sol"; import {DutchDecayLib} from "../../src/lib/DutchDecayLib.sol"; -import {ExpectedBalanceLib} from "../../src/lib/ExpectedBalanceLib.sol"; -import {NATIVE} from "../../src/lib/CurrencyLibrary.sol"; +import {CurrencyLibrary, NATIVE} from "../../src/lib/CurrencyLibrary.sol"; import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol"; import {MockERC20} from "../util/mock/MockERC20.sol"; import {ExclusiveDutchOrderLib} from "../../src/lib/ExclusiveDutchOrderLib.sol"; @@ -34,8 +33,7 @@ contract ExclusiveDutchOrderReactorExecuteTest is PermitSignature, DeployPermit2 } function createReactor() public override returns (BaseReactor) { - reactor = new ExclusiveDutchOrderReactor(permit2, PROTOCOL_FEE_OWNER); - return reactor; + return new ExclusiveDutchOrderReactor(permit2, PROTOCOL_FEE_OWNER); } /// @dev Create and return a basic single Dutch limit order along with its signature, orderHash, and orderInfo @@ -143,7 +141,7 @@ contract ExclusiveDutchOrderReactorExecuteTest is PermitSignature, DeployPermit2 emit Fill(orders[1].hash(), address(this), swapper, orders[1].info.nonce); vm.expectEmit(false, false, false, true); emit Fill(orders[2].hash(), address(this), swapper2, orders[2].info.nonce); - reactor.executeBatch(signedOrders, fillContract, bytes("")); + fillContract.executeBatch(signedOrders); assertEq(tokenOut.balanceOf(swapper), 6 ether); assertEq(tokenOut.balanceOf(swapper2), 12 ether); assertEq(tokenIn.balanceOf(address(fillContract)), 6 ether); @@ -183,14 +181,14 @@ contract ExclusiveDutchOrderReactorExecuteTest is PermitSignature, DeployPermit2 outputs: OutputsBuilder.singleDutch(address(tokenOut), outputAmount * 2, outputAmount * 2, swapper) }); - vm.expectRevert("TRANSFER_FAILED"); - reactor.executeBatch(generateSignedOrders(orders), fillContract, bytes("")); + vm.expectRevert("TRANSFER_FROM_FAILED"); + fillContract.executeBatch(generateSignedOrders(orders)); } // Execute 2 dutch orders, but executor does not send enough output tokens to the recipient // should fail with InsufficientOutput error from balance checks function testExecuteBatchInsufficientOutputSent() public { - MockFillContractWithOutputOverride fill = new MockFillContractWithOutputOverride(); + MockFillContractWithOutputOverride fill = new MockFillContractWithOutputOverride(address(reactor)); uint256 inputAmount = 10 ** 18; uint256 outputAmount = 2 * inputAmount; @@ -221,14 +219,14 @@ contract ExclusiveDutchOrderReactorExecuteTest is PermitSignature, DeployPermit2 }); fill.setOutputAmount(outputAmount); - vm.expectRevert(abi.encodeWithSelector(ExpectedBalanceLib.InsufficientOutput.selector, 4 ether, 6 ether)); - reactor.executeBatch(generateSignedOrders(orders), fill, bytes("")); + vm.expectRevert("TRANSFER_FROM_FAILED"); + fillContract.executeBatch(generateSignedOrders(orders)); } // Execute 2 dutch orders, but executor does not send enough output ETH to the recipient // should fail with InsufficientOutput error from balance checks function testExecuteBatchInsufficientOutputSentNative() public { - MockFillContractWithOutputOverride fill = new MockFillContractWithOutputOverride(); + MockFillContractWithOutputOverride fill = new MockFillContractWithOutputOverride(address(reactor)); uint256 inputAmount = 10 ** 18; uint256 outputAmount = inputAmount; @@ -259,15 +257,16 @@ contract ExclusiveDutchOrderReactorExecuteTest is PermitSignature, DeployPermit2 }); fill.setOutputAmount(outputAmount / 2); - vm.expectRevert(abi.encodeWithSelector(ExpectedBalanceLib.InsufficientOutput.selector, 1 ether, 2 ether)); - reactor.executeBatch(generateSignedOrders(orders), fill, bytes("")); + vm.expectRevert(CurrencyLibrary.NativeTransferFailed.selector); + fillContract.executeBatch(generateSignedOrders(orders)); } function testExclusivitySucceeds(address exclusive, uint128 amountIn, uint128 amountOut) public { vm.assume(exclusive != address(0)); tokenIn.mint(address(swapper), amountIn); tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - tokenOut.mint(address(fillContract), amountOut); + tokenOut.mint(address(exclusive), amountOut); + tokenOut.forceApprove(exclusive, address(reactor), type(uint256).max); ExclusiveDutchOrder memory order = ExclusiveDutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100), @@ -283,12 +282,12 @@ contract ExclusiveDutchOrderReactorExecuteTest is PermitSignature, DeployPermit2 SignedOrder memory signedOrder = SignedOrder(abi.encode(order), sig); vm.expectEmit(false, false, false, true); - emit Fill(order.hash(), address(this), swapper, order.info.nonce); + emit Fill(order.hash(), address(exclusive), swapper, order.info.nonce); vm.prank(exclusive); - reactor.execute(signedOrder, fillContract, bytes("")); + reactor.execute(signedOrder); assertEq(tokenOut.balanceOf(swapper), amountOut); - assertEq(tokenIn.balanceOf(address(fillContract)), amountIn); + assertEq(tokenIn.balanceOf(address(exclusive)), amountIn); } function testExclusivityOverride( @@ -299,7 +298,7 @@ contract ExclusiveDutchOrderReactorExecuteTest is PermitSignature, DeployPermit2 uint256 overrideAmt ) public { vm.assume(exclusive != address(0)); - vm.assume(exclusive != caller); + vm.assume(exclusive != caller && exclusive != address(fillContract)); vm.assume(overrideAmt > 0 && overrideAmt < 10000); tokenIn.mint(address(swapper), amountIn); tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); @@ -322,7 +321,7 @@ contract ExclusiveDutchOrderReactorExecuteTest is PermitSignature, DeployPermit2 emit Fill(order.hash(), address(this), swapper, order.info.nonce); vm.prank(caller); - reactor.execute(signedOrder, fillContract, bytes("")); + fillContract.execute(signedOrder); assertEq(tokenOut.balanceOf(swapper), amountOut * (10000 + overrideAmt) / 10000); assertEq(tokenIn.balanceOf(address(fillContract)), amountIn); } @@ -367,7 +366,7 @@ contract ExclusiveDutchOrderReactorExecuteTest is PermitSignature, DeployPermit2 emit Fill(order.hash(), address(this), swapper, order.info.nonce); vm.prank(caller); - reactor.execute(signedOrder, fillContract, bytes("")); + fillContract.execute(signedOrder); assertEq(tokenOut.balanceOf(swapper), amountOutSum); assertEq(tokenIn.balanceOf(address(fillContract)), amountIn); } diff --git a/test/reactors/LimitOrderReactor.t.sol b/test/reactors/LimitOrderReactor.t.sol index 16dbfc4b..23f7c3d7 100644 --- a/test/reactors/LimitOrderReactor.t.sol +++ b/test/reactors/LimitOrderReactor.t.sol @@ -6,7 +6,6 @@ import {OrderInfo, InputToken, OutputToken, ResolvedOrder, SignedOrder} from ".. import {ReactorEvents} from "../../src/base/ReactorEvents.sol"; import {MockERC20} from "../util/mock/MockERC20.sol"; import {LimitOrder, LimitOrderLib} from "../../src/lib/LimitOrderLib.sol"; -import {ExpectedBalanceLib} from "../../src/lib/ExpectedBalanceLib.sol"; import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; import {DeployPermit2} from "../util/DeployPermit2.sol"; import {MockValidationContract} from "../util/mock/MockValidationContract.sol"; @@ -37,8 +36,7 @@ contract LimitOrderReactorTest is PermitSignature, DeployPermit2, BaseReactorTes } function createReactor() public override returns (BaseReactor) { - reactor = new LimitOrderReactor(permit2, PROTOCOL_FEE_OWNER); - return reactor; + return new LimitOrderReactor(permit2, PROTOCOL_FEE_OWNER); } /// @dev Create and return a basic LimitOrder along with its signature, hash, and orderInfo @@ -73,7 +71,7 @@ contract LimitOrderReactorTest is PermitSignature, DeployPermit2, BaseReactorTes vm.expectEmit(false, false, false, true, address(reactor)); emit Fill(orderHash, address(this), swapper, order.info.nonce); - reactor.execute(SignedOrder(abi.encode(order), sig), fillContract, bytes("")); + fillContract.execute(SignedOrder(abi.encode(order), sig)); assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - ONE); assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + ONE); @@ -82,7 +80,7 @@ contract LimitOrderReactorTest is PermitSignature, DeployPermit2, BaseReactorTes } function testExecuteInsufficientOutput() public { - MockFillContractWithOutputOverride fill = new MockFillContractWithOutputOverride(); + MockFillContractWithOutputOverride fill = new MockFillContractWithOutputOverride(address(reactor)); tokenOut.mint(address(fill), ONE); tokenIn.forceApprove(swapper, address(permit2), ONE); LimitOrder memory order = LimitOrder({ @@ -96,8 +94,8 @@ contract LimitOrderReactorTest is PermitSignature, DeployPermit2, BaseReactorTes fill.setOutputAmount(ONE); - vm.expectRevert(abi.encodeWithSelector(ExpectedBalanceLib.InsufficientOutput.selector, 1 ether, 2 ether)); - reactor.execute(SignedOrder(abi.encode(order), sig), fill, bytes("")); + vm.expectRevert("TRANSFER_FROM_FAILED"); + fillContract.execute(SignedOrder(abi.encode(order), sig)); } function testExecuteWithDuplicateOutputs() public { @@ -123,7 +121,7 @@ contract LimitOrderReactorTest is PermitSignature, DeployPermit2, BaseReactorTes vm.expectEmit(false, false, false, true, address(reactor)); emit Fill(orderHash, address(this), swapper, order.info.nonce); - reactor.execute(SignedOrder(abi.encode(order), sig), fillContract, bytes("")); + fillContract.execute(SignedOrder(abi.encode(order), sig)); assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - ONE); assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + ONE); @@ -146,7 +144,7 @@ contract LimitOrderReactorTest is PermitSignature, DeployPermit2, BaseReactorTes order.info.additionalValidationContract = IValidationCallback(address(0)); vm.expectRevert(InvalidSigner.selector); - reactor.execute(SignedOrder(abi.encode(order), sig), fillContract, bytes("")); + fillContract.execute(SignedOrder(abi.encode(order), sig)); } function testExecuteWithFeeOutput() public { @@ -177,7 +175,7 @@ contract LimitOrderReactorTest is PermitSignature, DeployPermit2, BaseReactorTes vm.expectEmit(false, false, false, true, address(reactor)); emit Fill(orderHash, address(this), swapper, order.info.nonce); - reactor.execute(SignedOrder(abi.encode(order), sig), fillContract, bytes("")); + fillContract.execute(SignedOrder(abi.encode(order), sig)); assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - ONE); assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + ONE); @@ -202,7 +200,7 @@ contract LimitOrderReactorTest is PermitSignature, DeployPermit2, BaseReactorTes ); vm.expectRevert(InvalidSigner.selector); - reactor.execute(SignedOrder(abi.encode(order), sig), fillContract, bytes("")); + fillContract.execute(SignedOrder(abi.encode(order), sig)); } function testExecuteIncorrectSpender() public { @@ -225,7 +223,7 @@ contract LimitOrderReactorTest is PermitSignature, DeployPermit2, BaseReactorTes ); vm.expectRevert(InvalidSigner.selector); - reactor.execute(SignedOrder(abi.encode(order), sig), fillContract, bytes("")); + fillContract.execute(SignedOrder(abi.encode(order), sig)); } function testExecuteIncorrectToken() public { @@ -241,6 +239,6 @@ contract LimitOrderReactorTest is PermitSignature, DeployPermit2, BaseReactorTes swapperPrivateKey, address(permit2), order.info, address(tokenOut), ONE, LIMIT_ORDER_TYPE_HASH, orderHash ); vm.expectRevert(InvalidSigner.selector); - reactor.execute(SignedOrder(abi.encode(order), sig), fillContract, bytes("")); + fillContract.execute(SignedOrder(abi.encode(order), sig)); } } diff --git a/test/util/mock/MockExpectedBalanceLib.sol b/test/util/mock/MockExpectedBalanceLib.sol deleted file mode 100644 index 6a710280..00000000 --- a/test/util/mock/MockExpectedBalanceLib.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {OrderInfo, ResolvedOrder} from "../../../src/base/ReactorStructs.sol"; -import {ExpectedBalanceLib, ExpectedBalance} from "../../../src/lib/ExpectedBalanceLib.sol"; - -// needed to assert reverts as vm.expectRevert doesnt work on internal library calls -contract MockExpectedBalanceLib { - using ExpectedBalanceLib for ExpectedBalance[]; - - function check(ExpectedBalance[] memory expected) external view { - expected.check(); - } -} diff --git a/test/util/mock/MockFillContract.sol b/test/util/mock/MockFillContract.sol index 2da003c1..b72b2c2f 100644 --- a/test/util/mock/MockFillContract.sol +++ b/test/util/mock/MockFillContract.sol @@ -3,18 +3,43 @@ pragma solidity ^0.8.0; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {CurrencyLibrary} from "../../../src/lib/CurrencyLibrary.sol"; -import {ResolvedOrder, OutputToken} from "../../../src/base/ReactorStructs.sol"; +import {ResolvedOrder, OutputToken, SignedOrder} from "../../../src/base/ReactorStructs.sol"; +import {BaseReactor} from "../../../src/reactors/BaseReactor.sol"; +import {IReactor} from "../../../src/interfaces/IReactor.sol"; import {IReactorCallback} from "../../../src/interfaces/IReactorCallback.sol"; contract MockFillContract is IReactorCallback { using CurrencyLibrary for address; + /// @notice thrown if native transfer fails to the reactor + error NativeTransferFailed(); + + IReactor immutable reactor; + + constructor(address _reactor) { + reactor = IReactor(_reactor); + } + + /// @notice assume that we already have all output tokens + function execute(SignedOrder calldata order) external { + reactor.executeWithCallback(order, hex""); + } + + /// @notice assume that we already have all output tokens + function executeBatch(SignedOrder[] calldata orders) external { + reactor.executeBatchWithCallback(orders, hex""); + } + /// @notice assume that we already have all output tokens - function reactorCallback(ResolvedOrder[] memory resolvedOrders, address, bytes memory) external { + function reactorCallback(ResolvedOrder[] memory resolvedOrders, bytes memory) external { for (uint256 i = 0; i < resolvedOrders.length; i++) { for (uint256 j = 0; j < resolvedOrders[i].outputs.length; j++) { OutputToken memory output = resolvedOrders[i].outputs[j]; - output.token.transfer(output.recipient, output.amount); + if (output.token.isNative()) { + CurrencyLibrary.transferNative(address(reactor), output.amount); + } else { + ERC20(output.token).approve(address(reactor), type(uint256).max); + } } } } diff --git a/test/util/mock/MockFillContractDoubleExecution.sol b/test/util/mock/MockFillContractDoubleExecution.sol new file mode 100644 index 00000000..a99ef0b1 --- /dev/null +++ b/test/util/mock/MockFillContractDoubleExecution.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {CurrencyLibrary} from "../../../src/lib/CurrencyLibrary.sol"; +import {ResolvedOrder, OutputToken, SignedOrder} from "../../../src/base/ReactorStructs.sol"; +import {BaseReactor} from "../../../src/reactors/BaseReactor.sol"; +import {IReactor} from "../../../src/interfaces/IReactor.sol"; +import {IReactorCallback} from "../../../src/interfaces/IReactorCallback.sol"; + +contract MockFillContractDoubleExecution is IReactorCallback { + using CurrencyLibrary for address; + + /// @notice thrown if native transfer fails to the reactor + error NativeTransferFailed(); + + IReactor immutable reactor1; + IReactor immutable reactor2; + + constructor(address _reactor1, address _reactor2) { + reactor1 = IReactor(_reactor1); + reactor2 = IReactor(_reactor2); + } + + /// @notice assume that we already have all output tokens + function execute(SignedOrder calldata order, SignedOrder calldata other) external { + reactor1.executeWithCallback(order, abi.encode(other)); + } + + /// @notice assume that we already have all output tokens + function reactorCallback(ResolvedOrder[] memory resolvedOrders, bytes memory otherSignedOrder) external { + for (uint256 i = 0; i < resolvedOrders.length; i++) { + for (uint256 j = 0; j < resolvedOrders[i].outputs.length; j++) { + OutputToken memory output = resolvedOrders[i].outputs[j]; + if (output.token.isNative()) { + CurrencyLibrary.transferNative(msg.sender, output.amount); + } else { + ERC20(output.token).approve(msg.sender, type(uint256).max); + } + } + } + + if (msg.sender == address(reactor1)) { + reactor2.executeWithCallback(abi.decode(otherSignedOrder, (SignedOrder)), hex""); + } + } +} diff --git a/test/util/mock/MockFillContractWithOutputOverride.sol b/test/util/mock/MockFillContractWithOutputOverride.sol index 01e321fb..0c14598a 100644 --- a/test/util/mock/MockFillContractWithOutputOverride.sol +++ b/test/util/mock/MockFillContractWithOutputOverride.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.0; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {CurrencyLibrary} from "../../../src/lib/CurrencyLibrary.sol"; -import {ResolvedOrder, OutputToken} from "../../../src/base/ReactorStructs.sol"; +import {ResolvedOrder, OutputToken, SignedOrder} from "../../../src/base/ReactorStructs.sol"; +import {IReactor} from "../../../src/interfaces/IReactor.sol"; import {IReactorCallback} from "../../../src/interfaces/IReactorCallback.sol"; contract MockFillContractWithOutputOverride is IReactorCallback { @@ -11,18 +12,41 @@ contract MockFillContractWithOutputOverride is IReactorCallback { uint256 outputAmount; + IReactor immutable reactor; + /// @notice thrown if native transfer fails to the reactor + + error NativeTransferFailed(); + + constructor(address _reactor) { + reactor = IReactor(_reactor); + } + // override for sending less than reactor amount function setOutputAmount(uint256 amount) external { outputAmount = amount; } /// @notice assume that we already have all output tokens - function reactorCallback(ResolvedOrder[] memory resolvedOrders, address, bytes memory) external { + function execute(SignedOrder calldata order) external { + reactor.executeWithCallback(order, hex""); + } + + /// @notice assume that we already have all output tokens + function executeBatch(SignedOrder[] calldata orders) external { + reactor.executeBatchWithCallback(orders, hex""); + } + + /// @notice assume that we already have all output tokens + function reactorCallback(ResolvedOrder[] memory resolvedOrders, bytes memory) external { for (uint256 i = 0; i < resolvedOrders.length; i++) { for (uint256 j = 0; j < resolvedOrders[i].outputs.length; j++) { OutputToken memory output = resolvedOrders[i].outputs[j]; uint256 amount = outputAmount == 0 ? output.amount : outputAmount; - output.token.transfer(output.recipient, amount); + if (output.token.isNative()) { + CurrencyLibrary.transferNative(address(reactor), amount); + } else { + ERC20(output.token).approve(address(reactor), type(uint256).max); + } } } } diff --git a/test/util/mock/users/DirectFiller.sol b/test/util/mock/users/DirectFiller.sol deleted file mode 100644 index 9502275e..00000000 --- a/test/util/mock/users/DirectFiller.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {ERC20} from "solmate/src/tokens/ERC20.sol"; - -contract MockDirectFiller { - function approve(address token, address to, uint256 amount) external { - ERC20(token).approve(to, amount); - } -} diff --git a/test/util/mock/users/MockDirectTaker.sol b/test/util/mock/users/MockDirectTaker.sol deleted file mode 100644 index e038217b..00000000 --- a/test/util/mock/users/MockDirectTaker.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import {SignedOrder} from "../../../../src/base/ReactorStructs.sol"; -import {IReactor} from "../../../../src/interfaces/IReactor.sol"; -import {IReactorCallback} from "../../../../src/interfaces/IReactorCallback.sol"; - -contract MockDirectFiller { - function approve(address token, address to, uint256 amount) external { - ERC20(token).approve(to, amount); - } - - function execute(IReactor reactor, SignedOrder memory order, IReactorCallback fillContract, bytes calldata fillData) - external - { - reactor.execute(order, fillContract, fillData); - } -} diff --git a/test/validation-contracts/ExclusiveFillerValidation.t.sol b/test/validation-contracts/ExclusiveFillerValidation.t.sol index cef038a0..fff07c79 100644 --- a/test/validation-contracts/ExclusiveFillerValidation.t.sol +++ b/test/validation-contracts/ExclusiveFillerValidation.t.sol @@ -32,13 +32,13 @@ contract ExclusiveFillerValidationTest is Test, PermitSignature, GasSnapshot, De ExclusiveFillerValidation exclusiveFillerValidation; function setUp() public { - fillContract = new MockFillContract(); tokenIn = new MockERC20("Input", "IN", 18); tokenOut = new MockERC20("Output", "OUT", 18); swapperPrivateKey = 0x12341234; swapper = vm.addr(swapperPrivateKey); permit2 = IPermit2(deployPermit2()); reactor = new DutchOrderReactor(permit2, PROTOCOL_FEE_OWNER); + fillContract = new MockFillContract(address(reactor)); exclusiveFillerValidation = new ExclusiveFillerValidation(); } @@ -54,7 +54,7 @@ contract ExclusiveFillerValidationTest is Test, PermitSignature, GasSnapshot, De DutchOrder memory order = DutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100) .withValidationContract(exclusiveFillerValidation).withValidationData( - abi.encode(address(this), block.timestamp + 50) + abi.encode(address(fillContract), block.timestamp + 50) ), decayStartTime: block.timestamp, decayEndTime: block.timestamp + 100, @@ -65,11 +65,7 @@ contract ExclusiveFillerValidationTest is Test, PermitSignature, GasSnapshot, De // Below snapshot can be compared to `DutchExecuteSingle.snap` to compare an execute with and without // exclusive filler validation snapStart("testExclusiveFillerSucceeds"); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), - fillContract, - bytes("") - ); + fillContract.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order))); snapEnd(); assertEq(tokenOut.balanceOf(swapper), outputAmount); assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); @@ -87,7 +83,7 @@ contract ExclusiveFillerValidationTest is Test, PermitSignature, GasSnapshot, De DutchOrder memory order = DutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100) .withValidationContract(exclusiveFillerValidation).withValidationData( - abi.encode(address(this), block.timestamp + 50) + abi.encode(address(0x1234), block.timestamp + 50) ), decayStartTime: block.timestamp, decayEndTime: block.timestamp + 100, @@ -95,13 +91,10 @@ contract ExclusiveFillerValidationTest is Test, PermitSignature, GasSnapshot, De outputs: OutputsBuilder.singleDutch(address(tokenOut), outputAmount, outputAmount, swapper) }); - vm.prank(address(0x123)); - vm.expectRevert(abi.encodeWithSelector(ExclusiveFillerValidation.NotExclusiveFiller.selector, address(0x123))); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), - fillContract, - bytes("") + vm.expectRevert( + abi.encodeWithSelector(ExclusiveFillerValidation.NotExclusiveFiller.selector, address(fillContract)) ); + fillContract.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order))); } // Ensure a different filler (not the one encoded in additionalValidationData) is able to execute after last exclusive @@ -118,7 +111,7 @@ contract ExclusiveFillerValidationTest is Test, PermitSignature, GasSnapshot, De DutchOrder memory order = DutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100) .withValidationContract(exclusiveFillerValidation).withValidationData( - abi.encode(address(this), block.timestamp - 50) + abi.encode(address(0x1234), block.timestamp - 50) ), decayStartTime: block.timestamp, decayEndTime: block.timestamp + 100, @@ -126,12 +119,7 @@ contract ExclusiveFillerValidationTest is Test, PermitSignature, GasSnapshot, De outputs: OutputsBuilder.singleDutch(address(tokenOut), outputAmount, outputAmount, swapper) }); - vm.prank(address(0x123)); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), - fillContract, - bytes("") - ); + fillContract.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order))); assertEq(tokenOut.balanceOf(swapper), outputAmount); assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); } @@ -148,7 +136,7 @@ contract ExclusiveFillerValidationTest is Test, PermitSignature, GasSnapshot, De DutchOrder memory order = DutchOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100) .withValidationContract(exclusiveFillerValidation).withValidationData( - abi.encode(address(this), block.timestamp) + abi.encode(address(0x1234), block.timestamp) ), decayStartTime: block.timestamp, decayEndTime: block.timestamp + 100, @@ -156,12 +144,9 @@ contract ExclusiveFillerValidationTest is Test, PermitSignature, GasSnapshot, De outputs: OutputsBuilder.singleDutch(address(tokenOut), outputAmount, outputAmount, swapper) }); - vm.prank(address(0x123)); - vm.expectRevert(abi.encodeWithSelector(ExclusiveFillerValidation.NotExclusiveFiller.selector, address(0x123))); - reactor.execute( - SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), - fillContract, - bytes("") + vm.expectRevert( + abi.encodeWithSelector(ExclusiveFillerValidation.NotExclusiveFiller.selector, address(fillContract)) ); + fillContract.execute(SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order))); } }