|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | +pragma solidity ^0.8.0; |
| 3 | + |
| 4 | +import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; |
| 5 | +import {Test} from "forge-std/Test.sol"; |
| 6 | +import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; |
| 7 | +import {ERC20} from "solmate/src/tokens/ERC20.sol"; |
| 8 | +import {UniversalRouterExecutor} from "../../src/sample-executors/UniversalRouterExecutor.sol"; |
| 9 | +import {InputToken, OrderInfo, SignedOrder} from "../../src/base/ReactorStructs.sol"; |
| 10 | +import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol"; |
| 11 | +import {DutchOrderReactor, DutchOrder, DutchInput, DutchOutput} from "../../src/reactors/DutchOrderReactor.sol"; |
| 12 | +import {OutputsBuilder} from "../util/OutputsBuilder.sol"; |
| 13 | +import {PermitSignature} from "../util/PermitSignature.sol"; |
| 14 | +import {IReactor} from "../../src/interfaces/IReactor.sol"; |
| 15 | +import {IUniversalRouter} from "../../src/external/IUniversalRouter.sol"; |
| 16 | + |
| 17 | +contract UniversalRouterExecutorIntegrationTest is Test, PermitSignature { |
| 18 | + using OrderInfoBuilder for OrderInfo; |
| 19 | + using SafeTransferLib for ERC20; |
| 20 | + |
| 21 | + ERC20 constant USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); |
| 22 | + ERC20 constant USDT = ERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); |
| 23 | + |
| 24 | + uint256 constant USDC_ONE = 1e6; |
| 25 | + |
| 26 | + // UniversalRouter with V4 support |
| 27 | + IUniversalRouter universalRouter = IUniversalRouter(0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af); |
| 28 | + IPermit2 permit2 = IPermit2(0x000000000022D473030F116dDEE9F6B43aC78BA3); |
| 29 | + |
| 30 | + address swapper; |
| 31 | + uint256 swapperPrivateKey; |
| 32 | + address whitelistedCaller; |
| 33 | + address owner; |
| 34 | + UniversalRouterExecutor universalRouterExecutor; |
| 35 | + DutchOrderReactor reactor; |
| 36 | + |
| 37 | + // UniversalRouter commands |
| 38 | + uint256 constant V3_SWAP_EXACT_IN = 0x00; |
| 39 | + |
| 40 | + function setUp() public { |
| 41 | + swapperPrivateKey = 0xbeef; |
| 42 | + swapper = vm.addr(swapperPrivateKey); |
| 43 | + vm.label(swapper, "swapper"); |
| 44 | + whitelistedCaller = makeAddr("whitelistedCaller"); |
| 45 | + owner = makeAddr("owner"); |
| 46 | + // 02-10-2025 |
| 47 | + vm.createSelectFork(vm.envString("FOUNDRY_RPC_URL"), 21818802); |
| 48 | + reactor = new DutchOrderReactor(permit2, address(0)); |
| 49 | + address[] memory whitelistedCallers = new address[](1); |
| 50 | + whitelistedCallers[0] = whitelistedCaller; |
| 51 | + universalRouterExecutor = new UniversalRouterExecutor( |
| 52 | + whitelistedCallers, IReactor(address(reactor)), owner, address(universalRouter), permit2 |
| 53 | + ); |
| 54 | + |
| 55 | + vm.prank(swapper); |
| 56 | + USDC.approve(address(permit2), type(uint256).max); |
| 57 | + |
| 58 | + deal(address(USDC), swapper, 100 * 1e6); |
| 59 | + } |
| 60 | + |
| 61 | + function baseTest(DutchOrder memory order) internal { |
| 62 | + _baseTest(order, false, ""); |
| 63 | + } |
| 64 | + |
| 65 | + function _baseTest(DutchOrder memory order, bool expectRevert, bytes memory revertData) internal { |
| 66 | + address[] memory tokensToApproveForPermit2AndUniversalRouter = new address[](1); |
| 67 | + tokensToApproveForPermit2AndUniversalRouter[0] = address(USDC); |
| 68 | + |
| 69 | + address[] memory tokensToApproveForReactor = new address[](1); |
| 70 | + tokensToApproveForReactor[0] = address(USDT); |
| 71 | + |
| 72 | + bytes memory commands = hex"00"; |
| 73 | + bytes[] memory inputs = new bytes[](1); |
| 74 | + // V3 swap USDC -> USDT, with recipient as universalRouterExecutor |
| 75 | + inputs[0] = |
| 76 | + hex"0000000000000000000000002e234DAe75C793f67A35089C9d99245E1C58470b0000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000090972200000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000064dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000"; |
| 77 | + |
| 78 | + bytes memory data = |
| 79 | + abi.encodeWithSelector(IUniversalRouter.execute.selector, commands, inputs, uint256(block.timestamp + 1000)); |
| 80 | + |
| 81 | + vm.prank(whitelistedCaller); |
| 82 | + if (expectRevert) { |
| 83 | + vm.expectRevert(revertData); |
| 84 | + } |
| 85 | + universalRouterExecutor.execute( |
| 86 | + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), |
| 87 | + abi.encode(tokensToApproveForPermit2AndUniversalRouter, tokensToApproveForReactor, data) |
| 88 | + ); |
| 89 | + } |
| 90 | + |
| 91 | + function test_universalRouterExecutor() public { |
| 92 | + DutchOrder memory order = DutchOrder({ |
| 93 | + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100), |
| 94 | + decayStartTime: block.timestamp - 100, |
| 95 | + decayEndTime: block.timestamp + 100, |
| 96 | + input: DutchInput(USDC, 10 * USDC_ONE, 10 * USDC_ONE), |
| 97 | + outputs: OutputsBuilder.singleDutch(address(USDT), 9 * USDC_ONE, 9 * USDC_ONE, address(swapper)) |
| 98 | + }); |
| 99 | + |
| 100 | + address[] memory tokensToApproveForPermit2AndUniversalRouter = new address[](1); |
| 101 | + tokensToApproveForPermit2AndUniversalRouter[0] = address(USDC); |
| 102 | + |
| 103 | + address[] memory tokensToApproveForReactor = new address[](1); |
| 104 | + tokensToApproveForReactor[0] = address(USDT); |
| 105 | + |
| 106 | + uint256 swapperInputBalanceBefore = USDC.balanceOf(swapper); |
| 107 | + uint256 swapperOutputBalanceBefore = USDT.balanceOf(swapper); |
| 108 | + |
| 109 | + baseTest(order); |
| 110 | + |
| 111 | + assertEq(USDC.balanceOf(swapper), swapperInputBalanceBefore - 10 * USDC_ONE); |
| 112 | + assertEq(USDT.balanceOf(swapper), swapperOutputBalanceBefore + 9 * USDC_ONE); |
| 113 | + // Expect some USDT to be left in the executor from the swap |
| 114 | + assertGe(USDT.balanceOf(address(universalRouterExecutor)), 0); |
| 115 | + } |
| 116 | + |
| 117 | + function test_universalRouterExecutor_TooLittleReceived() public { |
| 118 | + DutchOrder memory order = DutchOrder({ |
| 119 | + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100), |
| 120 | + decayStartTime: block.timestamp - 100, |
| 121 | + decayEndTime: block.timestamp + 100, |
| 122 | + input: DutchInput(USDC, 10 * USDC_ONE, 10 * USDC_ONE), |
| 123 | + // Too much output |
| 124 | + outputs: OutputsBuilder.singleDutch(address(USDT), 11 * USDC_ONE, 11 * USDC_ONE, address(swapper)) |
| 125 | + }); |
| 126 | + |
| 127 | + _baseTest(order, true, bytes("TRANSFER_FROM_FAILED")); |
| 128 | + } |
| 129 | + |
| 130 | + function test_universalRouterExecutor_onlyOwner() public { |
| 131 | + address nonOwner = makeAddr("nonOwner"); |
| 132 | + address recipient = makeAddr("recipient"); |
| 133 | + uint256 recipientBalanceBefore = recipient.balance; |
| 134 | + uint256 recipientUSDCBalanceBefore = USDC.balanceOf(recipient); |
| 135 | + |
| 136 | + vm.deal(address(universalRouterExecutor), 1 ether); |
| 137 | + deal(address(USDC), address(universalRouterExecutor), 100 * USDC_ONE); |
| 138 | + |
| 139 | + vm.prank(nonOwner); |
| 140 | + vm.expectRevert("UNAUTHORIZED"); |
| 141 | + universalRouterExecutor.withdrawETH(recipient); |
| 142 | + |
| 143 | + vm.prank(nonOwner); |
| 144 | + vm.expectRevert("UNAUTHORIZED"); |
| 145 | + universalRouterExecutor.withdrawERC20(USDC, recipient); |
| 146 | + |
| 147 | + vm.prank(owner); |
| 148 | + universalRouterExecutor.withdrawETH(recipient); |
| 149 | + assertEq(address(recipient).balance, recipientBalanceBefore + 1 ether); |
| 150 | + |
| 151 | + vm.prank(owner); |
| 152 | + universalRouterExecutor.withdrawERC20(USDC, recipient); |
| 153 | + assertEq(USDC.balanceOf(recipient), recipientUSDCBalanceBefore + 100 * USDC_ONE); |
| 154 | + assertEq(USDC.balanceOf(address(universalRouterExecutor)), 0); |
| 155 | + } |
| 156 | +} |
0 commit comments