From 5285ce6b134ed26660571a05832514050691f3e3 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Fri, 7 Feb 2025 11:55:20 -0500 Subject: [PATCH 1/5] add universal router executor --- script/DeployUniversalRouterExecutor.s.sol | 26 +++++ .../UniversalRouterExecutor.sol | 103 ++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 script/DeployUniversalRouterExecutor.s.sol create mode 100644 src/sample-executors/UniversalRouterExecutor.sol diff --git a/script/DeployUniversalRouterExecutor.s.sol b/script/DeployUniversalRouterExecutor.s.sol new file mode 100644 index 00000000..1222cadb --- /dev/null +++ b/script/DeployUniversalRouterExecutor.s.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.13; + +import "forge-std/console2.sol"; +import "forge-std/Script.sol"; +import {UniversalRouterExecutor} from "../src/sample-executors/UniversalRouterExecutor.sol"; +import {IReactor} from "../src/interfaces/IReactor.sol"; + +contract DeployUniversalRouterExecutor is Script { + function setUp() public {} + + function run() public returns (UniversalRouterExecutor executor) { + uint256 privateKey = vm.envUint("FOUNDRY_PRIVATE_KEY"); + IReactor reactor = IReactor(vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_REACTOR")); + address whitelistedCaller = vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_WHITELISTED_CALLER"); + address owner = vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_OWNER"); + address universalRouter = vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_UNIVERSALROUTER"); + + vm.startBroadcast(privateKey); + executor = new UniversalRouterExecutor{salt: 0x00}(whitelistedCaller, reactor, owner, universalRouter); + vm.stopBroadcast(); + + console2.log("UniversalRouterExecutor", address(executor)); + console2.log("owner", executor.owner()); + } +} diff --git a/src/sample-executors/UniversalRouterExecutor.sol b/src/sample-executors/UniversalRouterExecutor.sol new file mode 100644 index 00000000..59047a81 --- /dev/null +++ b/src/sample-executors/UniversalRouterExecutor.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Owned} from "solmate/src/auth/Owned.sol"; +import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; +import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {WETH} from "solmate/src/tokens/WETH.sol"; +import {IReactorCallback} from "../interfaces/IReactorCallback.sol"; +import {IReactor} from "../interfaces/IReactor.sol"; +import {CurrencyLibrary} from "../lib/CurrencyLibrary.sol"; +import {ResolvedOrder, SignedOrder} from "../base/ReactorStructs.sol"; + +/// @notice A fill contract that uses UniversalRouter to execute trades +contract UniversalRouterExecutor is IReactorCallback, Owned { + using SafeTransferLib for ERC20; + using CurrencyLibrary for address; + + /// @notice thrown if reactorCallback is called with a non-whitelisted filler + error CallerNotWhitelisted(); + /// @notice thrown if reactorCallback is called by an address other than the reactor + error MsgSenderNotReactor(); + + address private immutable universalRouter; + address private immutable whitelistedCaller; + IReactor private immutable reactor; + + modifier onlyWhitelistedCaller() { + if (msg.sender != whitelistedCaller) { + revert CallerNotWhitelisted(); + } + _; + } + + modifier onlyReactor() { + if (msg.sender != address(reactor)) { + revert MsgSenderNotReactor(); + } + _; + } + + constructor(address _whitelistedCaller, IReactor _reactor, address _owner, address _universalRouter) + Owned(_owner) + { + whitelistedCaller = _whitelistedCaller; + reactor = _reactor; + universalRouter = _universalRouter; + } + + /// @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 UniversalRouter + /// @param callbackData It has the below encoded: + /// address[] memory tokensToApproveForUniversalRouter: Max approve these tokens to universalRouter + /// address[] memory tokensToApproveForReactor: Max approve these tokens to reactor + /// bytes[] memory data: execution data + function reactorCallback(ResolvedOrder[] calldata, bytes calldata callbackData) external onlyReactor { + ( + address[] memory tokensToApproveForUniversalRouter, + address[] memory tokensToApproveForReactor, + bytes memory data + ) = abi.decode(callbackData, (address[], address[], bytes)); + + unchecked { + for (uint256 i = 0; i < tokensToApproveForUniversalRouter.length; i++) { + ERC20(tokensToApproveForUniversalRouter[i]).safeApprove(address(universalRouter), type(uint256).max); + } + + for (uint256 i = 0; i < tokensToApproveForReactor.length; i++) { + ERC20(tokensToApproveForReactor[i]).safeApprove(address(reactor), type(uint256).max); + } + } + + (bool success, bytes memory returnData) = universalRouter.call(data); + if (!success) { + assembly { + revert(add(returnData, 32), mload(returnData)) + } + } + + // transfer any native balance to the reactor + // it will refund any excess + if (address(this).balance > 0) { + CurrencyLibrary.transferNative(address(reactor), address(this).balance); + } + } + + /// @notice Transfer all ETH in this contract to the recipient. Can only be called by owner. + /// @param recipient The recipient of the ETH + function withdrawETH(address recipient) external onlyOwner { + SafeTransferLib.safeTransferETH(recipient, address(this).balance); + } + + /// @notice Necessary for this contract to receive ETH when calling unwrapWETH() + receive() external payable {} +} From 250b1ae6e074dddc777728ec9d24049e1e8124c3 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Mon, 10 Feb 2025 17:24:13 -0500 Subject: [PATCH 2/5] Add tests --- script/DeployUniversalRouterExecutor.s.sol | 10 +- src/external/IUniversalRouter.sol | 25 +++ .../UniversalRouterExecutor.sol | 39 ++++- .../UniversalRouterExecutorIntegration.t.sol | 158 ++++++++++++++++++ 4 files changed, 221 insertions(+), 11 deletions(-) create mode 100644 src/external/IUniversalRouter.sol create mode 100644 test/integration/UniversalRouterExecutorIntegration.t.sol diff --git a/script/DeployUniversalRouterExecutor.s.sol b/script/DeployUniversalRouterExecutor.s.sol index 1222cadb..a406f8ea 100644 --- a/script/DeployUniversalRouterExecutor.s.sol +++ b/script/DeployUniversalRouterExecutor.s.sol @@ -5,6 +5,7 @@ import "forge-std/console2.sol"; import "forge-std/Script.sol"; import {UniversalRouterExecutor} from "../src/sample-executors/UniversalRouterExecutor.sol"; import {IReactor} from "../src/interfaces/IReactor.sol"; +import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; contract DeployUniversalRouterExecutor is Script { function setUp() public {} @@ -12,12 +13,17 @@ contract DeployUniversalRouterExecutor is Script { function run() public returns (UniversalRouterExecutor executor) { uint256 privateKey = vm.envUint("FOUNDRY_PRIVATE_KEY"); IReactor reactor = IReactor(vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_REACTOR")); - address whitelistedCaller = vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_WHITELISTED_CALLER"); + // can encode with cast abi-encode "foo(address[])" "[addr1, addr2, ...]" + bytes memory encodedAddresses = + vm.envBytes("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_WHITELISTED_CALLERS_ENCODED"); address owner = vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_OWNER"); address universalRouter = vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_UNIVERSALROUTER"); + IPermit2 permit2 = IPermit2(vm.envAddress("FOUNDRY_UNIVERSALROUTEREXECUTOR_DEPLOY_PERMIT2")); + + address[] memory decodedAddresses = abi.decode(encodedAddresses, (address[])); vm.startBroadcast(privateKey); - executor = new UniversalRouterExecutor{salt: 0x00}(whitelistedCaller, reactor, owner, universalRouter); + executor = new UniversalRouterExecutor{salt: 0x00}(decodedAddresses, reactor, owner, universalRouter, permit2); vm.stopBroadcast(); console2.log("UniversalRouterExecutor", address(executor)); diff --git a/src/external/IUniversalRouter.sol b/src/external/IUniversalRouter.sol new file mode 100644 index 00000000..6c70134e --- /dev/null +++ b/src/external/IUniversalRouter.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.24; + +interface IUniversalRouter { + /// @notice Thrown when a required command has failed + error ExecutionFailed(uint256 commandIndex, bytes message); + + /// @notice Thrown when attempting to send ETH directly to the contract + error ETHNotAccepted(); + + /// @notice Thrown when executing commands with an expired deadline + error TransactionDeadlinePassed(); + + /// @notice Thrown when attempting to execute commands and an incorrect number of inputs are provided + error LengthMismatch(); + + // @notice Thrown when an address that isn't WETH tries to send ETH to the router without calldata + error InvalidEthSender(); + + /// @notice Executes encoded commands along with provided inputs. Reverts if deadline has expired. + /// @param commands A set of concatenated commands, each 1 byte in length + /// @param inputs An array of byte strings containing abi encoded inputs for each command + /// @param deadline The deadline by which the transaction must be executed + function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable; +} diff --git a/src/sample-executors/UniversalRouterExecutor.sol b/src/sample-executors/UniversalRouterExecutor.sol index 59047a81..a5de1575 100644 --- a/src/sample-executors/UniversalRouterExecutor.sol +++ b/src/sample-executors/UniversalRouterExecutor.sol @@ -5,6 +5,7 @@ import {Owned} from "solmate/src/auth/Owned.sol"; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {WETH} from "solmate/src/tokens/WETH.sol"; +import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; import {IReactorCallback} from "../interfaces/IReactorCallback.sol"; import {IReactor} from "../interfaces/IReactor.sol"; import {CurrencyLibrary} from "../lib/CurrencyLibrary.sol"; @@ -20,12 +21,13 @@ contract UniversalRouterExecutor is IReactorCallback, Owned { /// @notice thrown if reactorCallback is called by an address other than the reactor error MsgSenderNotReactor(); - address private immutable universalRouter; - address private immutable whitelistedCaller; - IReactor private immutable reactor; + address public immutable universalRouter; + mapping(address => bool) whitelistedCallers; + IReactor public immutable reactor; + IPermit2 public immutable permit2; modifier onlyWhitelistedCaller() { - if (msg.sender != whitelistedCaller) { + if (whitelistedCallers[msg.sender] == false) { revert CallerNotWhitelisted(); } _; @@ -38,12 +40,19 @@ contract UniversalRouterExecutor is IReactorCallback, Owned { _; } - constructor(address _whitelistedCaller, IReactor _reactor, address _owner, address _universalRouter) - Owned(_owner) - { - whitelistedCaller = _whitelistedCaller; + constructor( + address[] memory _whitelistedCallers, + IReactor _reactor, + address _owner, + address _universalRouter, + IPermit2 _permit2 + ) Owned(_owner) { + for (uint256 i = 0; i < _whitelistedCallers.length; i++) { + whitelistedCallers[_whitelistedCallers[i]] = true; + } reactor = _reactor; universalRouter = _universalRouter; + permit2 = _permit2; } /// @notice assume that we already have all output tokens @@ -70,7 +79,12 @@ contract UniversalRouterExecutor is IReactorCallback, Owned { unchecked { for (uint256 i = 0; i < tokensToApproveForUniversalRouter.length; i++) { - ERC20(tokensToApproveForUniversalRouter[i]).safeApprove(address(universalRouter), type(uint256).max); + // Max approve token to permit2 + ERC20(tokensToApproveForUniversalRouter[i]).safeApprove(address(permit2), type(uint256).max); + // Max approve token to universalRouter via permit2 + permit2.approve( + tokensToApproveForUniversalRouter[i], address(universalRouter), type(uint160).max, type(uint48).max + ); } for (uint256 i = 0; i < tokensToApproveForReactor.length; i++) { @@ -98,6 +112,13 @@ contract UniversalRouterExecutor is IReactorCallback, Owned { SafeTransferLib.safeTransferETH(recipient, address(this).balance); } + /// @notice Transfer the entire balance of an ERC20 token in this contract to a recipient. Can only be called by owner. + /// @param token The ERC20 token to withdraw + /// @param to The recipient of the tokens + function withdrawERC20(ERC20 token, address to) external onlyOwner { + token.safeTransfer(to, token.balanceOf(address(this))); + } + /// @notice Necessary for this contract to receive ETH when calling unwrapWETH() receive() external payable {} } diff --git a/test/integration/UniversalRouterExecutorIntegration.t.sol b/test/integration/UniversalRouterExecutorIntegration.t.sol new file mode 100644 index 00000000..7d366d58 --- /dev/null +++ b/test/integration/UniversalRouterExecutorIntegration.t.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; +import {Test} from "forge-std/Test.sol"; +import {console} from "forge-std/console.sol"; +import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {UniversalRouterExecutor} from "../../src/sample-executors/UniversalRouterExecutor.sol"; +import {InputToken, OrderInfo, SignedOrder} from "../../src/base/ReactorStructs.sol"; +import {NATIVE} from "../../src/lib/CurrencyLibrary.sol"; +import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol"; +import {DutchOrderReactor, DutchOrder, DutchInput, DutchOutput} from "../../src/reactors/DutchOrderReactor.sol"; +import {OutputsBuilder} from "../util/OutputsBuilder.sol"; +import {PermitSignature} from "../util/PermitSignature.sol"; +import {IReactor} from "../../src/interfaces/IReactor.sol"; +import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; +import {IUniversalRouter} from "../../src/external/IUniversalRouter.sol"; + +contract UniversalRouterExecutorIntegrationTest is Test, PermitSignature { + using OrderInfoBuilder for OrderInfo; + using SafeTransferLib for ERC20; + + ERC20 constant USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + ERC20 constant USDT = ERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); + + uint256 constant USDC_ONE = 1e6; + + // UniversalRouter with V4 support + IUniversalRouter universalRouter = IUniversalRouter(0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af); + IPermit2 permit2 = IPermit2(0x000000000022D473030F116dDEE9F6B43aC78BA3); + + address swapper; + uint256 swapperPrivateKey; + address whitelistedCaller; + address owner; + UniversalRouterExecutor universalRouterExecutor; + DutchOrderReactor reactor; + + // UniversalRouter commands + uint256 constant V3_SWAP_EXACT_IN = 0x00; + + function setUp() public { + swapperPrivateKey = 0xbeef; + swapper = vm.addr(swapperPrivateKey); + vm.label(swapper, "swapper"); + whitelistedCaller = makeAddr("whitelistedCaller"); + owner = makeAddr("owner"); + // 02-10-2025 + vm.createSelectFork(vm.envString("FOUNDRY_RPC_URL"), 21818802); + reactor = new DutchOrderReactor(permit2, address(0)); + address[] memory whitelistedCallers = new address[](1); + whitelistedCallers[0] = whitelistedCaller; + universalRouterExecutor = new UniversalRouterExecutor( + whitelistedCallers, IReactor(address(reactor)), owner, address(universalRouter), permit2 + ); + + vm.prank(swapper); + USDC.approve(address(permit2), type(uint256).max); + + deal(address(USDC), swapper, 100 * 1e6); + } + + function baseTest(DutchOrder memory order) internal { + _baseTest(order, false, ""); + } + + function _baseTest(DutchOrder memory order, bool expectRevert, bytes memory revertData) internal { + address[] memory tokensToApproveForPermit2AndUniversalRouter = new address[](1); + tokensToApproveForPermit2AndUniversalRouter[0] = address(USDC); + + address[] memory tokensToApproveForReactor = new address[](1); + tokensToApproveForReactor[0] = address(USDT); + + bytes memory commands = hex"00"; + bytes[] memory inputs = new bytes[](1); + // V3 swap USDC -> USDT, with recipient as universalRouterExecutor + inputs[0] = + hex"0000000000000000000000002e234DAe75C793f67A35089C9d99245E1C58470b0000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000090972200000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000064dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000"; + + bytes memory data = + abi.encodeWithSelector(IUniversalRouter.execute.selector, commands, inputs, uint256(block.timestamp + 1000)); + + vm.prank(whitelistedCaller); + if (expectRevert) { + vm.expectRevert(revertData); + } + universalRouterExecutor.execute( + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), + abi.encode(tokensToApproveForPermit2AndUniversalRouter, tokensToApproveForReactor, data) + ); + } + + function test_universalRouterExecutor() 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(USDC, 10 * USDC_ONE, 10 * USDC_ONE), + outputs: OutputsBuilder.singleDutch(address(USDT), 9 * USDC_ONE, 9 * USDC_ONE, address(swapper)) + }); + + address[] memory tokensToApproveForPermit2AndUniversalRouter = new address[](1); + tokensToApproveForPermit2AndUniversalRouter[0] = address(USDC); + + address[] memory tokensToApproveForReactor = new address[](1); + tokensToApproveForReactor[0] = address(USDT); + + uint256 swapperInputBalanceBefore = USDC.balanceOf(swapper); + uint256 swapperOutputBalanceBefore = USDT.balanceOf(swapper); + + baseTest(order); + + assertEq(USDC.balanceOf(swapper), swapperInputBalanceBefore - 10 * USDC_ONE); + assertEq(USDT.balanceOf(swapper), swapperOutputBalanceBefore + 9 * USDC_ONE); + // Expect some USDT to be left in the executor from the swap + assertGe(USDT.balanceOf(address(universalRouterExecutor)), 0); + } + + function test_universalRouterExecutor_TooLittleReceived() 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(USDC, 10 * USDC_ONE, 10 * USDC_ONE), + // Too much output + outputs: OutputsBuilder.singleDutch(address(USDT), 11 * USDC_ONE, 11 * USDC_ONE, address(swapper)) + }); + + _baseTest(order, true, bytes("TRANSFER_FROM_FAILED")); + } + + function test_universalRouterExecutor_onlyOwner() public { + address nonOwner = makeAddr("nonOwner"); + address recipient = makeAddr("recipient"); + uint256 recipientBalanceBefore = recipient.balance; + uint256 recipientUSDCBalanceBefore = USDC.balanceOf(recipient); + + vm.deal(address(universalRouterExecutor), 1 ether); + deal(address(USDC), address(universalRouterExecutor), 100 * USDC_ONE); + + vm.prank(nonOwner); + vm.expectRevert("UNAUTHORIZED"); + universalRouterExecutor.withdrawETH(recipient); + + vm.prank(nonOwner); + vm.expectRevert("UNAUTHORIZED"); + universalRouterExecutor.withdrawERC20(USDC, recipient); + + vm.prank(owner); + universalRouterExecutor.withdrawETH(recipient); + assertEq(address(recipient).balance, recipientBalanceBefore + 1 ether); + + vm.prank(owner); + universalRouterExecutor.withdrawERC20(USDC, recipient); + assertEq(USDC.balanceOf(recipient), recipientUSDCBalanceBefore + 100 * USDC_ONE); + assertEq(USDC.balanceOf(address(universalRouterExecutor)), 0); + } +} From 3aac882ee352e8644c5732ba44d76b58d628d5aa Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Mon, 10 Feb 2025 17:27:45 -0500 Subject: [PATCH 3/5] fix import --- src/sample-executors/UniversalRouterExecutor.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sample-executors/UniversalRouterExecutor.sol b/src/sample-executors/UniversalRouterExecutor.sol index a5de1575..f93f4bb1 100644 --- a/src/sample-executors/UniversalRouterExecutor.sol +++ b/src/sample-executors/UniversalRouterExecutor.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.0; import {Owned} from "solmate/src/auth/Owned.sol"; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import {WETH} from "solmate/src/tokens/WETH.sol"; import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; import {IReactorCallback} from "../interfaces/IReactorCallback.sol"; import {IReactor} from "../interfaces/IReactor.sol"; From ad95fc13e1f7efcf5d44f0a01fc09a621748564e Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Mon, 10 Feb 2025 17:28:50 -0500 Subject: [PATCH 4/5] nit comments fix import --- src/sample-executors/UniversalRouterExecutor.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sample-executors/UniversalRouterExecutor.sol b/src/sample-executors/UniversalRouterExecutor.sol index f93f4bb1..39e254b4 100644 --- a/src/sample-executors/UniversalRouterExecutor.sol +++ b/src/sample-executors/UniversalRouterExecutor.sol @@ -66,9 +66,9 @@ contract UniversalRouterExecutor is IReactorCallback, Owned { /// @notice fill UniswapX orders using UniversalRouter /// @param callbackData It has the below encoded: - /// address[] memory tokensToApproveForUniversalRouter: Max approve these tokens to universalRouter + /// address[] memory tokensToApproveForUniversalRouter: Max approve these tokens to permit2 and universalRouter /// address[] memory tokensToApproveForReactor: Max approve these tokens to reactor - /// bytes[] memory data: execution data + /// bytes memory data: execution data function reactorCallback(ResolvedOrder[] calldata, bytes calldata callbackData) external onlyReactor { ( address[] memory tokensToApproveForUniversalRouter, @@ -118,6 +118,6 @@ contract UniversalRouterExecutor is IReactorCallback, Owned { token.safeTransfer(to, token.balanceOf(address(this))); } - /// @notice Necessary for this contract to receive ETH when calling unwrapWETH() + /// @notice Necessary for this contract to receive ETH receive() external payable {} } From 04b8e0b591f197e7cfc8beb3858c6775b08b3f9e Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Mon, 10 Feb 2025 17:30:17 -0500 Subject: [PATCH 5/5] nit: style --- test/integration/UniversalRouterExecutorIntegration.t.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/integration/UniversalRouterExecutorIntegration.t.sol b/test/integration/UniversalRouterExecutorIntegration.t.sol index 7d366d58..3011c583 100644 --- a/test/integration/UniversalRouterExecutorIntegration.t.sol +++ b/test/integration/UniversalRouterExecutorIntegration.t.sol @@ -3,17 +3,15 @@ pragma solidity ^0.8.0; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {Test} from "forge-std/Test.sol"; -import {console} from "forge-std/console.sol"; +import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {UniversalRouterExecutor} from "../../src/sample-executors/UniversalRouterExecutor.sol"; import {InputToken, OrderInfo, SignedOrder} from "../../src/base/ReactorStructs.sol"; -import {NATIVE} from "../../src/lib/CurrencyLibrary.sol"; import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol"; import {DutchOrderReactor, DutchOrder, DutchInput, DutchOutput} from "../../src/reactors/DutchOrderReactor.sol"; import {OutputsBuilder} from "../util/OutputsBuilder.sol"; import {PermitSignature} from "../util/PermitSignature.sol"; import {IReactor} from "../../src/interfaces/IReactor.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; import {IUniversalRouter} from "../../src/external/IUniversalRouter.sol"; contract UniversalRouterExecutorIntegrationTest is Test, PermitSignature {