From 8f4a29d506d46fb1b0cae4fe41312a186ad21082 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 29 Nov 2023 22:08:48 -0500 Subject: [PATCH 01/33] Refactor tests and add arbitrary call --- ...derReactorIntegrationTest-testExecute.snap | 2 +- ...ionTest-testExecuteWithNativeAsOutput.snap | 2 +- ...rIntegrationTest-testPermitAndExecute.snap | 2 +- lib/universal-router | 1 + src/lib/RelayOrderLib.sol | 12 +- src/reactors/RelayOrderReactor.sol | 28 ++--- .../RelayOrderReactorIntegration.t.sol | 103 ++++++++++++------ 7 files changed, 82 insertions(+), 68 deletions(-) create mode 160000 lib/universal-router diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap index 28d61538..87c81da2 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap @@ -1 +1 @@ -303406 \ No newline at end of file +288585 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap index 67ad5c65..09fceba3 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap @@ -1 +1 @@ -309001 \ No newline at end of file +291179 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap index 8719ff76..1fff7e94 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap @@ -1 +1 @@ -350945 \ No newline at end of file +340631 \ No newline at end of file diff --git a/lib/universal-router b/lib/universal-router new file mode 160000 index 00000000..7d763cb2 --- /dev/null +++ b/lib/universal-router @@ -0,0 +1 @@ +Subproject commit 7d763cb28c88bb3bce5a30ad2356722f10c4d484 diff --git a/src/lib/RelayOrderLib.sol b/src/lib/RelayOrderLib.sol index 519c2be2..8bbee916 100644 --- a/src/lib/RelayOrderLib.sol +++ b/src/lib/RelayOrderLib.sol @@ -5,12 +5,6 @@ import {OrderInfo, OutputToken} from "UniswapX/src/base/ReactorStructs.sol"; import {OrderInfoLib} from "UniswapX/src/lib/OrderInfoLib.sol"; import {InputTokenWithRecipient} from "../base/ReactorStructs.sol"; -enum ActionType { - ApprovePermit2, - Permit2612, - UniversalRouter -} - /// @dev External struct used to specify simple relay orders struct RelayOrder { // generic order information @@ -104,12 +98,12 @@ library RelayOrderLib { function hash(RelayOrder memory order) internal pure returns (bytes32) { return keccak256( abi.encode( - ORDER_TYPE_HASH, - order.info.hash(), + ORDER_TYPE_HASH, + order.info.hash(), order.decayStartTime, order.decayEndTime, order.actions, - hash(order.inputs), + hash(order.inputs), hash(order.outputs) ) ); diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index e13de8b6..8a19b544 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -12,7 +12,7 @@ import {ReactorErrors} from "../base/ReactorErrors.sol"; import {InputTokenWithRecipient, ResolvedRelayOrder} from "../base/ReactorStructs.sol"; import {CurrencyLibrary, NATIVE} from "../lib/CurrencyLibrary.sol"; import {Permit2Lib} from "../lib/Permit2Lib.sol"; -import {RelayOrderLib, RelayOrder, ActionType} from "../lib/RelayOrderLib.sol"; +import {RelayOrderLib, RelayOrder} from "../lib/RelayOrderLib.sol"; import {ResolvedRelayOrderLib} from "../lib/ResolvedRelayOrderLib.sol"; import {RelayDecayLib} from "../lib/RelayDecayLib.sol"; @@ -62,30 +62,16 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe function _execute(ResolvedRelayOrder[] memory orders) internal { uint256 ordersLength = orders.length; - // actions are encoded as (ActionType actionType, bytes actionData)[] + // actions are encoded as (address target, uint256 value, bytes data)[] for (uint256 i = 0; i < ordersLength;) { ResolvedRelayOrder memory order = orders[i]; uint256 actionsLength = order.actions.length; for (uint256 j = 0; j < actionsLength;) { - (ActionType actionType, bytes memory actionData) = abi.decode(order.actions[j], (ActionType, bytes)); - if (actionType == ActionType.UniversalRouter) { - /// @dev to use universal router integration, this contract must be recipient of all output tokens - (bool success,) = universalRouter.call(actionData); - if (!success) revert CallFailed(); - } - // Max approve an ERC20 to UniversalRouter from the reactor using Permit2 - else if (actionType == ActionType.ApprovePermit2) { - (address token) = abi.decode(actionData, (address)); - if (token == address(0)) revert InvalidToken(); - if (ERC20(token).allowance(address(this), address(permit2)) == 0) { - ERC20(token).approve(address(permit2), type(uint256).max); - } - permit2.approve(token, universalRouter, type(uint160).max, type(uint48).max); - } - // Catch unsupported action types - else { - revert UnsupportedAction(); - } + (address target, uint256 value, bytes memory data) = + abi.decode(order.actions[j], (address, uint256, bytes)); + + (bool success,) = target.call{value: value}(data); + if (!success) revert CallFailed(); unchecked { j++; } diff --git a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol index 1a52159c..e63ab8a2 100644 --- a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol +++ b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol @@ -14,7 +14,7 @@ import {InputTokenWithRecipient, ResolvedRelayOrder} from "../../../src/base/Rea import {ReactorEvents} from "../../../src/base/ReactorEvents.sol"; import {CurrencyLibrary} from "../../../src/lib/CurrencyLibrary.sol"; import {PermitSignature} from "../util/PermitSignature.sol"; -import {RelayOrderLib, RelayOrder, ActionType} from "../../../src/lib/RelayOrderLib.sol"; +import {RelayOrderLib, RelayOrder} from "../../../src/lib/RelayOrderLib.sol"; import {RelayOrderReactor} from "../../../src/reactors/RelayOrderReactor.sol"; import {PermitExecutor} from "../../../src/sample-executors/PermitExecutor.sol"; import {MethodParameters, Interop} from "../util/Interop.sol"; @@ -49,6 +49,12 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS error InvalidNonce(); error InvalidSigner(); + uint256 swapperInputBalanceStart; + uint256 swapperOutputBalanceStart; + uint256 routerInputBalanceStart; + uint256 routerOutputBalanceStart; + uint256 fillerGasInputBalanceStart; + function setUp() public { swapperPrivateKey = 0xbabe; swapper = vm.addr(swapperPrivateKey); @@ -63,7 +69,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS reactor = RelayOrderReactor(RELAY_ORDER_REACTOR); permitExecutor = new PermitExecutor(address(filler), reactor, address(filler)); - // Swapper max approves permit post + // Swapper max approves permit post for all input tokens vm.startPrank(swapper); DAI.approve(address(PERMIT2), type(uint256).max); USDC.approve(address(PERMIT2), type(uint256).max); @@ -71,15 +77,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS PERMIT2.approve(address(USDC), address(reactor), type(uint160).max, type(uint48).max); vm.stopPrank(); - // reactor max approves permit post - vm.startPrank(address(reactor)); - DAI.approve(address(PERMIT2), type(uint256).max); - USDC.approve(address(PERMIT2), type(uint256).max); - PERMIT2.approve(address(DAI), UNIVERSAL_ROUTER, type(uint160).max, type(uint48).max); - PERMIT2.approve(address(USDC), UNIVERSAL_ROUTER, type(uint160).max, type(uint48).max); - vm.stopPrank(); - - // Transfer 1000 DAI to swapper + // Fund swappers vm.startPrank(WHALE); DAI.transfer(swapper, 1000 * ONE); DAI.transfer(swapper2, 1000 * ONE); @@ -87,6 +85,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS USDC.transfer(swapper2, 1000 * USDC_ONE); vm.stopPrank(); + // initial assumptions assertEq(USDC.balanceOf(address(reactor)), 0, "reactor should have no USDC"); assertEq(DAI.balanceOf(address(reactor)), 0, "reactor should have no DAI"); } @@ -109,7 +108,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS bytes[] memory actions = new bytes[](1); MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_DAI_USDC"); - actions[0] = abi.encode(ActionType.UniversalRouter, methodParameters.data); + actions[0] = abi.encode(UNIVERSAL_ROUTER, methodParameters.value, methodParameters.data); RelayOrder memory order = RelayOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100), @@ -123,17 +122,25 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS SignedOrder memory signedOrder = SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order)); - uint256 routerDaiBalanceBefore = DAI.balanceOf(UNIVERSAL_ROUTER); + ERC20 tokenIn = DAI; + ERC20 tokenOut = USDC; + _checkpointBalances(swapper, filler, tokenIn, tokenOut, USDC); vm.prank(filler); snapStart("RelayOrderReactorIntegrationTest-testExecute"); reactor.execute{value: methodParameters.value}(signedOrder); snapEnd(); - assertEq(DAI.balanceOf(UNIVERSAL_ROUTER), routerDaiBalanceBefore, "No leftover input in router"); - assertEq(USDC.balanceOf(address(reactor)), 0, "No leftover output in reactor"); - assertGe(USDC.balanceOf(swapper), amountOutMin, "Swapper did not receive enough output"); - assertEq(USDC.balanceOf((filler)), 10 * USDC_ONE, "filler did not receive enough USDC"); + assertEq(tokenIn.balanceOf(UNIVERSAL_ROUTER), routerInputBalanceStart, "No leftover input in router"); + assertEq(tokenOut.balanceOf(UNIVERSAL_ROUTER), routerOutputBalanceStart, "No leftover output in reactor"); + assertEq(tokenOut.balanceOf(address(reactor)), 0, "No leftover output in reactor"); + assertEq(tokenIn.balanceOf(swapper), swapperInputBalanceStart - 100 * ONE, "Swapper input tokens"); + assertGe( + tokenOut.balanceOf(swapper), + swapperOutputBalanceStart + amountOutMin - 10 * USDC_ONE, + "Swapper did not receive enough output" + ); + assertEq(tokenOut.balanceOf((filler)), fillerGasInputBalanceStart + 10 * USDC_ONE, "filler balance"); } function testPermitAndExecute() public { @@ -157,8 +164,6 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS uint256 amountOutMin = 95 * ONE; // sign permit for USDC - uint256 amount = type(uint256).max - 1; // infinite approval to permit2 - uint256 deadline = type(uint256).max - 1; // never expires bytes32 digest = keccak256( abi.encodePacked( "\x19\x01", @@ -168,9 +173,9 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), swapper2, address(PERMIT2), - amount, + type(uint256).max - 1, // infinite approval USDC.nonces(swapper2), - deadline + type(uint256).max - 1 // infinite deadline ) ) ) @@ -181,11 +186,11 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS assertEq(signer, swapper2); bytes memory permitData = - abi.encode(address(USDC), abi.encode(swapper2, address(PERMIT2), amount, deadline, v, r, s)); + abi.encode(address(USDC), abi.encode(swapper2, address(PERMIT2), type(uint256).max - 1, type(uint256).max - 1, v, r, s)); bytes[] memory actions = new bytes[](1); MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_USDC_DAI"); - actions[0] = abi.encode(ActionType.UniversalRouter, methodParameters.data); + actions[0] = abi.encode(UNIVERSAL_ROUTER, methodParameters.value, methodParameters.data); RelayOrder memory order = RelayOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper2).withDeadline(block.timestamp + 100), @@ -199,18 +204,27 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS SignedOrder memory signedOrder = SignedOrder(abi.encode(order), signOrder(swapper2PrivateKey, address(PERMIT2), order)); - uint256 routerUSDCBalanceBefore = USDC.balanceOf(UNIVERSAL_ROUTER); + ERC20 tokenIn = USDC; + ERC20 tokenOut = DAI; + // in this case, gas payment will go to executor (msg.sender) + _checkpointBalances(swapper2, address(permitExecutor), tokenIn, tokenOut, USDC); vm.prank(filler); snapStart("RelayOrderReactorIntegrationTest-testPermitAndExecute"); permitExecutor.executeWithPermit{value: methodParameters.value}(signedOrder, permitData); snapEnd(); - assertEq(USDC.balanceOf(UNIVERSAL_ROUTER), routerUSDCBalanceBefore, "No leftover input in router"); - assertEq(DAI.balanceOf(address(reactor)), 0, "No leftover output in reactor"); - assertGe(DAI.balanceOf(swapper2), amountOutMin, "Swapper did not receive enough output"); - // in this case, gas payment will go to sender (executor) - assertEq(USDC.balanceOf(address(permitExecutor)), 10 * USDC_ONE, "executor did not receive enough USDC"); + assertEq(tokenIn.balanceOf(UNIVERSAL_ROUTER), routerInputBalanceStart, "No leftover input in router"); + assertEq(tokenOut.balanceOf(UNIVERSAL_ROUTER), routerOutputBalanceStart, "No leftover output in reactor"); + assertEq(tokenOut.balanceOf(address(reactor)), 0, "No leftover output in reactor"); + // swapper must have spent 100 USDC for the swap and 10 USDC for gas + assertEq( + tokenIn.balanceOf(swapper2), swapperInputBalanceStart - 100 * USDC_ONE - 10 * USDC_ONE, "Swapper input tokens" + ); + assertGe( + tokenOut.balanceOf(swapper2), swapperOutputBalanceStart + amountOutMin, "Swapper did not receive enough output" + ); + assertEq(tokenIn.balanceOf(address(permitExecutor)), fillerGasInputBalanceStart + 10 * USDC_ONE, "executor balance"); } // swapper creates one order containing a universal router swap for 100 DAI -> ETH @@ -231,7 +245,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS bytes[] memory actions = new bytes[](1); MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_DAI_ETH"); - actions[0] = abi.encode(ActionType.UniversalRouter, methodParameters.data); + actions[0] = abi.encode(UNIVERSAL_ROUTER, methodParameters.value, methodParameters.data); RelayOrder memory order = RelayOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100), @@ -246,17 +260,28 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS SignedOrder memory signedOrder = SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order)); - uint256 routerDaiBalanceBefore = DAI.balanceOf(UNIVERSAL_ROUTER); + ERC20 tokenIn = DAI; + swapperInputBalanceStart = tokenIn.balanceOf(swapper); + swapperOutputBalanceStart = swapper.balance; + routerInputBalanceStart = tokenIn.balanceOf(UNIVERSAL_ROUTER); + routerOutputBalanceStart = UNIVERSAL_ROUTER.balance; + fillerGasInputBalanceStart = USDC.balanceOf(filler); vm.prank(filler); snapStart("RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput"); reactor.execute{value: methodParameters.value}(signedOrder); snapEnd(); - assertEq(DAI.balanceOf(UNIVERSAL_ROUTER), routerDaiBalanceBefore, "No leftover input in router"); + assertEq(tokenIn.balanceOf(UNIVERSAL_ROUTER), routerInputBalanceStart, "No leftover input in router"); + assertEq(UNIVERSAL_ROUTER.balance, routerOutputBalanceStart, "No leftover output in reactor"); assertEq(address(reactor).balance, 0, "No leftover output in reactor"); - assertGe(swapper.balance, amountOutMin, "Swapper did not receive enough output"); - assertEq(USDC.balanceOf((filler)), 10 * USDC_ONE, "filler did not receive enough USDC"); + assertEq(tokenIn.balanceOf(swapper), swapperInputBalanceStart - 100 * ONE, "Swapper input tokens"); + assertGe( + swapper.balance, + swapperOutputBalanceStart + amountOutMin - 10 * USDC_ONE, + "Swapper did not receive enough output" + ); + assertEq(USDC.balanceOf(filler), fillerGasInputBalanceStart + 10 * USDC_ONE, "filler balance"); } function testExecuteFailsIfReactorIsNotRecipient() public { @@ -274,7 +299,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS bytes[] memory actions = new bytes[](1); MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_DAI_USDC_RECIPIENT_NOT_REACTOR"); - actions[0] = abi.encode(ActionType.UniversalRouter, methodParameters.data); + actions[0] = abi.encode(UNIVERSAL_ROUTER, methodParameters.value, methodParameters.data); RelayOrder memory order = RelayOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100), @@ -292,4 +317,12 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS vm.expectRevert(CurrencyLibrary.InsufficientBalance.selector); reactor.execute{value: methodParameters.value}(signedOrder); } + + function _checkpointBalances(address _swapper, address _filler, ERC20 tokenIn, ERC20 tokenOut, ERC20 gasInput) internal { + swapperInputBalanceStart = tokenIn.balanceOf(_swapper); + swapperOutputBalanceStart = tokenOut.balanceOf(_swapper); + routerInputBalanceStart = tokenIn.balanceOf(UNIVERSAL_ROUTER); + routerOutputBalanceStart = tokenOut.balanceOf(UNIVERSAL_ROUTER); + fillerGasInputBalanceStart = gasInput.balanceOf(_filler); + } } From 7fa5bcec673ded01f2857d6b9984c76b1bb75110 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 29 Nov 2023 22:11:03 -0500 Subject: [PATCH 02/33] forge fmt --- src/reactors/RelayOrderReactor.sol | 1 - .../RelayOrderReactorIntegration.t.sol | 23 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index 8a19b544..7cb56c60 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -69,7 +69,6 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe for (uint256 j = 0; j < actionsLength;) { (address target, uint256 value, bytes memory data) = abi.decode(order.actions[j], (address, uint256, bytes)); - (bool success,) = target.call{value: value}(data); if (!success) revert CallFailed(); unchecked { diff --git a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol index e63ab8a2..ae318a27 100644 --- a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol +++ b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol @@ -175,7 +175,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS address(PERMIT2), type(uint256).max - 1, // infinite approval USDC.nonces(swapper2), - type(uint256).max - 1 // infinite deadline + type(uint256).max - 1 // infinite deadline ) ) ) @@ -185,8 +185,9 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS address signer = ecrecover(digest, v, r, s); assertEq(signer, swapper2); - bytes memory permitData = - abi.encode(address(USDC), abi.encode(swapper2, address(PERMIT2), type(uint256).max - 1, type(uint256).max - 1, v, r, s)); + bytes memory permitData = abi.encode( + address(USDC), abi.encode(swapper2, address(PERMIT2), type(uint256).max - 1, type(uint256).max - 1, v, r, s) + ); bytes[] memory actions = new bytes[](1); MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_USDC_DAI"); @@ -219,12 +220,18 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS assertEq(tokenOut.balanceOf(address(reactor)), 0, "No leftover output in reactor"); // swapper must have spent 100 USDC for the swap and 10 USDC for gas assertEq( - tokenIn.balanceOf(swapper2), swapperInputBalanceStart - 100 * USDC_ONE - 10 * USDC_ONE, "Swapper input tokens" + tokenIn.balanceOf(swapper2), + swapperInputBalanceStart - 100 * USDC_ONE - 10 * USDC_ONE, + "Swapper input tokens" ); assertGe( - tokenOut.balanceOf(swapper2), swapperOutputBalanceStart + amountOutMin, "Swapper did not receive enough output" + tokenOut.balanceOf(swapper2), + swapperOutputBalanceStart + amountOutMin, + "Swapper did not receive enough output" + ); + assertEq( + tokenIn.balanceOf(address(permitExecutor)), fillerGasInputBalanceStart + 10 * USDC_ONE, "executor balance" ); - assertEq(tokenIn.balanceOf(address(permitExecutor)), fillerGasInputBalanceStart + 10 * USDC_ONE, "executor balance"); } // swapper creates one order containing a universal router swap for 100 DAI -> ETH @@ -318,7 +325,9 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS reactor.execute{value: methodParameters.value}(signedOrder); } - function _checkpointBalances(address _swapper, address _filler, ERC20 tokenIn, ERC20 tokenOut, ERC20 gasInput) internal { + function _checkpointBalances(address _swapper, address _filler, ERC20 tokenIn, ERC20 tokenOut, ERC20 gasInput) + internal + { swapperInputBalanceStart = tokenIn.balanceOf(_swapper); swapperOutputBalanceStart = tokenOut.balanceOf(_swapper); routerInputBalanceStart = tokenIn.balanceOf(UNIVERSAL_ROUTER); From 23c460deb78db7725016fc46b490753edd90f3d5 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 30 Nov 2023 10:51:28 -0500 Subject: [PATCH 03/33] add comment --- lib/universal-router | 1 - 1 file changed, 1 deletion(-) delete mode 160000 lib/universal-router diff --git a/lib/universal-router b/lib/universal-router deleted file mode 160000 index 7d763cb2..00000000 --- a/lib/universal-router +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7d763cb28c88bb3bce5a30ad2356722f10c4d484 From a1ff78da78d4a8429737d6a54a0738944ff2b4e3 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 30 Nov 2023 10:51:35 -0500 Subject: [PATCH 04/33] add comment --- src/reactors/RelayOrderReactor.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index 7cb56c60..d10b4ebf 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -16,8 +16,8 @@ import {RelayOrderLib, RelayOrder} from "../lib/RelayOrderLib.sol"; import {ResolvedRelayOrderLib} from "../lib/ResolvedRelayOrderLib.sol"; import {RelayDecayLib} from "../lib/RelayDecayLib.sol"; -/// @notice Reactor for relaying calls to UniversalRouter onchain -/// @dev This reactor only supports V2/V3 swaps, do NOT attempt to use other Universal Router commands +/// @notice Reactor for handling the execution of RelayOrders +/// @notice This contract MUST NOT have approvals or priviledged access contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRelayOrderReactor { using SafeTransferLib for ERC20; using CurrencyLibrary for address; From f1d91eff6a7f5e740851792e0d6f209e99272c19 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 30 Nov 2023 11:01:01 -0500 Subject: [PATCH 05/33] Remove output token and lib --- src/lib/RelayOrderLib.sol | 33 ++----------------- src/reactors/RelayOrderReactor.sol | 25 +++----------- .../RelayOrderReactorIntegration.t.sol | 16 +++------ 3 files changed, 13 insertions(+), 61 deletions(-) diff --git a/src/lib/RelayOrderLib.sol b/src/lib/RelayOrderLib.sol index 8bbee916..efbd3fc4 100644 --- a/src/lib/RelayOrderLib.sol +++ b/src/lib/RelayOrderLib.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {OrderInfo, OutputToken} from "UniswapX/src/base/ReactorStructs.sol"; +import {OrderInfo} from "UniswapX/src/base/ReactorStructs.sol"; import {OrderInfoLib} from "UniswapX/src/lib/OrderInfoLib.sol"; import {InputTokenWithRecipient} from "../base/ReactorStructs.sol"; @@ -17,8 +17,6 @@ struct RelayOrder { bytes[] actions; // The tokens that the swapper will provide when settling the order InputTokenWithRecipient[] inputs; - // The tokens that must be received to satisfy the order - OutputToken[] outputs; } /// @notice helpers for handling relay order objects @@ -27,10 +25,8 @@ library RelayOrderLib { bytes private constant INPUT_TOKEN_TYPE = "InputTokenWithRecipient(address token,uint256 amount,uint256 maxAmount,address recipient)"; - bytes private constant OUTPUT_TOKEN_TYPE = "OutputToken(address token,uint256 amount,address recipient)"; bytes32 private constant INPUT_TOKEN_TYPE_HASH = keccak256(INPUT_TOKEN_TYPE); - bytes32 private constant OUTPUT_TOKEN_TYPE_HASH = keccak256(OUTPUT_TOKEN_TYPE); bytes internal constant ORDER_TYPE = abi.encodePacked( "RelayOrder(", @@ -41,8 +37,7 @@ library RelayOrderLib { "InputTokenWithRecipient[] inputs,", "OutputToken[] outputs)", OrderInfoLib.ORDER_INFO_TYPE, - INPUT_TOKEN_TYPE, - OUTPUT_TOKEN_TYPE + INPUT_TOKEN_TYPE ); bytes32 internal constant ORDER_TYPE_HASH = keccak256(ORDER_TYPE); @@ -55,11 +50,6 @@ library RelayOrderLib { return keccak256(abi.encode(INPUT_TOKEN_TYPE_HASH, input.token, input.amount, input.maxAmount)); } - /// @notice returns the hash of an output token struct - function hash(OutputToken memory output) private pure returns (bytes32) { - return keccak256(abi.encode(OUTPUT_TOKEN_TYPE_HASH, output.token, output.amount, output.recipient)); - } - /// @notice returns the hash of an input token struct function hash(InputTokenWithRecipient[] memory inputs) private pure returns (bytes32) { unchecked { @@ -76,22 +66,6 @@ library RelayOrderLib { } } - /// @notice returns the hash of an output token struct - function hash(OutputToken[] memory outputs) private pure returns (bytes32) { - unchecked { - bytes memory packedHashes = new bytes(32 * outputs.length); - - for (uint256 i = 0; i < outputs.length; i++) { - bytes32 outputHash = hash(outputs[i]); - assembly { - mstore(add(add(packedHashes, 0x20), mul(i, 0x20)), outputHash) - } - } - - return keccak256(packedHashes); - } - } - /// @notice hash the given order /// @param order the order to hash /// @return the eip-712 order hash @@ -103,8 +77,7 @@ library RelayOrderLib { order.decayStartTime, order.decayEndTime, order.actions, - hash(order.inputs), - hash(order.outputs) + hash(order.inputs) ) ); } diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index d10b4ebf..311b0fb6 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -5,7 +5,7 @@ import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {SignedOrder, OrderInfo, OutputToken} from "UniswapX/src/base/ReactorStructs.sol"; +import {SignedOrder, OrderInfo} from "UniswapX/src/base/ReactorStructs.sol"; import {IRelayOrderReactor} from "../interfaces/IRelayOrderReactor.sol"; import {ReactorEvents} from "../base/ReactorEvents.sol"; import {ReactorErrors} from "../base/ReactorErrors.sol"; @@ -81,7 +81,7 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe } } - /// @notice validates, injects fees, and transfers input tokens in preparation for order fill + /// @notice validates and transfers input tokens in preparation for order fill /// @param orders The orders to prepare function _prepare(ResolvedRelayOrder[] memory orders) internal { uint256 ordersLength = orders.length; @@ -97,31 +97,16 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe } } - /// @notice fills a list of orders, ensuring all outputs are satisfied - /// @param orders The orders to fill + /// @notice emits a Fill event for each order + /// @notice all output token checks must be done in the encoded actions within the order + /// @param orders The orders that have been filled function _fill(ResolvedRelayOrder[] memory orders) internal { uint256 ordersLength = orders.length; - // attempt to transfer all currencies to all recipients unchecked { - // transfer output tokens to their respective recipients for (uint256 i = 0; i < ordersLength; i++) { - ResolvedRelayOrder memory resolvedOrder = orders[i]; - uint256 outputsLength = resolvedOrder.outputs.length; - for (uint256 j = 0; j < outputsLength; j++) { - OutputToken memory output = resolvedOrder.outputs[j]; - output.token.transferFillFromBalance(output.recipient, output.amount); - } - 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 { diff --git a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol index ae318a27..52a79c26 100644 --- a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol +++ b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol @@ -6,9 +6,8 @@ import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {Test, stdJson} from "forge-std/Test.sol"; import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {OrderInfo, OutputToken, SignedOrder} from "UniswapX/src/base/ReactorStructs.sol"; +import {OrderInfo, SignedOrder} from "UniswapX/src/base/ReactorStructs.sol"; import {OrderInfoBuilder} from "UniswapX/test/util/OrderInfoBuilder.sol"; -import {OutputsBuilder} from "UniswapX/test/util/OutputsBuilder.sol"; import {ArrayBuilder} from "UniswapX/test/util/ArrayBuilder.sol"; import {InputTokenWithRecipient, ResolvedRelayOrder} from "../../../src/base/ReactorStructs.sol"; import {ReactorEvents} from "../../../src/base/ReactorEvents.sol"; @@ -115,8 +114,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS decayStartTime: block.timestamp, decayEndTime: block.timestamp + 100, actions: actions, - inputs: inputTokens, - outputs: OutputsBuilder.single(address(USDC), amountOutMin, address(swapper)) + inputs: inputTokens }); SignedOrder memory signedOrder = @@ -198,8 +196,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS decayStartTime: block.timestamp, decayEndTime: block.timestamp + 100, actions: actions, - inputs: inputTokens, - outputs: OutputsBuilder.single(address(DAI), amountOutMin, address(swapper2)) + inputs: inputTokens }); SignedOrder memory signedOrder = @@ -259,9 +256,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS decayStartTime: block.timestamp, decayEndTime: block.timestamp + 100, actions: actions, - inputs: inputTokens, - // address 0 for native output - outputs: OutputsBuilder.single(address(0), amountOutMin, address(swapper)) + inputs: inputTokens }); SignedOrder memory signedOrder = @@ -313,8 +308,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS decayStartTime: block.timestamp, decayEndTime: block.timestamp + 100, actions: actions, - inputs: inputTokens, - outputs: OutputsBuilder.single(address(USDC), amountOutMin, address(swapper)) + inputs: inputTokens }); SignedOrder memory signedOrder = From 85b6976f7ef96de1eba19f23b5466256fa0fadd7 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 30 Nov 2023 23:04:01 -0500 Subject: [PATCH 06/33] Remove output tokens and update tests --- ...derReactorIntegrationTest-testExecute.snap | 2 +- ...ionTest-testExecuteWithNativeAsOutput.snap | 2 +- ...rIntegrationTest-testPermitAndExecute.snap | 2 +- src/base/ReactorStructs.sol | 1 - src/reactors/RelayOrderReactor.sol | 2 +- .../RelayOrderReactorIntegration.t.sol | 8 +-- test/foundry-tests/interop.json | 14 +++-- .../RelayOrderReactor.test.ts | 58 ++++++++++++++----- test/integration-tests/shared/constants.ts | 15 +++++ .../shared/mainnetForkHelpers.ts | 2 + .../shared/swapRouter02Helpers.ts | 22 +++++++ test/integration-tests/shared/uniswapData.ts | 4 +- 12 files changed, 103 insertions(+), 29 deletions(-) create mode 100644 test/integration-tests/shared/constants.ts create mode 100644 test/integration-tests/shared/swapRouter02Helpers.ts diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap index 87c81da2..0b1c40d9 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap @@ -1 +1 @@ -288585 \ No newline at end of file +254382 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap index 09fceba3..f879bb4c 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap @@ -1 +1 @@ -291179 \ No newline at end of file +280749 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap index 1fff7e94..1dfb8b7c 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap @@ -1 +1 @@ -340631 \ No newline at end of file +310457 \ No newline at end of file diff --git a/src/base/ReactorStructs.sol b/src/base/ReactorStructs.sol index 58ddbd5c..1790f2f1 100644 --- a/src/base/ReactorStructs.sol +++ b/src/base/ReactorStructs.sol @@ -17,7 +17,6 @@ struct ResolvedRelayOrder { OrderInfo info; bytes[] actions; InputTokenWithRecipient[] inputs; - OutputToken[] outputs; bytes sig; bytes32 hash; } diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index 311b0fb6..03f9b36c 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -104,6 +104,7 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe uint256 ordersLength = orders.length; unchecked { for (uint256 i = 0; i < ordersLength; i++) { + ResolvedRelayOrder memory resolvedOrder = orders[i]; emit Fill(orders[i].hash, msg.sender, resolvedOrder.info.swapper, resolvedOrder.info.nonce); } } @@ -125,7 +126,6 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe info: order.info, actions: order.actions, inputs: order.inputs.decay(order.decayStartTime, order.decayEndTime), - outputs: order.outputs, sig: signedOrder.sig, hash: order.hash() }); diff --git a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol index 52a79c26..14b5abd7 100644 --- a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol +++ b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol @@ -188,7 +188,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS ); bytes[] memory actions = new bytes[](1); - MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_USDC_DAI"); + MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_USDC_DAI_SWAPPER2"); actions[0] = abi.encode(UNIVERSAL_ROUTER, methodParameters.value, methodParameters.data); RelayOrder memory order = RelayOrder({ @@ -286,7 +286,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS assertEq(USDC.balanceOf(filler), fillerGasInputBalanceStart + 10 * USDC_ONE, "filler balance"); } - function testExecuteFailsIfReactorIsNotRecipient() public { + function testExecuteDoesNotSucceedIfReactorIsRecipientAndUniversalRouterSweep() public { InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](2); inputTokens[0] = InputTokenWithRecipient({token: DAI, amount: 100 * ONE, maxAmount: 100 * ONE, recipient: UNIVERSAL_ROUTER}); @@ -300,7 +300,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS uint256 amountOutMin = 95 * USDC_ONE; bytes[] memory actions = new bytes[](1); - MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_DAI_USDC_RECIPIENT_NOT_REACTOR"); + MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_DAI_USDC_RECIPIENT_REACTOR_WITH_SWEEP"); actions[0] = abi.encode(UNIVERSAL_ROUTER, methodParameters.value, methodParameters.data); RelayOrder memory order = RelayOrder({ @@ -315,7 +315,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(PERMIT2), order)); vm.prank(filler); - vm.expectRevert(CurrencyLibrary.InsufficientBalance.selector); + vm.expectRevert(0x675cae38); // InvalidToken() reactor.execute{value: methodParameters.value}(signedOrder); } diff --git a/test/foundry-tests/interop.json b/test/foundry-tests/interop.json index ef8962a8..1b31b793 100644 --- a/test/foundry-tests/interop.json +++ b/test/foundry-tests/interop.json @@ -1,18 +1,22 @@ { "_UNISWAP_V3_DAI_USDC": { - "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000378718523232a14be8a24e291b5a5075be04d1210000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000005abe5b100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b6b175474e89094c44da98b954eedeac495271d0f000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000", + "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000342079f0e9da82bb28a27a6edc814a33660203830000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000005abe5b100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b6b175474e89094c44da98b954eedeac495271d0f000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000", "value": "0" }, "_UNISWAP_V3_USDC_DAI": { - "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000378718523232a14be8a24e291b5a5075be04d1210000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000521e290a7c8117c1c00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb86b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000", + "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000342079f0e9da82bb28a27a6edc814a33660203830000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000521e290a7c8117c1c00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb86b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000", "value": "0" }, - "_UNISWAP_V3_DAI_USDC_RECIPIENT_NOT_REACTOR": { - "calldata": "0x24856bc3000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000deadbeef0000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000005abe5b100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b6b175474e89094c44da98b954eedeac495271d0f000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000", + "_UNISWAP_V3_USDC_DAI_SWAPPER2": { + "calldata": "0x24856bc300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001000000000000000000000000006e9972213bf459853fa33e28ab7219e9157c8d020000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000521e290a7c8117c1c00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb86b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000", "value": "0" }, "_UNISWAP_V3_DAI_ETH": { - "calldata": "0x24856bc3000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000b78088a89f723100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b6b175474e89094c44da98b954eedeac495271d0f000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000378718523232a14be8a24e291b5a5075be04d12100000000000000000000000000000000000000000000000000b78088a89f7231", + "calldata": "0x24856bc3000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000b78088a89f723100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b6b175474e89094c44da98b954eedeac495271d0f000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000342079f0e9da82bb28a27a6edc814a336602038300000000000000000000000000000000000000000000000000b78088a89f7231", + "value": "0" + }, + "_UNISWAP_V3_DAI_USDC_RECIPIENT_REACTOR_WITH_SWEEP": { + "calldata": "0x24856bc300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000200040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000100000000000000000000000000378718523232a14be8a24e291b5a5075be04d1210000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000005a995c000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b6b175474e89094c44da98b954eedeac495271d0f000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000342079f0e9da82bb28a27a6edc814a33660203830000000000000000000000000000000000000000000000000000000005a995c0", "value": "0" } } \ No newline at end of file diff --git a/test/integration-tests/RelayOrderReactor.test.ts b/test/integration-tests/RelayOrderReactor.test.ts index 91614636..937c5762 100644 --- a/test/integration-tests/RelayOrderReactor.test.ts +++ b/test/integration-tests/RelayOrderReactor.test.ts @@ -1,12 +1,15 @@ import { expect } from 'chai' import { SwapRouter, UniswapTrade} from "@uniswap/universal-router-sdk"; -import {Pool, Route as RouteV3, Trade as V3Trade} from "@uniswap/v3-sdk"; +import {FeeAmount, Pool, Route as RouteV3, Trade as V3Trade} from "@uniswap/v3-sdk"; import { CurrencyAmount, Ether, TradeType } from '@uniswap/sdk-core'; import { DAI, FEE_AMOUNT, USDC, WETH, buildTrade, getPool, swapOptions } from './shared/uniswapData'; -import { DEFAULT_FORK_BLOCK } from './shared/mainnetForkHelpers'; +import { DEFAULT_FORK_BLOCK, FORGE_ROUTER_ADDRESS, FORGE_SWAPPER2_ADDRESS, FORGE_SWAPPER_ADDRESS } from './shared/mainnetForkHelpers'; import { hexToDecimalString } from './shared/hexToDecimalString'; import { registerFixture } from './shared/writeInterop'; import { expandTo18DecimalsBN, expandTo6DecimalsBN } from './shared/helpers'; +import { CommandType, RoutePlanner } from '@uniswap/universal-router-sdk'; +import { SOURCE_ROUTER } from './shared/constants'; +import { encodePath } from './shared/swapRouter02Helpers'; describe("Relay order reactor tests", () => { let DAI_USDC_V3: Pool; @@ -19,6 +22,12 @@ describe("Relay order reactor tests", () => { DAI_WETH_V3 = await getPool(DAI, WETH, FEE_AMOUNT, DEFAULT_FORK_BLOCK); }); + /* + Guidelines for relay orders + - ROUTER pays (payerIsUser = false) + - recipient is user + - unless, routerMustCustody then we add sweep + */ it("basic v3 swap, DAI -> USDC", async () => { const trade = await V3Trade.fromRoute( new RouteV3([DAI_USDC_V3], DAI, USDC), @@ -43,29 +52,52 @@ describe("Relay order reactor tests", () => { expect(hexToDecimalString(methodParameters.value)).to.eq('0') }); - it("basic v3 swap to native, DAI -> ETH", async () => { + it("basic v3 swap, USDC -> DAI swapper2", async () => { const trade = await V3Trade.fromRoute( - new RouteV3([DAI_WETH_V3], DAI, Ether.onChain(1)), - CurrencyAmount.fromRawAmount(DAI, expandTo18DecimalsBN(100).toString()), + new RouteV3([USDC_DAI_V3], USDC, DAI), + CurrencyAmount.fromRawAmount(USDC, expandTo6DecimalsBN(100).toString()), TradeType.EXACT_INPUT ) - const opts = swapOptions({}) + const opts = swapOptions({ + recipient: FORGE_SWAPPER2_ADDRESS + }) const methodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) - registerFixture('_UNISWAP_V3_DAI_ETH', methodParameters) + registerFixture('_UNISWAP_V3_USDC_DAI_SWAPPER2', methodParameters) expect(hexToDecimalString(methodParameters.value)).to.eq('0') }); - it("recipient is not reactor", async () => { + it("basic v3 swap to native, DAI -> ETH", async () => { const trade = await V3Trade.fromRoute( - new RouteV3([DAI_USDC_V3], DAI, USDC), + new RouteV3([DAI_WETH_V3], DAI, Ether.onChain(1)), CurrencyAmount.fromRawAmount(DAI, expandTo18DecimalsBN(100).toString()), TradeType.EXACT_INPUT ) - const opts = swapOptions({ - recipient: "0x00000000000000000000000000000000DeaDBeef" - }) + const opts = swapOptions({}) const methodParameters = SwapRouter.swapCallParameters(new UniswapTrade(buildTrade([trade]), opts)) - registerFixture('_UNISWAP_V3_DAI_USDC_RECIPIENT_NOT_REACTOR', methodParameters) + registerFixture('_UNISWAP_V3_DAI_ETH', methodParameters) + expect(hexToDecimalString(methodParameters.value)).to.eq('0') + }); + + it("recipient is reactor not user", async () => { + const planner = (new RoutePlanner()); + planner.addCommand(CommandType.V3_SWAP_EXACT_IN, [ + FORGE_ROUTER_ADDRESS, // not swapper + expandTo18DecimalsBN(100), + expandTo6DecimalsBN(95), + encodePath([DAI.address, USDC.address], [FeeAmount.MEDIUM]), + SOURCE_ROUTER + ]) + planner.addCommand(CommandType.SWEEP, [ + USDC.address, + FORGE_SWAPPER_ADDRESS, + expandTo6DecimalsBN(95) + ]) + const { commands, inputs } = planner; + const methodParameters = { + calldata: SwapRouter.INTERFACE.encodeFunctionData('execute(bytes,bytes[])', [commands, inputs]), + value: '0x0', + } + registerFixture('_UNISWAP_V3_DAI_USDC_RECIPIENT_REACTOR_WITH_SWEEP', methodParameters) expect(hexToDecimalString(methodParameters.value)).to.eq('0') }); }) \ No newline at end of file diff --git a/test/integration-tests/shared/constants.ts b/test/integration-tests/shared/constants.ts new file mode 100644 index 00000000..7be6f79c --- /dev/null +++ b/test/integration-tests/shared/constants.ts @@ -0,0 +1,15 @@ +// Router Helpers +// copied from universal-router/test/integration-tests/shared/constants.ts +export const MAX_UINT = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' +export const MAX_UINT160 = '0xffffffffffffffffffffffffffffffffffffffff' +export const DEADLINE = 2000000000 +export const CONTRACT_BALANCE = '0x8000000000000000000000000000000000000000000000000000000000000000' +export const ALREADY_PAID = 0 +export const ALICE_ADDRESS = '0x28c6c06298d514db089934071355e5743bf21d60' +export const ETH_ADDRESS = '0x0000000000000000000000000000000000000000' +export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' +export const ONE_PERCENT_BIPS = 100 +export const MSG_SENDER: string = '0x0000000000000000000000000000000000000001' +export const ADDRESS_THIS: string = '0x0000000000000000000000000000000000000002' +export const SOURCE_MSG_SENDER: boolean = true +export const SOURCE_ROUTER: boolean = false diff --git a/test/integration-tests/shared/mainnetForkHelpers.ts b/test/integration-tests/shared/mainnetForkHelpers.ts index cd158043..647874da 100644 --- a/test/integration-tests/shared/mainnetForkHelpers.ts +++ b/test/integration-tests/shared/mainnetForkHelpers.ts @@ -4,6 +4,8 @@ import hre from 'hardhat' export const DEFAULT_FORK_BLOCK = 15360000; export const MAINNET_ROUTER_ADDRESS = UNIVERSAL_ROUTER_ADDRESS(1) export const FORGE_ROUTER_ADDRESS = "0x378718523232A14BE8A24e291b5A5075BE04D121"; +export const FORGE_SWAPPER_ADDRESS = "0x342079F0E9Da82bb28A27a6Edc814A3366020383"; +export const FORGE_SWAPPER2_ADDRESS = "0x6E9972213BF459853FA33E28Ab7219e9157C8d02"; export const TEST_RECIPIENT_ADDRESS = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' export const resetFork = async (block: number = DEFAULT_FORK_BLOCK) => { diff --git a/test/integration-tests/shared/swapRouter02Helpers.ts b/test/integration-tests/shared/swapRouter02Helpers.ts new file mode 100644 index 00000000..de81f5a5 --- /dev/null +++ b/test/integration-tests/shared/swapRouter02Helpers.ts @@ -0,0 +1,22 @@ +import { FeeAmount } from '@uniswap/v3-sdk' + +const FEE_SIZE = 3 + +// v3 +export function encodePath(path: string[], fees: FeeAmount[]): string { + if (path.length != fees.length + 1) { + throw new Error('path/fee lengths do not match') + } + + let encoded = '0x' + for (let i = 0; i < fees.length; i++) { + // 20 byte encoding of the address + encoded += path[i].slice(2) + // 3 byte encoding of the fee + encoded += fees[i].toString(16).padStart(2 * FEE_SIZE, '0') + } + // encode the final token + encoded += path[path.length - 1].slice(2) + + return encoded.toLowerCase() +} diff --git a/test/integration-tests/shared/uniswapData.ts b/test/integration-tests/shared/uniswapData.ts index 50e8896a..51f86ad5 100644 --- a/test/integration-tests/shared/uniswapData.ts +++ b/test/integration-tests/shared/uniswapData.ts @@ -14,7 +14,7 @@ import { import { CurrencyAmount, TradeType, Ether, Token, Percent, Currency } from '@uniswap/sdk-core' import IUniswapV3Pool from '@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json' import { SwapOptions } from '@uniswap/universal-router-sdk' -import { DEFAULT_FORK_BLOCK, FORGE_ROUTER_ADDRESS } from './mainnetForkHelpers' +import { DEFAULT_FORK_BLOCK, FORGE_ROUTER_ADDRESS, FORGE_SWAPPER_ADDRESS } from './mainnetForkHelpers' const V2_FACTORY = '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f' const V2_ABI = [ @@ -118,7 +118,7 @@ export function swapOptions(options: Partial): SwapOptions { return Object.assign( { slippageTolerance: new Percent(5, 100), - recipient: FORGE_ROUTER_ADDRESS, + recipient: FORGE_SWAPPER_ADDRESS, payerIsRouter: true }, options From 5ad68305f5365616cdf0335558ab60e72230bd7c Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Fri, 1 Dec 2023 10:37:21 -0500 Subject: [PATCH 07/33] bubble up revert reason --- ...OrderReactorIntegrationTest-testExecute.snap | 2 +- ...ationTest-testExecuteWithNativeAsOutput.snap | 2 +- ...torIntegrationTest-testPermitAndExecute.snap | 2 +- src/reactors/RelayOrderReactor.sol | 17 +++++++++++++++-- .../RelayOrderReactorIntegration.t.sol | 2 ++ 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap index 0b1c40d9..81bc663b 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap @@ -1 +1 @@ -254382 \ No newline at end of file +254405 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap index f879bb4c..b95b8fc1 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap @@ -1 +1 @@ -280749 \ No newline at end of file +280769 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap index 1dfb8b7c..113b6799 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap @@ -1 +1 @@ -310457 \ No newline at end of file +310474 \ No newline at end of file diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index 03f9b36c..bcb7ddf9 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -69,8 +69,21 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe for (uint256 j = 0; j < actionsLength;) { (address target, uint256 value, bytes memory data) = abi.decode(order.actions[j], (address, uint256, bytes)); - (bool success,) = target.call{value: value}(data); - if (!success) revert CallFailed(); + (bool success, bytes memory result) = target.call{value: value}(data); + if (!success) { + // handle custom errors + if (result.length == 4) { + assembly { + revert(add(result, 0x20), mload(result)) + } + } + // Next 5 lines from https://ethereum.stackexchange.com/a/83577 + if (result.length < 68) revert(); + assembly { + result := add(result, 0x04) + } + revert(abi.decode(result, (string))); + } unchecked { j++; } diff --git a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol index 14b5abd7..91a1a860 100644 --- a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol +++ b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol @@ -286,6 +286,8 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS assertEq(USDC.balanceOf(filler), fillerGasInputBalanceStart + 10 * USDC_ONE, "filler balance"); } + // in the case wehre the swapper incorrectly sets the recipient to an address that is not theirs, but the + // calldata includes a SWEEP back to them which should cause the transaction to revert function testExecuteDoesNotSucceedIfReactorIsRecipientAndUniversalRouterSweep() public { InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](2); inputTokens[0] = From 4180cc8462fee78a2158e34acb67b42d7de78824 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Fri, 1 Dec 2023 10:41:53 -0500 Subject: [PATCH 08/33] Remove unused currencyLibary --- src/lib/CurrencyLibrary.sol | 64 ------------------- src/reactors/RelayOrderReactor.sol | 3 +- src/sample-executors/PermitExecutor.sol | 2 +- .../RelayOrderReactorIntegration.t.sol | 2 +- 4 files changed, 4 insertions(+), 67 deletions(-) delete mode 100644 src/lib/CurrencyLibrary.sol diff --git a/src/lib/CurrencyLibrary.sol b/src/lib/CurrencyLibrary.sol deleted file mode 100644 index e94ec81f..00000000 --- a/src/lib/CurrencyLibrary.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -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. -library CurrencyLibrary { - using SafeTransferLib for ERC20; - - /// @notice Thrown when a native transfer fails - error NativeTransferFailed(); - /// @notice Thrown for insufficient balance - error InsufficientBalance(); - - /// @notice Get the balance of a currency for addr - /// @param currency The currency to get the balance of - /// @param addr The address to get the balance of - /// @return balance The balance of the currency for addr - function balanceOf(address currency, address addr) internal view returns (uint256 balance) { - if (isNative(currency)) { - balance = addr.balance; - } else { - balance = ERC20(currency).balanceOf(addr); - } - } - - /// @notice Transfer this contract's entire balance of a currency to recipient - /// @param currency The currency to transfer - /// @param recipient The recipient of the currency - /// @param minAmount The minAmount of currency that must be transferred - function transferFillFromBalance(address currency, address recipient, uint256 minAmount) internal { - if (isNative(currency)) { - if (address(this).balance < minAmount) revert InsufficientBalance(); - // we will have received native assets directly so can directly transfer - transferNative(recipient, address(this).balance); - } else { - uint256 balance = ERC20(currency).balanceOf(address(this)); - if (balance < minAmount) revert InsufficientBalance(); - ERC20(currency).transfer(recipient, balance); - } - } - - /// @notice Transfer native currency to recipient - /// @param recipient The recipient of the currency - /// @param amount The amount of currency to transfer - 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 - /// @param currency The currency to check - /// @return true if currency is native - function isNative(address currency) internal pure returns (bool) { - return currency == NATIVE; - } -} diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index bcb7ddf9..8ae79a31 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -6,11 +6,11 @@ import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard. import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; import {SignedOrder, OrderInfo} from "UniswapX/src/base/ReactorStructs.sol"; +import {CurrencyLibrary} from "UniswapX/src/lib/CurrencyLibrary.sol"; import {IRelayOrderReactor} from "../interfaces/IRelayOrderReactor.sol"; import {ReactorEvents} from "../base/ReactorEvents.sol"; import {ReactorErrors} from "../base/ReactorErrors.sol"; import {InputTokenWithRecipient, ResolvedRelayOrder} from "../base/ReactorStructs.sol"; -import {CurrencyLibrary, NATIVE} from "../lib/CurrencyLibrary.sol"; import {Permit2Lib} from "../lib/Permit2Lib.sol"; import {RelayOrderLib, RelayOrder} from "../lib/RelayOrderLib.sol"; import {ResolvedRelayOrderLib} from "../lib/ResolvedRelayOrderLib.sol"; @@ -18,6 +18,7 @@ import {RelayDecayLib} from "../lib/RelayDecayLib.sol"; /// @notice Reactor for handling the execution of RelayOrders /// @notice This contract MUST NOT have approvals or priviledged access +/// @notice any funds in this contract can be swept away by anyone contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRelayOrderReactor { using SafeTransferLib for ERC20; using CurrencyLibrary for address; diff --git a/src/sample-executors/PermitExecutor.sol b/src/sample-executors/PermitExecutor.sol index dbaeee6c..830f8648 100644 --- a/src/sample-executors/PermitExecutor.sol +++ b/src/sample-executors/PermitExecutor.sol @@ -5,8 +5,8 @@ import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {Owned} from "solmate/src/auth/Owned.sol"; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {SignedOrder} from "UniswapX/src/base/ReactorStructs.sol"; +import {CurrencyLibrary} from "UniswapX/src/lib/CurrencyLibrary.sol"; import {IRelayOrderReactor} from "../interfaces/IRelayOrderReactor.sol"; -import {CurrencyLibrary} from "../lib/CurrencyLibrary.sol"; /// @notice A simple fill contract that relays 2612 style permits on chain before filling a Relay order contract PermitExecutor is Owned { diff --git a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol index 91a1a860..acdae2ff 100644 --- a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol +++ b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol @@ -9,9 +9,9 @@ import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; import {OrderInfo, SignedOrder} from "UniswapX/src/base/ReactorStructs.sol"; import {OrderInfoBuilder} from "UniswapX/test/util/OrderInfoBuilder.sol"; import {ArrayBuilder} from "UniswapX/test/util/ArrayBuilder.sol"; +import {CurrencyLibrary} from "UniswapX/src/lib/CurrencyLibrary.sol"; import {InputTokenWithRecipient, ResolvedRelayOrder} from "../../../src/base/ReactorStructs.sol"; import {ReactorEvents} from "../../../src/base/ReactorEvents.sol"; -import {CurrencyLibrary} from "../../../src/lib/CurrencyLibrary.sol"; import {PermitSignature} from "../util/PermitSignature.sol"; import {RelayOrderLib, RelayOrder} from "../../../src/lib/RelayOrderLib.sol"; import {RelayOrderReactor} from "../../../src/reactors/RelayOrderReactor.sol"; From 2793258233c0c39fa5ddf0bd74d146fcbf54817e Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Fri, 1 Dec 2023 10:51:34 -0500 Subject: [PATCH 09/33] Change local imports to remote and natspe --- src/base/ReactorErrors.sol | 24 ------------------------ src/reactors/RelayOrderReactor.sol | 9 ++++++--- 2 files changed, 6 insertions(+), 27 deletions(-) delete mode 100644 src/base/ReactorErrors.sol diff --git a/src/base/ReactorErrors.sol b/src/base/ReactorErrors.sol deleted file mode 100644 index fef4b900..00000000 --- a/src/base/ReactorErrors.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -interface ReactorErrors { - // Occurs when an output = ETH and the reactor does contain enough ETH but - // the direct filler did not include enough ETH in their call to execute/executeBatch - error InsufficientEth(); - - // A nested call failed - error CallFailed(); - - error InvalidToken(); - error UnsupportedAction(); - error ReactorCallbackNotSupported(); - - /// @notice thrown when an order's deadline is before its end time - error DeadlineBeforeEndTime(); - - /// @notice thrown when an order's end time is before its start time - error OrderEndTimeBeforeStartTime(); - - /// @notice thrown when an order's inputs and outputs both decay - error InputAndOutputDecay(); -} diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index 8ae79a31..6bdec425 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -6,10 +6,10 @@ import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard. import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; import {SignedOrder, OrderInfo} from "UniswapX/src/base/ReactorStructs.sol"; +import {ReactorErrors} from "UniswapX/src/base/ReactorErrors.sol"; +import {ReactorEvents} from "UniswapX/src/base/ReactorEvents.sol"; import {CurrencyLibrary} from "UniswapX/src/lib/CurrencyLibrary.sol"; import {IRelayOrderReactor} from "../interfaces/IRelayOrderReactor.sol"; -import {ReactorEvents} from "../base/ReactorEvents.sol"; -import {ReactorErrors} from "../base/ReactorErrors.sol"; import {InputTokenWithRecipient, ResolvedRelayOrder} from "../base/ReactorStructs.sol"; import {Permit2Lib} from "../lib/Permit2Lib.sol"; import {RelayOrderLib, RelayOrder} from "../lib/RelayOrderLib.sol"; @@ -27,6 +27,9 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe using RelayOrderLib for RelayOrder; using RelayDecayLib for InputTokenWithRecipient[]; + /// @notice A nested call failed without a revert message + error CallFailed(); + /// @notice permit2 address used for token transfers and signature verification IPermit2 public immutable permit2; @@ -79,7 +82,7 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe } } // Next 5 lines from https://ethereum.stackexchange.com/a/83577 - if (result.length < 68) revert(); + if (result.length < 68) revert CallFailed(); assembly { result := add(result, 0x04) } From 3e47dfe4ca402aef4509e6fe29d78e74516127c4 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Fri, 1 Dec 2023 10:51:55 -0500 Subject: [PATCH 10/33] forge fmt --- .../integration/RelayOrderReactorIntegration.t.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol index acdae2ff..0a227677 100644 --- a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol +++ b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol @@ -302,7 +302,8 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS uint256 amountOutMin = 95 * USDC_ONE; bytes[] memory actions = new bytes[](1); - MethodParameters memory methodParameters = readFixture(json, "._UNISWAP_V3_DAI_USDC_RECIPIENT_REACTOR_WITH_SWEEP"); + MethodParameters memory methodParameters = + readFixture(json, "._UNISWAP_V3_DAI_USDC_RECIPIENT_REACTOR_WITH_SWEEP"); actions[0] = abi.encode(UNIVERSAL_ROUTER, methodParameters.value, methodParameters.data); RelayOrder memory order = RelayOrder({ From 2482fe95b46114e3706e70b7852bcb376f4b1dd8 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Fri, 1 Dec 2023 15:49:14 -0500 Subject: [PATCH 11/33] Re-add errors --- src/base/ReactorErrors.sol | 20 ++++++++++++++++++++ src/reactors/RelayOrderReactor.sol | 5 +---- 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 src/base/ReactorErrors.sol diff --git a/src/base/ReactorErrors.sol b/src/base/ReactorErrors.sol new file mode 100644 index 00000000..01070e9d --- /dev/null +++ b/src/base/ReactorErrors.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +interface ReactorErrors { + // Occurs when an output = ETH and the reactor does contain enough ETH but + // the direct filler did not include enough ETH in their call to execute/executeBatch + error InsufficientEth(); + + // A nested call failed + error CallFailed(); + + /// @notice thrown when an order's deadline is before its end time + error DeadlineBeforeEndTime(); + + /// @notice thrown when an order's end time is before its start time + error OrderEndTimeBeforeStartTime(); + + /// @notice thrown when an order's inputs and outputs both decay + error InputAndOutputDecay(); +} diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index 6bdec425..66b861ca 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -6,11 +6,11 @@ import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard. import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; import {SignedOrder, OrderInfo} from "UniswapX/src/base/ReactorStructs.sol"; -import {ReactorErrors} from "UniswapX/src/base/ReactorErrors.sol"; import {ReactorEvents} from "UniswapX/src/base/ReactorEvents.sol"; import {CurrencyLibrary} from "UniswapX/src/lib/CurrencyLibrary.sol"; import {IRelayOrderReactor} from "../interfaces/IRelayOrderReactor.sol"; import {InputTokenWithRecipient, ResolvedRelayOrder} from "../base/ReactorStructs.sol"; +import {ReactorErrors} from "../base/ReactorErrors.sol"; import {Permit2Lib} from "../lib/Permit2Lib.sol"; import {RelayOrderLib, RelayOrder} from "../lib/RelayOrderLib.sol"; import {ResolvedRelayOrderLib} from "../lib/ResolvedRelayOrderLib.sol"; @@ -27,9 +27,6 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe using RelayOrderLib for RelayOrder; using RelayDecayLib for InputTokenWithRecipient[]; - /// @notice A nested call failed without a revert message - error CallFailed(); - /// @notice permit2 address used for token transfers and signature verification IPermit2 public immutable permit2; From 1fbcf880fd5eb1c3a1b0c1de8677cedc02cd5104 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Tue, 5 Dec 2023 15:32:13 -0500 Subject: [PATCH 12/33] save state --- src/base/ReactorStructs.sol | 13 ++++++++++--- src/lib/Permit2Lib.sol | 12 ++++++++---- src/lib/ResolvedRelayOrderLib.sol | 11 +++++++++++ src/reactors/RelayOrderReactor.sol | 16 +++++++++++++++- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/base/ReactorStructs.sol b/src/base/ReactorStructs.sol index 1790f2f1..b9f52b7b 100644 --- a/src/base/ReactorStructs.sol +++ b/src/base/ReactorStructs.sol @@ -7,9 +7,16 @@ import {OrderInfo, OutputToken} from "UniswapX/src/base/ReactorStructs.sol"; /// @dev tokens that need to be sent from the swapper in order to satisfy an order struct InputTokenWithRecipient { ERC20 token; - uint256 amount; - // Needed for dutch decaying inputs - uint256 maxAmount; + int256 amount; + int256 maxAmount; + address recipient; +} + +/// @dev An amount of input tokens that increases linearly over time +struct RelayInput { + ERC20 token; + int256 startAmount; + int256 endAmount; address recipient; } diff --git a/src/lib/Permit2Lib.sol b/src/lib/Permit2Lib.sol index 0d5e8803..637de923 100644 --- a/src/lib/Permit2Lib.sol +++ b/src/lib/Permit2Lib.sol @@ -63,10 +63,14 @@ library Permit2Lib { ISignatureTransfer.SignatureTransferDetails[] memory details = new ISignatureTransfer.SignatureTransferDetails[](order.inputs.length); for (uint256 i = 0; i < order.inputs.length; i++) { - // if recipient is 0x0, use msg.sender - address recipient = order.inputs[i].recipient == address(0) ? msg.sender : order.inputs[i].recipient; - details[i] = - ISignatureTransfer.SignatureTransferDetails({to: recipient, requestedAmount: order.inputs[i].amount}); + InputTokenWithRecipient memory input = order.inputs[i]; + // we only need to transfer positive token amounts + if(input.amount >= 0) { + // if recipient is 0x0, use msg.sender + address recipient = input.recipient == address(0) ? msg.sender : input.recipient; + details[i] = + ISignatureTransfer.SignatureTransferDetails({to: recipient, requestedAmount: input.amount}); + } } return details; } diff --git a/src/lib/ResolvedRelayOrderLib.sol b/src/lib/ResolvedRelayOrderLib.sol index b95fa2b4..12eec49a 100644 --- a/src/lib/ResolvedRelayOrderLib.sol +++ b/src/lib/ResolvedRelayOrderLib.sol @@ -21,4 +21,15 @@ library ResolvedRelayOrderLib { revert DeadlinePassed(); } } + + function hasNegativeInputs(ResolvedRelayOrder memory resolvedOrder) internal pure returns (bool) { + unchecked { + for (uint256 i = 0; i < resolvedOrder.inputs.length; i++) { + if (resolvedOrder.inputs[i].amount < 0) { + return true; + } + } + return false; + } + } } diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index 66b861ca..cdbb0fe1 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -119,6 +119,20 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe unchecked { for (uint256 i = 0; i < ordersLength; i++) { ResolvedRelayOrder memory resolvedOrder = orders[i]; + // If there are negative inputs, we must transfer those to the swapper from the reactor's balance + if(resolvedOrder.hasNegativeInputs()) { + // Transfer the negative inputs to the swapper + for (uint256 j = 0; j < resolvedOrder.inputs.length; j++) { + InputTokenWithRecipient memory input = resolvedOrder.inputs[j]; + if (input.amount < 0) { + input.token.transfer( + resolvedOrder.info.swapper, + uint256(-input.amount) + ); + } + } + } + emit Fill(orders[i].hash, msg.sender, resolvedOrder.info.swapper, resolvedOrder.info.nonce); } } @@ -167,6 +181,6 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe revert OrderEndTimeBeforeStartTime(); } - // TODO: add additional validations related to relayed actions, if desired + } } From ee9423ad577e0a7d4cf6706a8fd91654bf2b7f03 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 6 Dec 2023 11:11:09 -0500 Subject: [PATCH 13/33] remove constructor param (#13) --- .../RelayOrderReactorIntegrationTest-testExecute.snap | 2 +- ...ReactorIntegrationTest-testExecuteWithNativeAsOutput.snap | 2 +- ...elayOrderReactorIntegrationTest-testPermitAndExecute.snap | 2 +- src/reactors/RelayOrderReactor.sol | 5 +---- .../integration/RelayOrderReactorIntegration.t.sol | 2 +- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap index 2175a16a..37fbf6d2 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap @@ -1 +1 @@ -254368 \ No newline at end of file +254346 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap index e8c58f56..f1743f2f 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap @@ -1 +1 @@ -280732 \ No newline at end of file +280710 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap index 003e4e86..cb9d3a56 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap @@ -1 +1 @@ -310437 \ No newline at end of file +310415 \ No newline at end of file diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index 43e20a0d..769d02b1 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -30,11 +30,8 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe /// @notice permit2 address used for token transfers and signature verification IPermit2 public immutable permit2; - address public immutable universalRouter; - - constructor(IPermit2 _permit2, address _universalRouter) { + constructor(IPermit2 _permit2) { permit2 = _permit2; - universalRouter = _universalRouter; } function execute(SignedOrder calldata order) external payable nonReentrant { diff --git a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol index 0d0f26ed..c205eb5a 100644 --- a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol +++ b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol @@ -64,7 +64,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS json = vm.readFile(string.concat(root, "/test/foundry-tests/interop.json")); vm.createSelectFork(vm.envString("FOUNDRY_RPC_URL"), 17972788); - deployCodeTo("RelayOrderReactor.sol", abi.encode(PERMIT2, UNIVERSAL_ROUTER), RELAY_ORDER_REACTOR); + deployCodeTo("RelayOrderReactor.sol", abi.encode(PERMIT2), RELAY_ORDER_REACTOR); reactor = RelayOrderReactor(RELAY_ORDER_REACTOR); permitExecutor = new PermitExecutor(address(filler), reactor, address(filler)); From 9f846b9eebf4aa4849f01bbf575aacf6a15cda0d Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 6 Dec 2023 11:53:42 -0500 Subject: [PATCH 14/33] Initial types refactor for signed amounts --- src/base/ReactorStructs.sol | 4 +- src/lib/Permit2Lib.sol | 72 ++++++++---------- src/lib/RelayDecayLib.sol | 32 +++++++- src/lib/ResolvedRelayOrderLib.sol | 3 + src/reactors/RelayOrderReactor.sol | 11 +-- .../RelayOrderReactorIntegration.t.sol | 76 ++++++++++--------- test/foundry-tests/lib/RelayDecayLib.t.sol | 35 +++++---- test/foundry-tests/util/PermitSignature.sol | 6 +- test/foundry-tests/util/UnsafeSignedMath.sol | 16 ++++ 9 files changed, 149 insertions(+), 106 deletions(-) create mode 100644 test/foundry-tests/util/UnsafeSignedMath.sol diff --git a/src/base/ReactorStructs.sol b/src/base/ReactorStructs.sol index b9f52b7b..bfc3a7d4 100644 --- a/src/base/ReactorStructs.sol +++ b/src/base/ReactorStructs.sol @@ -15,8 +15,8 @@ struct InputTokenWithRecipient { /// @dev An amount of input tokens that increases linearly over time struct RelayInput { ERC20 token; - int256 startAmount; - int256 endAmount; + uint256 startAmount; + uint256 endAmount; address recipient; } diff --git a/src/lib/Permit2Lib.sol b/src/lib/Permit2Lib.sol index 637de923..13b4adba 100644 --- a/src/lib/Permit2Lib.sol +++ b/src/lib/Permit2Lib.sol @@ -4,47 +4,24 @@ pragma solidity ^0.8.0; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; import {ResolvedOrder} from "UniswapX/src/base/ReactorStructs.sol"; -import {ResolvedRelayOrder} from "../base/ReactorStructs.sol"; +import {ResolvedRelayOrder, InputTokenWithRecipient} from "../base/ReactorStructs.sol"; /// @notice handling some permit2-specific encoding library Permit2Lib { - /// @notice returns a ResolvedOrder into a permit object - function toPermit(ResolvedOrder memory order) - internal - pure - returns (ISignatureTransfer.PermitTransferFrom memory) - { - return ISignatureTransfer.PermitTransferFrom({ - permitted: ISignatureTransfer.TokenPermissions({ - token: address(order.input.token), - amount: order.input.maxAmount - }), - nonce: order.info.nonce, - deadline: order.info.deadline - }); - } - - /// @notice returns a ResolvedOrder into a permit object - function transferDetails(ResolvedOrder memory order, address to) - internal - pure - returns (ISignatureTransfer.SignatureTransferDetails memory) - { - return ISignatureTransfer.SignatureTransferDetails({to: to, requestedAmount: order.input.amount}); - } - /// @notice returns a ResolvedOrder into a permit object function toPermit(ResolvedRelayOrder memory order) internal - pure + view returns (ISignatureTransfer.PermitBatchTransferFrom memory) { + InputTokenWithRecipient[] memory inputsRequired = getPositiveInputs(order.inputs); ISignatureTransfer.TokenPermissions[] memory permissions = - new ISignatureTransfer.TokenPermissions[](order.inputs.length); - for (uint256 i = 0; i < order.inputs.length; i++) { + new ISignatureTransfer.TokenPermissions[](inputsRequired.length); + + for (uint256 i = 0; i < inputsRequired.length; i++) { permissions[i] = ISignatureTransfer.TokenPermissions({ - token: address(order.inputs[i].token), - amount: order.inputs[i].amount + token: address(inputsRequired[i].token), + amount: uint256(inputsRequired[i].amount) // safe to cast here because we check above }); } return ISignatureTransfer.PermitBatchTransferFrom({ @@ -60,18 +37,31 @@ library Permit2Lib { view returns (ISignatureTransfer.SignatureTransferDetails[] memory) { + InputTokenWithRecipient[] memory inputsRequired = getPositiveInputs(order.inputs); ISignatureTransfer.SignatureTransferDetails[] memory details = - new ISignatureTransfer.SignatureTransferDetails[](order.inputs.length); - for (uint256 i = 0; i < order.inputs.length; i++) { - InputTokenWithRecipient memory input = order.inputs[i]; - // we only need to transfer positive token amounts - if(input.amount >= 0) { - // if recipient is 0x0, use msg.sender - address recipient = input.recipient == address(0) ? msg.sender : input.recipient; - details[i] = - ISignatureTransfer.SignatureTransferDetails({to: recipient, requestedAmount: input.amount}); - } + new ISignatureTransfer.SignatureTransferDetails[](inputsRequired.length); + for (uint256 i = 0; i < inputsRequired.length; i++) { + // if recipient is 0x0, use msg.sender + address recipient = inputsRequired[i].recipient == address(0) ? msg.sender : inputsRequired[i].recipient; + details[i] = ISignatureTransfer.SignatureTransferDetails({ + to: recipient, + requestedAmount: uint256(inputsRequired[i].amount) // safe to cast here because we check above + }); } return details; } + + function getPositiveInputs(InputTokenWithRecipient[] memory inputs) + internal + view + returns (InputTokenWithRecipient[] memory) + { + InputTokenWithRecipient[] memory positiveInputs; + for (uint256 i = 0; i < inputs.length; i++) { + if (inputs[i].amount > 0) { + positiveInputs[i] = inputs[i]; + } + } + return positiveInputs; + } } diff --git a/src/lib/RelayDecayLib.sol b/src/lib/RelayDecayLib.sol index 8a8be587..b5eb093a 100644 --- a/src/lib/RelayDecayLib.sol +++ b/src/lib/RelayDecayLib.sol @@ -46,6 +46,36 @@ library RelayDecayLib { } } + function decay(int256 startAmount, int256 endAmount, uint256 decayStartTime, uint256 decayEndTime) + internal + view + returns (int256 decayedAmount) + { + if (decayEndTime < decayStartTime) { + revert EndTimeBeforeStartTime(); + } else if (decayEndTime <= block.timestamp) { + decayedAmount = endAmount; + } else if (decayStartTime >= block.timestamp) { + decayedAmount = startAmount; + } else { + unchecked { + uint256 elapsed = block.timestamp - decayStartTime; + uint256 duration = decayEndTime - decayStartTime; + if (endAmount < startAmount) { + decayedAmount = + startAmount - int256(uint256(abs(startAmount) - abs(endAmount)).mulDivDown(elapsed, duration)); + } else { + decayedAmount = + startAmount + int256(uint256(abs(endAmount) - abs(startAmount)).mulDivDown(elapsed, duration)); + } + } + } + } + + function abs(int256 x) internal pure returns (int256) { + return x >= 0 ? x : -x; + } + /// @notice returns a decayed input array using the given decay spec and times /// @param inputs The input array to decay /// @param decayStartTime The time to start decaying @@ -79,7 +109,7 @@ library RelayDecayLib { revert IncorrectAmounts(); } - uint256 decayedInput = RelayDecayLib.decay(input.amount, input.maxAmount, decayStartTime, decayEndTime); + int256 decayedInput = RelayDecayLib.decay(input.amount, input.maxAmount, decayStartTime, decayEndTime); result = InputTokenWithRecipient(input.token, decayedInput, input.maxAmount, input.recipient); } } diff --git a/src/lib/ResolvedRelayOrderLib.sol b/src/lib/ResolvedRelayOrderLib.sol index 12eec49a..e095d67a 100644 --- a/src/lib/ResolvedRelayOrderLib.sol +++ b/src/lib/ResolvedRelayOrderLib.sol @@ -10,6 +10,9 @@ library ResolvedRelayOrderLib { /// @notice thrown if the order has expired error DeadlinePassed(); + /// @notice thrown if the order has incorrect inputs + error InvalidInputs(); + /// @notice Validates a resolved order, reverting if invalid /// @param filler The filler of the order function validate(ResolvedRelayOrder memory resolvedOrder, address filler) internal view { diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index cdbb0fe1..f35bff42 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -120,19 +120,16 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe for (uint256 i = 0; i < ordersLength; i++) { ResolvedRelayOrder memory resolvedOrder = orders[i]; // If there are negative inputs, we must transfer those to the swapper from the reactor's balance - if(resolvedOrder.hasNegativeInputs()) { + if (resolvedOrder.hasNegativeInputs()) { // Transfer the negative inputs to the swapper for (uint256 j = 0; j < resolvedOrder.inputs.length; j++) { InputTokenWithRecipient memory input = resolvedOrder.inputs[j]; if (input.amount < 0) { - input.token.transfer( - resolvedOrder.info.swapper, - uint256(-input.amount) - ); + input.token.transfer(resolvedOrder.info.swapper, uint256(-input.amount)); } } } - + emit Fill(orders[i].hash, msg.sender, resolvedOrder.info.swapper, resolvedOrder.info.nonce); } } @@ -180,7 +177,5 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe if (order.decayEndTime < order.decayStartTime) { revert OrderEndTimeBeforeStartTime(); } - - } } diff --git a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol index 0a227677..7bf779a5 100644 --- a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol +++ b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol @@ -17,11 +17,14 @@ import {RelayOrderLib, RelayOrder} from "../../../src/lib/RelayOrderLib.sol"; import {RelayOrderReactor} from "../../../src/reactors/RelayOrderReactor.sol"; import {PermitExecutor} from "../../../src/sample-executors/PermitExecutor.sol"; import {MethodParameters, Interop} from "../util/Interop.sol"; +import {UnsafeSignedMath} from "../util/UnsafeSignedMath.sol"; contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitSignature { using stdJson for string; using OrderInfoBuilder for OrderInfo; using RelayOrderLib for RelayOrder; + using UnsafeSignedMath for int256; + using UnsafeSignedMath for uint256; uint256 constant ONE = 10 ** 18; uint256 constant USDC_ONE = 10 ** 6; @@ -89,19 +92,27 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS assertEq(DAI.balanceOf(address(reactor)), 0, "reactor should have no DAI"); } + function _createInputTokenWithRecipient(ERC20 token, uint256 amount, uint256 maxAmount, address recipient) + internal + pure + returns (InputTokenWithRecipient memory) + { + return InputTokenWithRecipient({token: token, amount: int256(amount), maxAmount: int256(maxAmount), recipient: recipient}); + } + // swapper creates one order containing a universal router swap for 100 DAI -> USDC // order contains two inputs: DAI for the swap and USDC as gas payment for fillers // at the forked block, 95276229 is the minAmountOut function testExecute() public { InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](2); - inputTokens[0] = - InputTokenWithRecipient({token: DAI, amount: 100 * ONE, maxAmount: 100 * ONE, recipient: UNIVERSAL_ROUTER}); - inputTokens[1] = InputTokenWithRecipient({ - token: USDC, - amount: 10 * USDC_ONE, - maxAmount: 10 * USDC_ONE, - recipient: address(0) - }); + inputTokens[0] = _createInputTokenWithRecipient(DAI, 100 * ONE, 100 * ONE, UNIVERSAL_ROUTER); + inputTokens[1] = _createInputTokenWithRecipient( + USDC, + 10 * USDC_ONE, + 10 * USDC_ONE, + address(0) + ); + uint256 amountOutMin = 95 * USDC_ONE; @@ -146,18 +157,13 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS // so we will relay a USDC 2612 permit to the P2 contract first // making a USDC -> DAI swap InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](2); - inputTokens[0] = InputTokenWithRecipient({ - token: USDC, - amount: 100 * USDC_ONE, - maxAmount: 100 * USDC_ONE, - recipient: UNIVERSAL_ROUTER - }); - inputTokens[1] = InputTokenWithRecipient({ - token: USDC, - amount: 10 * USDC_ONE, - maxAmount: 10 * USDC_ONE, - recipient: address(0) - }); + inputTokens[0] = _createInputTokenWithRecipient(USDC, 100 * USDC_ONE, 100 * USDC_ONE, UNIVERSAL_ROUTER); + inputTokens[1] = _createInputTokenWithRecipient( + USDC, + 10 * USDC_ONE, + 10 * USDC_ONE, + address(0) + ); uint256 amountOutMin = 95 * ONE; @@ -236,14 +242,13 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS // at the forked block, X is the minAmountOut function testExecuteWithNativeAsOutput() public { InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](2); - inputTokens[0] = - InputTokenWithRecipient({token: DAI, amount: 100 * ONE, maxAmount: 100 * ONE, recipient: UNIVERSAL_ROUTER}); - inputTokens[1] = InputTokenWithRecipient({ - token: USDC, - amount: 10 * USDC_ONE, - maxAmount: 10 * USDC_ONE, - recipient: address(0) - }); + inputTokens[0] = _createInputTokenWithRecipient(DAI, 100 * ONE, 100 * ONE, UNIVERSAL_ROUTER); + inputTokens[1] = _createInputTokenWithRecipient( + USDC, + 10 * USDC_ONE, + 10 * USDC_ONE, + address(0) + ); uint256 amountOutMin = 51651245170979377; // with 5% slipapge at forked block @@ -290,14 +295,13 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS // calldata includes a SWEEP back to them which should cause the transaction to revert function testExecuteDoesNotSucceedIfReactorIsRecipientAndUniversalRouterSweep() public { InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](2); - inputTokens[0] = - InputTokenWithRecipient({token: DAI, amount: 100 * ONE, maxAmount: 100 * ONE, recipient: UNIVERSAL_ROUTER}); - inputTokens[1] = InputTokenWithRecipient({ - token: USDC, - amount: 10 * USDC_ONE, - maxAmount: 10 * USDC_ONE, - recipient: address(0) - }); + inputTokens[0] = _createInputTokenWithRecipient(DAI, 100 * ONE, 100 * ONE, UNIVERSAL_ROUTER); + inputTokens[1] = _createInputTokenWithRecipient( + USDC, + 10 * USDC_ONE, + 10 * USDC_ONE, + address(0) + ); uint256 amountOutMin = 95 * USDC_ONE; diff --git a/test/foundry-tests/lib/RelayDecayLib.t.sol b/test/foundry-tests/lib/RelayDecayLib.t.sol index 4f517e8e..3b16325b 100644 --- a/test/foundry-tests/lib/RelayDecayLib.t.sol +++ b/test/foundry-tests/lib/RelayDecayLib.t.sol @@ -10,68 +10,71 @@ contract RelayDecayLibTest is Test { assertEq(RelayDecayLib.decay(amount, amount, decayStartTime, decayEndTime), amount); } + uint256 constant ONE_ETHER = 1 ether; + uint256 constant TWO_ETHER = 2 ether; + function testRelayDecayNoDecayYet() public { vm.warp(100); // at decayStartTime - assertEq(RelayDecayLib.decay(1 ether, 2 ether, 100, 200), 1 ether); + assertEq(RelayDecayLib.decay(ONE_ETHER, TWO_ETHER, 100, 200), ONE_ETHER); vm.warp(80); // before decayStartTime - assertEq(RelayDecayLib.decay(1 ether, 2 ether, 100, 200), 1 ether); + assertEq(RelayDecayLib.decay(ONE_ETHER, TWO_ETHER, 100, 200), ONE_ETHER); } function testRelayDecayNoDecayYetNegative() public { vm.warp(100); // at decayStartTime - assertEq(RelayDecayLib.decay(2 ether, 1 ether, 100, 200), 2 ether); + assertEq(RelayDecayLib.decay(TWO_ETHER, ONE_ETHER, 100, 200), TWO_ETHER); vm.warp(80); // before decayStartTime - assertEq(RelayDecayLib.decay(2 ether, 1 ether, 100, 200), 2 ether); + assertEq(RelayDecayLib.decay(TWO_ETHER, ONE_ETHER, 100, 200), TWO_ETHER); } function testRelayDecay() public { vm.warp(150); - assertEq(RelayDecayLib.decay(1 ether, 2 ether, 100, 200), 1.5 ether); + assertEq(RelayDecayLib.decay(ONE_ETHER, TWO_ETHER, 100, 200), 1.5 ether); vm.warp(180); - assertEq(RelayDecayLib.decay(1 ether, 2 ether, 100, 200), 1.8 ether); + assertEq(RelayDecayLib.decay(ONE_ETHER, TWO_ETHER, 100, 200), 1.8 ether); vm.warp(110); - assertEq(RelayDecayLib.decay(1 ether, 2 ether, 100, 200), 1.1 ether); + assertEq(RelayDecayLib.decay(ONE_ETHER, TWO_ETHER, 100, 200), 1.1 ether); vm.warp(190); - assertEq(RelayDecayLib.decay(1 ether, 2 ether, 100, 200), 1.9 ether); + assertEq(RelayDecayLib.decay(ONE_ETHER, TWO_ETHER, 100, 200), 1.9 ether); } function testRelayDecayNegative() public { vm.warp(150); - assertEq(RelayDecayLib.decay(2 ether, 1 ether, 100, 200), 1.5 ether); + assertEq(RelayDecayLib.decay(TWO_ETHER, ONE_ETHER, 100, 200), 1.5 ether); vm.warp(180); - assertEq(RelayDecayLib.decay(2 ether, 1 ether, 100, 200), 1.2 ether); + assertEq(RelayDecayLib.decay(TWO_ETHER, ONE_ETHER, 100, 200), 1.2 ether); vm.warp(110); - assertEq(RelayDecayLib.decay(2 ether, 1 ether, 100, 200), 1.9 ether); + assertEq(RelayDecayLib.decay(TWO_ETHER, ONE_ETHER, 100, 200), 1.9 ether); vm.warp(190); - assertEq(RelayDecayLib.decay(2 ether, 1 ether, 100, 200), 1.1 ether); + assertEq(RelayDecayLib.decay(TWO_ETHER, ONE_ETHER, 100, 200), 1.1 ether); } function testRelayDecayFullyDecayed() public { vm.warp(200); - assertEq(RelayDecayLib.decay(1 ether, 2 ether, 100, 200), 2 ether); + assertEq(RelayDecayLib.decay(ONE_ETHER, TWO_ETHER, 100, 200), TWO_ETHER); vm.warp(250); - assertEq(RelayDecayLib.decay(1 ether, 2 ether, 100, 200), 2 ether); + assertEq(RelayDecayLib.decay(ONE_ETHER, TWO_ETHER, 100, 200), TWO_ETHER); } function testRelayDecayFullyDecayedNegative() public { vm.warp(200); - assertEq(RelayDecayLib.decay(2 ether, 1 ether, 100, 200), 1 ether); + assertEq(RelayDecayLib.decay(TWO_ETHER, ONE_ETHER, 100, 200), ONE_ETHER); vm.warp(250); - assertEq(RelayDecayLib.decay(2 ether, 1 ether, 100, 200), 1 ether); + assertEq(RelayDecayLib.decay(TWO_ETHER, ONE_ETHER, 100, 200), ONE_ETHER); } function testRelayDecayBounded(uint256 startAmount, uint256 endAmount, uint256 decayStartTime, uint256 decayEndTime) diff --git a/test/foundry-tests/util/PermitSignature.sol b/test/foundry-tests/util/PermitSignature.sol index c287db33..80b05dd7 100644 --- a/test/foundry-tests/util/PermitSignature.sol +++ b/test/foundry-tests/util/PermitSignature.sol @@ -109,8 +109,10 @@ contract PermitSignature is Test { ISignatureTransfer.TokenPermissions[] memory permissions = new ISignatureTransfer.TokenPermissions[](inputs.length); for (uint256 i = 0; i < inputs.length; i++) { - permissions[i] = - ISignatureTransfer.TokenPermissions({token: address(inputs[i].token), amount: inputs[i].amount}); + if(inputs[i].amount > 0) { + permissions[i] = + ISignatureTransfer.TokenPermissions({token: address(inputs[i].token), amount: uint256(inputs[i].amount)}); + } } ISignatureTransfer.PermitBatchTransferFrom memory permit = ISignatureTransfer.PermitBatchTransferFrom({ diff --git a/test/foundry-tests/util/UnsafeSignedMath.sol b/test/foundry-tests/util/UnsafeSignedMath.sol new file mode 100644 index 00000000..0254c891 --- /dev/null +++ b/test/foundry-tests/util/UnsafeSignedMath.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +library UnsafeSignedMath { + function mul(uint256 a, int256 b) internal pure returns (int256) { + unchecked { + return int256(a) * b; + } + } + + function mul(int256 a, uint256 b) internal pure returns (int256) { + unchecked { + return a * int256(b); + } + } +} \ No newline at end of file From 127d6250481b97e8a888699b5b52c895624eccfb Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 6 Dec 2023 16:11:36 -0500 Subject: [PATCH 15/33] Fix decay lib tests --- src/lib/RelayDecayLib.sol | 4 +-- test/foundry-tests/lib/RelayDecayLib.t.sol | 35 +++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/lib/RelayDecayLib.sol b/src/lib/RelayDecayLib.sol index b5eb093a..6a3928f7 100644 --- a/src/lib/RelayDecayLib.sol +++ b/src/lib/RelayDecayLib.sol @@ -63,10 +63,10 @@ library RelayDecayLib { uint256 duration = decayEndTime - decayStartTime; if (endAmount < startAmount) { decayedAmount = - startAmount - int256(uint256(abs(startAmount) - abs(endAmount)).mulDivDown(elapsed, duration)); + startAmount - int256(uint256(abs(startAmount - endAmount)).mulDivDown(elapsed, duration)); } else { decayedAmount = - startAmount + int256(uint256(abs(endAmount) - abs(startAmount)).mulDivDown(elapsed, duration)); + startAmount + int256(uint256(abs(endAmount - startAmount)).mulDivDown(elapsed, duration)); } } } diff --git a/test/foundry-tests/lib/RelayDecayLib.t.sol b/test/foundry-tests/lib/RelayDecayLib.t.sol index 3b16325b..ffa4b4dd 100644 --- a/test/foundry-tests/lib/RelayDecayLib.t.sol +++ b/test/foundry-tests/lib/RelayDecayLib.t.sol @@ -86,8 +86,23 @@ contract RelayDecayLibTest is Test { assertGe(decayed, startAmount); assertLe(decayed, endAmount); } + + function testRelayDecayBounded(int256 startAmount, int256 endAmount, uint256 decayStartTime, uint256 decayEndTime) + public + { + vm.assume(endAmount > startAmount); + vm.assume(decayEndTime >= decayStartTime); + unchecked { + // given that endAmount > startAmount, on overflow, check will be less than the absolute value of endAmount + int256 check = (endAmount > 0 ? endAmount : -endAmount) + (startAmount > 0 ? startAmount : -startAmount); + vm.assume(check >= (endAmount > 0 ? endAmount : -endAmount)); + } + int256 decayed = RelayDecayLib.decay(startAmount, endAmount, decayStartTime, decayEndTime); + assertGe(decayed, startAmount); + assertLe(decayed, endAmount); + } - function testRelayDecayNegative( + function testRelayDecayDecreasing( uint256 startAmount, uint256 endAmount, uint256 decayStartTime, @@ -100,6 +115,24 @@ contract RelayDecayLibTest is Test { assertGe(decayed, endAmount); } + function testRelayDecayDecreasing( + int256 startAmount, + int256 endAmount, + uint256 decayStartTime, + uint256 decayEndTime + ) public { + vm.assume(endAmount < startAmount); + vm.assume(decayEndTime >= decayStartTime); + unchecked { + // given that endAmount < startAmount, on overflow, check will be less than the absolute value of startAmount + int256 check = (endAmount > 0 ? endAmount : -endAmount) + (startAmount > 0 ? startAmount : -startAmount); + vm.assume(check >= (startAmount > 0 ? startAmount : -startAmount)); + } + int256 decayed = RelayDecayLib.decay(startAmount, endAmount, decayStartTime, decayEndTime); + assertLe(decayed, startAmount); + assertGe(decayed, endAmount); + } + function testRelayDecayInvalidTimes( uint256 startAmount, uint256 endAmount, From 388c8c3e7383e782f075a299ebe9e2cafab8321a Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 6 Dec 2023 16:20:37 -0500 Subject: [PATCH 16/33] Fix out of bounds error and integ tests pass --- ...derReactorIntegrationTest-testExecute.snap | 2 +- ...ionTest-testExecuteWithNativeAsOutput.snap | 2 +- ...rIntegrationTest-testPermitAndExecute.snap | 2 +- src/lib/Permit2Lib.sol | 2 +- src/reactors/RelayOrderReactor.sol | 11 +++--- .../RelayOrderReactorIntegration.t.sol | 36 ++++++------------- test/foundry-tests/lib/RelayDecayLib.t.sol | 2 +- test/foundry-tests/util/PermitSignature.sol | 8 +++-- test/foundry-tests/util/UnsafeSignedMath.sol | 2 +- 9 files changed, 25 insertions(+), 42 deletions(-) diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap index 81bc663b..8cc5cdc2 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap @@ -1 +1 @@ -254405 \ No newline at end of file +256904 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap index b95b8fc1..bb5c7fb7 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap @@ -1 +1 @@ -280769 \ No newline at end of file +283270 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap index 113b6799..97a06ae7 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap @@ -1 +1 @@ -310474 \ No newline at end of file +312974 \ No newline at end of file diff --git a/src/lib/Permit2Lib.sol b/src/lib/Permit2Lib.sol index 13b4adba..d46bcc13 100644 --- a/src/lib/Permit2Lib.sol +++ b/src/lib/Permit2Lib.sol @@ -56,7 +56,7 @@ library Permit2Lib { view returns (InputTokenWithRecipient[] memory) { - InputTokenWithRecipient[] memory positiveInputs; + InputTokenWithRecipient[] memory positiveInputs = new InputTokenWithRecipient[](inputs.length); for (uint256 i = 0; i < inputs.length; i++) { if (inputs[i].amount > 0) { positiveInputs[i] = inputs[i]; diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index f35bff42..468b0ab2 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -120,13 +120,10 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe for (uint256 i = 0; i < ordersLength; i++) { ResolvedRelayOrder memory resolvedOrder = orders[i]; // If there are negative inputs, we must transfer those to the swapper from the reactor's balance - if (resolvedOrder.hasNegativeInputs()) { - // Transfer the negative inputs to the swapper - for (uint256 j = 0; j < resolvedOrder.inputs.length; j++) { - InputTokenWithRecipient memory input = resolvedOrder.inputs[j]; - if (input.amount < 0) { - input.token.transfer(resolvedOrder.info.swapper, uint256(-input.amount)); - } + for (uint256 j = 0; j < resolvedOrder.inputs.length; j++) { + InputTokenWithRecipient memory input = resolvedOrder.inputs[j]; + if (input.amount < 0) { + input.token.transfer(resolvedOrder.info.swapper, uint256(-input.amount)); } } diff --git a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol index 7bf779a5..05f19a9e 100644 --- a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol +++ b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol @@ -97,7 +97,12 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS pure returns (InputTokenWithRecipient memory) { - return InputTokenWithRecipient({token: token, amount: int256(amount), maxAmount: int256(maxAmount), recipient: recipient}); + return InputTokenWithRecipient({ + token: token, + amount: int256(amount), + maxAmount: int256(maxAmount), + recipient: recipient + }); } // swapper creates one order containing a universal router swap for 100 DAI -> USDC @@ -106,13 +111,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS function testExecute() public { InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](2); inputTokens[0] = _createInputTokenWithRecipient(DAI, 100 * ONE, 100 * ONE, UNIVERSAL_ROUTER); - inputTokens[1] = _createInputTokenWithRecipient( - USDC, - 10 * USDC_ONE, - 10 * USDC_ONE, - address(0) - ); - + inputTokens[1] = _createInputTokenWithRecipient(USDC, 10 * USDC_ONE, 10 * USDC_ONE, address(0)); uint256 amountOutMin = 95 * USDC_ONE; @@ -158,12 +157,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS // making a USDC -> DAI swap InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](2); inputTokens[0] = _createInputTokenWithRecipient(USDC, 100 * USDC_ONE, 100 * USDC_ONE, UNIVERSAL_ROUTER); - inputTokens[1] = _createInputTokenWithRecipient( - USDC, - 10 * USDC_ONE, - 10 * USDC_ONE, - address(0) - ); + inputTokens[1] = _createInputTokenWithRecipient(USDC, 10 * USDC_ONE, 10 * USDC_ONE, address(0)); uint256 amountOutMin = 95 * ONE; @@ -243,12 +237,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS function testExecuteWithNativeAsOutput() public { InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](2); inputTokens[0] = _createInputTokenWithRecipient(DAI, 100 * ONE, 100 * ONE, UNIVERSAL_ROUTER); - inputTokens[1] = _createInputTokenWithRecipient( - USDC, - 10 * USDC_ONE, - 10 * USDC_ONE, - address(0) - ); + inputTokens[1] = _createInputTokenWithRecipient(USDC, 10 * USDC_ONE, 10 * USDC_ONE, address(0)); uint256 amountOutMin = 51651245170979377; // with 5% slipapge at forked block @@ -296,12 +285,7 @@ contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitS function testExecuteDoesNotSucceedIfReactorIsRecipientAndUniversalRouterSweep() public { InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](2); inputTokens[0] = _createInputTokenWithRecipient(DAI, 100 * ONE, 100 * ONE, UNIVERSAL_ROUTER); - inputTokens[1] = _createInputTokenWithRecipient( - USDC, - 10 * USDC_ONE, - 10 * USDC_ONE, - address(0) - ); + inputTokens[1] = _createInputTokenWithRecipient(USDC, 10 * USDC_ONE, 10 * USDC_ONE, address(0)); uint256 amountOutMin = 95 * USDC_ONE; diff --git a/test/foundry-tests/lib/RelayDecayLib.t.sol b/test/foundry-tests/lib/RelayDecayLib.t.sol index ffa4b4dd..137dd5aa 100644 --- a/test/foundry-tests/lib/RelayDecayLib.t.sol +++ b/test/foundry-tests/lib/RelayDecayLib.t.sol @@ -86,7 +86,7 @@ contract RelayDecayLibTest is Test { assertGe(decayed, startAmount); assertLe(decayed, endAmount); } - + function testRelayDecayBounded(int256 startAmount, int256 endAmount, uint256 decayStartTime, uint256 decayEndTime) public { diff --git a/test/foundry-tests/util/PermitSignature.sol b/test/foundry-tests/util/PermitSignature.sol index 80b05dd7..885a036d 100644 --- a/test/foundry-tests/util/PermitSignature.sol +++ b/test/foundry-tests/util/PermitSignature.sol @@ -109,9 +109,11 @@ contract PermitSignature is Test { ISignatureTransfer.TokenPermissions[] memory permissions = new ISignatureTransfer.TokenPermissions[](inputs.length); for (uint256 i = 0; i < inputs.length; i++) { - if(inputs[i].amount > 0) { - permissions[i] = - ISignatureTransfer.TokenPermissions({token: address(inputs[i].token), amount: uint256(inputs[i].amount)}); + if (inputs[i].amount > 0) { + permissions[i] = ISignatureTransfer.TokenPermissions({ + token: address(inputs[i].token), + amount: uint256(inputs[i].amount) + }); } } diff --git a/test/foundry-tests/util/UnsafeSignedMath.sol b/test/foundry-tests/util/UnsafeSignedMath.sol index 0254c891..61aed1cf 100644 --- a/test/foundry-tests/util/UnsafeSignedMath.sol +++ b/test/foundry-tests/util/UnsafeSignedMath.sol @@ -13,4 +13,4 @@ library UnsafeSignedMath { return a * int256(b); } } -} \ No newline at end of file +} From f1522837c93f21de35856c0e0c8ebf11c385c845 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 6 Dec 2023 16:29:10 -0500 Subject: [PATCH 17/33] Add unit tests --- .../RelayOrderReactorIntegration.t.sol | 3 -- .../reactors/RelayOrderReactorTest.sol | 34 +++++++++++++++ test/foundry-tests/util/UnsafeSignedMath.sol | 16 ------- test/foundry-tests/util/mock/MockERC20.sol | 20 --------- test/foundry-tests/util/mock/MockExecutor.sol | 42 +++++++++++++++++++ 5 files changed, 76 insertions(+), 39 deletions(-) create mode 100644 test/foundry-tests/reactors/RelayOrderReactorTest.sol delete mode 100644 test/foundry-tests/util/UnsafeSignedMath.sol delete mode 100644 test/foundry-tests/util/mock/MockERC20.sol create mode 100644 test/foundry-tests/util/mock/MockExecutor.sol diff --git a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol index 05f19a9e..54a11e60 100644 --- a/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol +++ b/test/foundry-tests/integration/RelayOrderReactorIntegration.t.sol @@ -17,14 +17,11 @@ import {RelayOrderLib, RelayOrder} from "../../../src/lib/RelayOrderLib.sol"; import {RelayOrderReactor} from "../../../src/reactors/RelayOrderReactor.sol"; import {PermitExecutor} from "../../../src/sample-executors/PermitExecutor.sol"; import {MethodParameters, Interop} from "../util/Interop.sol"; -import {UnsafeSignedMath} from "../util/UnsafeSignedMath.sol"; contract RelayOrderReactorIntegrationTest is GasSnapshot, Test, Interop, PermitSignature { using stdJson for string; using OrderInfoBuilder for OrderInfo; using RelayOrderLib for RelayOrder; - using UnsafeSignedMath for int256; - using UnsafeSignedMath for uint256; uint256 constant ONE = 10 ** 18; uint256 constant USDC_ONE = 10 ** 6; diff --git a/test/foundry-tests/reactors/RelayOrderReactorTest.sol b/test/foundry-tests/reactors/RelayOrderReactorTest.sol new file mode 100644 index 00000000..9f8fbeed --- /dev/null +++ b/test/foundry-tests/reactors/RelayOrderReactorTest.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Test} from "forge-std/Test.sol"; +import {OrderInfoBuilder} from "UniswapX/test/util/OrderInfoBuilder.sol"; +import {OrderInfo, SignedOrder} from "UniswapX/src/base/ReactorStructs.sol"; +import {ArrayBuilder} from "UniswapX/test/util/ArrayBuilder.sol"; +import {MockERC20} from "UniswapX/test/util/mock/MockERC20.sol"; +import {CurrencyLibrary} from "UniswapX/src/lib/CurrencyLibrary.sol"; +import {InputTokenWithRecipient, ResolvedRelayOrder} from "../../../src/base/ReactorStructs.sol"; +import {RelayOrderLib, RelayOrder} from "../../../src/lib/RelayOrderLib.sol"; +import {RelayOrderReactor} from "../../../src/reactors/RelayOrderReactor.sol"; +import {PermitSignature} from "../util/PermitSignature.sol"; + +contract RelayOrderReactorTest is Test, PermitSignature { + using OrderInfoBuilder for OrderInfo; + using RelayOrderLib for RelayOrder; + + MockERC20 tokenIn; + MockERC20 tokenOut; + MockFillContract fillContract; + MockValidationContract additionalValidationContract; + IPermit2 permit2; + RelayOrderReactor reactor; + uint256 swapperPrivateKey; + address swapper; + + function setUp() { + tokenIn = new MockERC20("Input", "IN", 18); + tokenOut = new MockERC20("Output", "OUT", 18); + + + } +} \ No newline at end of file diff --git a/test/foundry-tests/util/UnsafeSignedMath.sol b/test/foundry-tests/util/UnsafeSignedMath.sol deleted file mode 100644 index 61aed1cf..00000000 --- a/test/foundry-tests/util/UnsafeSignedMath.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -library UnsafeSignedMath { - function mul(uint256 a, int256 b) internal pure returns (int256) { - unchecked { - return int256(a) * b; - } - } - - function mul(int256 a, uint256 b) internal pure returns (int256) { - unchecked { - return a * int256(b); - } - } -} diff --git a/test/foundry-tests/util/mock/MockERC20.sol b/test/foundry-tests/util/mock/MockERC20.sol deleted file mode 100644 index e2bc6c1c..00000000 --- a/test/foundry-tests/util/mock/MockERC20.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {ERC20} from "solmate/src/tokens/ERC20.sol"; - -contract MockERC20 is ERC20 { - constructor(string memory name, string memory symbol, uint8 decimals) ERC20(name, symbol, decimals) {} - - function mint(address _to, uint256 _amount) public { - _mint(_to, _amount); - } - - function forceApprove(address _from, address _to, uint256 _amount) public returns (bool) { - allowance[_from][_to] = _amount; - - emit Approval(_from, _to, _amount); - - return true; - } -} diff --git a/test/foundry-tests/util/mock/MockExecutor.sol b/test/foundry-tests/util/mock/MockExecutor.sol new file mode 100644 index 00000000..28398c5a --- /dev/null +++ b/test/foundry-tests/util/mock/MockExecutor.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {CurrencyLibrary} from "UniswapX/src/lib/CurrencyLibrary.sol"; +import {IReactor} from "UniswapX/src/interfaces/IReactor.sol"; +import {IReactorCallback} from "UniswapX/src/interfaces/IReactorCallback.sol"; +import {ResolvedOrder, OutputToken, SignedOrder} from "UniswapX/src/base/ReactorStructs.sol"; + +contract MockFillContract { + using CurrencyLibrary for address; + + 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, 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]; + if (output.token.isNative()) { + CurrencyLibrary.transferNative(address(reactor), output.amount); + } else { + ERC20(output.token).approve(address(reactor), type(uint256).max); + } + } + } + } +} From bc99032e4a393d312bdc21adcb333f296e69262f Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 6 Dec 2023 16:43:32 -0500 Subject: [PATCH 18/33] Make RelayOrderReactor inherit IReactor --- ...derReactorIntegrationTest-testExecute.snap | 2 +- ...ionTest-testExecuteWithNativeAsOutput.snap | 2 +- ...rIntegrationTest-testPermitAndExecute.snap | 2 +- src/interfaces/IRelayOrderReactor.sol | 15 -------- src/interfaces/IRelayOrderReactorCallback.sol | 13 +++++++ src/reactors/RelayOrderReactor.sol | 34 +++++++++++++++++-- src/sample-executors/PermitExecutor.sol | 6 ++-- 7 files changed, 51 insertions(+), 23 deletions(-) delete mode 100644 src/interfaces/IRelayOrderReactor.sol create mode 100644 src/interfaces/IRelayOrderReactorCallback.sol diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap index 37fbf6d2..6ca941ad 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap @@ -1 +1 @@ -254346 \ No newline at end of file +254413 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap index f1743f2f..44dc11ae 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap @@ -1 +1 @@ -280710 \ No newline at end of file +280777 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap index cb9d3a56..356463af 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap @@ -1 +1 @@ -310415 \ No newline at end of file +310482 \ No newline at end of file diff --git a/src/interfaces/IRelayOrderReactor.sol b/src/interfaces/IRelayOrderReactor.sol deleted file mode 100644 index dcfc84f8..00000000 --- a/src/interfaces/IRelayOrderReactor.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {SignedOrder} from "UniswapX/src/base/ReactorStructs.sol"; - -/// @notice Interface for the relay order reactors -interface IRelayOrderReactor { - /// @notice Execute a single order - /// @param order The order definition and valid signature to execute - function execute(SignedOrder calldata order) 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; -} diff --git a/src/interfaces/IRelayOrderReactorCallback.sol b/src/interfaces/IRelayOrderReactorCallback.sol new file mode 100644 index 00000000..7b83e921 --- /dev/null +++ b/src/interfaces/IRelayOrderReactorCallback.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {ResolvedRelayOrder} from "../base/ReactorStructs.sol"; + +/// @notice Callback for executing orders through a reactor. +interface IRelayOrderReactorCallback { + /// @notice Called by the reactor during the execution of an order + /// @param resolvedOrders Has inputs and outputs + /// @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(ResolvedRelayOrder[] memory resolvedOrders, bytes memory callbackData) external; +} diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index 769d02b1..04b4d321 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -7,8 +7,9 @@ import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; import {SignedOrder, OrderInfo} from "UniswapX/src/base/ReactorStructs.sol"; import {ReactorEvents} from "UniswapX/src/base/ReactorEvents.sol"; +import {IReactor} from "UniswapX/src/interfaces/IReactor.sol"; import {CurrencyLibrary} from "UniswapX/src/lib/CurrencyLibrary.sol"; -import {IRelayOrderReactor} from "../interfaces/IRelayOrderReactor.sol"; +import {IRelayOrderReactorCallback} from "../interfaces/IRelayOrderReactorCallback.sol"; import {InputTokenWithRecipient, ResolvedRelayOrder} from "../base/ReactorStructs.sol"; import {ReactorErrors} from "../base/ReactorErrors.sol"; import {Permit2Lib} from "../lib/Permit2Lib.sol"; @@ -19,7 +20,7 @@ import {RelayDecayLib} from "../lib/RelayDecayLib.sol"; /// @notice Reactor for handling the execution of RelayOrders /// @notice This contract MUST NOT have approvals or priviledged access /// @notice any funds in this contract can be swept away by anyone -contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRelayOrderReactor { +contract RelayOrderReactor is IReactor, ReactorEvents, ReactorErrors, ReentrancyGuard { using SafeTransferLib for ERC20; using CurrencyLibrary for address; using Permit2Lib for ResolvedRelayOrder; @@ -43,6 +44,16 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe _fill(resolvedOrders); } + function executeWithCallback(SignedOrder calldata order, bytes calldata callbackData) external payable nonReentrant { + ResolvedRelayOrder[] memory resolvedOrders = new ResolvedRelayOrder[](1); + resolvedOrders[0] = resolve(order); + + _prepare(resolvedOrders); + _execute(resolvedOrders); + IRelayOrderReactorCallback(msg.sender).reactorCallback(resolvedOrders, callbackData); + _fill(resolvedOrders); + } + function executeBatch(SignedOrder[] calldata orders) external payable nonReentrant { uint256 ordersLength = orders.length; ResolvedRelayOrder[] memory resolvedOrders = new ResolvedRelayOrder[](ordersLength); @@ -58,6 +69,25 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe _fill(resolvedOrders); } + function executeBatchWithCallback( + SignedOrder[] calldata orders, + bytes calldata callbackData + ) external payable nonReentrant { + uint256 ordersLength = orders.length; + ResolvedRelayOrder[] memory resolvedOrders = new ResolvedRelayOrder[](ordersLength); + + unchecked { + for (uint256 i = 0; i < ordersLength; i++) { + resolvedOrders[i] = resolve(orders[i]); + } + } + + _prepare(resolvedOrders); + _execute(resolvedOrders); + IRelayOrderReactorCallback(msg.sender).reactorCallback(resolvedOrders, callbackData); + _fill(resolvedOrders); + } + function _execute(ResolvedRelayOrder[] memory orders) internal { uint256 ordersLength = orders.length; // actions are encoded as (address target, uint256 value, bytes data)[] diff --git a/src/sample-executors/PermitExecutor.sol b/src/sample-executors/PermitExecutor.sol index 830f8648..913e2fa2 100644 --- a/src/sample-executors/PermitExecutor.sol +++ b/src/sample-executors/PermitExecutor.sol @@ -6,7 +6,7 @@ import {Owned} from "solmate/src/auth/Owned.sol"; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {SignedOrder} from "UniswapX/src/base/ReactorStructs.sol"; import {CurrencyLibrary} from "UniswapX/src/lib/CurrencyLibrary.sol"; -import {IRelayOrderReactor} from "../interfaces/IRelayOrderReactor.sol"; +import {IReactor} from "UniswapX/src/interfaces/IReactor.sol"; /// @notice A simple fill contract that relays 2612 style permits on chain before filling a Relay order contract PermitExecutor is Owned { @@ -17,7 +17,7 @@ contract PermitExecutor is Owned { error CallerNotWhitelisted(); address private immutable whitelistedCaller; - IRelayOrderReactor private immutable reactor; + IReactor private immutable reactor; modifier onlyWhitelistedCaller() { if (msg.sender != whitelistedCaller) { @@ -26,7 +26,7 @@ contract PermitExecutor is Owned { _; } - constructor(address _whitelistedCaller, IRelayOrderReactor _reactor, address _owner) Owned(_owner) { + constructor(address _whitelistedCaller, IReactor _reactor, address _owner) Owned(_owner) { whitelistedCaller = _whitelistedCaller; reactor = _reactor; } From 343c2948defd4ad632146bff2aabc23a8d66fe04 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 6 Dec 2023 16:46:07 -0500 Subject: [PATCH 19/33] Add comment --- src/reactors/RelayOrderReactor.sol | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index 04b4d321..da8e2842 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -44,7 +44,13 @@ contract RelayOrderReactor is IReactor, ReactorEvents, ReactorErrors, Reentrancy _fill(resolvedOrders); } - function executeWithCallback(SignedOrder calldata order, bytes calldata callbackData) external payable nonReentrant { + /// @notice callbacks allow fillers to perform additional actions after the order is executed + /// example, to transfer in tokens to fill orders where users are owed additional amounts + function executeWithCallback(SignedOrder calldata order, bytes calldata callbackData) + external + payable + nonReentrant + { ResolvedRelayOrder[] memory resolvedOrders = new ResolvedRelayOrder[](1); resolvedOrders[0] = resolve(order); @@ -69,10 +75,13 @@ contract RelayOrderReactor is IReactor, ReactorEvents, ReactorErrors, Reentrancy _fill(resolvedOrders); } - function executeBatchWithCallback( - SignedOrder[] calldata orders, - bytes calldata callbackData - ) external payable nonReentrant { + /// @notice callbacks allow fillers to perform additional actions after the order is executed + /// example, to transfer in tokens to fill orders where users are owed additional amounts + function executeBatchWithCallback(SignedOrder[] calldata orders, bytes calldata callbackData) + external + payable + nonReentrant + { uint256 ordersLength = orders.length; ResolvedRelayOrder[] memory resolvedOrders = new ResolvedRelayOrder[](ordersLength); From a8815e6ee75a066b7b68d30fde0ca97c0a070868 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Wed, 6 Dec 2023 16:50:36 -0500 Subject: [PATCH 20/33] natspec --- src/interfaces/IRelayOrderReactorCallback.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/interfaces/IRelayOrderReactorCallback.sol b/src/interfaces/IRelayOrderReactorCallback.sol index 7b83e921..6a8ea455 100644 --- a/src/interfaces/IRelayOrderReactorCallback.sol +++ b/src/interfaces/IRelayOrderReactorCallback.sol @@ -5,9 +5,8 @@ import {ResolvedRelayOrder} from "../base/ReactorStructs.sol"; /// @notice Callback for executing orders through a reactor. interface IRelayOrderReactorCallback { - /// @notice Called by the reactor during the execution of an order + /// @notice Called by the reactor after actions are exected in an order /// @param resolvedOrders Has inputs and outputs /// @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(ResolvedRelayOrder[] memory resolvedOrders, bytes memory callbackData) external; } From c604a6676d9412f13a4f89c81828d45f7070ef89 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Mon, 11 Dec 2023 12:02:13 -0500 Subject: [PATCH 21/33] Add basic unit test framework --- .forge-snapshots/ExecuteSingle.snap | 1 + src/reactors/RelayOrderReactor.sol | 16 ++-- .../reactors/RelayOrderReactorTest.sol | 73 +++++++++++++++++-- test/foundry-tests/util/DeployPermit2.sol | 16 ++++ test/foundry-tests/util/mock/MockExecutor.sol | 21 +++--- 5 files changed, 106 insertions(+), 21 deletions(-) create mode 100644 .forge-snapshots/ExecuteSingle.snap create mode 100644 test/foundry-tests/util/DeployPermit2.sol diff --git a/.forge-snapshots/ExecuteSingle.snap b/.forge-snapshots/ExecuteSingle.snap new file mode 100644 index 00000000..da14ac66 --- /dev/null +++ b/.forge-snapshots/ExecuteSingle.snap @@ -0,0 +1 @@ +107641 \ No newline at end of file diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index 9604d83a..b9117d94 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -104,13 +104,15 @@ contract RelayOrderReactor is IReactor, ReactorEvents, ReactorErrors, Reentrancy ResolvedRelayOrder memory order = orders[i]; uint256 actionsLength = order.actions.length; for (uint256 j = 0; j < actionsLength;) { - (address target, uint256 value, bytes memory data) = - abi.decode(order.actions[j], (address, uint256, bytes)); - (bool success, bytes memory result) = target.call{value: value}(data); - if (!success) { - // bubble up all errors, including custom errors which are encoded like functions - assembly { - revert(add(result, 0x20), mload(result)) + if (order.actions[j].length != 0) { + (address target, uint256 value, bytes memory data) = + abi.decode(order.actions[j], (address, uint256, bytes)); + (bool success, bytes memory result) = target.call{value: value}(data); + if (!success) { + // bubble up all errors, including custom errors which are encoded like functions + assembly { + revert(add(result, 0x20), mload(result)) + } } } unchecked { diff --git a/test/foundry-tests/reactors/RelayOrderReactorTest.sol b/test/foundry-tests/reactors/RelayOrderReactorTest.sol index 9f8fbeed..14ae4303 100644 --- a/test/foundry-tests/reactors/RelayOrderReactorTest.sol +++ b/test/foundry-tests/reactors/RelayOrderReactorTest.sol @@ -1,34 +1,97 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; import {Test} from "forge-std/Test.sol"; import {OrderInfoBuilder} from "UniswapX/test/util/OrderInfoBuilder.sol"; import {OrderInfo, SignedOrder} from "UniswapX/src/base/ReactorStructs.sol"; +import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; import {ArrayBuilder} from "UniswapX/test/util/ArrayBuilder.sol"; +import {DeployPermit2} from "UniswapX/test/util/DeployPermit2.sol"; import {MockERC20} from "UniswapX/test/util/mock/MockERC20.sol"; import {CurrencyLibrary} from "UniswapX/src/lib/CurrencyLibrary.sol"; import {InputTokenWithRecipient, ResolvedRelayOrder} from "../../../src/base/ReactorStructs.sol"; import {RelayOrderLib, RelayOrder} from "../../../src/lib/RelayOrderLib.sol"; import {RelayOrderReactor} from "../../../src/reactors/RelayOrderReactor.sol"; import {PermitSignature} from "../util/PermitSignature.sol"; +import {MockFillContract} from "../util/mock/MockExecutor.sol"; -contract RelayOrderReactorTest is Test, PermitSignature { +contract RelayOrderReactorTest is GasSnapshot, Test, PermitSignature, DeployPermit2 { using OrderInfoBuilder for OrderInfo; using RelayOrderLib for RelayOrder; MockERC20 tokenIn; MockERC20 tokenOut; MockFillContract fillContract; - MockValidationContract additionalValidationContract; IPermit2 permit2; RelayOrderReactor reactor; uint256 swapperPrivateKey; address swapper; - function setUp() { + function setUp() public { tokenIn = new MockERC20("Input", "IN", 18); tokenOut = new MockERC20("Output", "OUT", 18); - + swapperPrivateKey = 0x12341234; + swapper = vm.addr(swapperPrivateKey); + permit2 = IPermit2(deployPermit2()); + reactor = new RelayOrderReactor(permit2); + + fillContract = new MockFillContract(address(reactor)); + } + + /// @notice Create and return a basic single Relay order along with its signature, orderHash, and orderInfo + function createAndSignOrder(ResolvedRelayOrder memory resolvedOrder) + public + view + returns (SignedOrder memory signedOrder, bytes32 orderHash) + { + RelayOrder memory order = RelayOrder({ + info: resolvedOrder.info, + decayStartTime: block.timestamp, + decayEndTime: resolvedOrder.info.deadline, + actions: resolvedOrder.actions, + inputs: resolvedOrder.inputs + }); + orderHash = order.hash(); + return (SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)), orderHash); + } + + /// @dev Test of a simple execute + function testBaseExecute() public { + uint256 inputAmount = 1 ether; + uint256 deadline = block.timestamp + 1000; + + tokenIn.mint(address(swapper), uint256(inputAmount) * 100); + tokenIn.forceApprove(swapper, address(permit2), inputAmount); + + InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](1); + inputTokens[0] = InputTokenWithRecipient({ + token: tokenIn, + amount: int256(inputAmount), + maxAmount: int256(inputAmount), + // sending to filler + recipient: address(0) + }); + + bytes[] memory actions = new bytes[](1); + actions[0] = ""; + + ResolvedRelayOrder memory order = ResolvedRelayOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline), + inputs: inputTokens, + actions: actions, + sig: hex"00", + hash: bytes32(0) + }); + + (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); + + // vm.expectEmit(true, true, true, true, address(reactor)); + // emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); + // execute order + snapStart("ExecuteSingle"); + fillContract.execute(signedOrder); + snapEnd(); } -} \ No newline at end of file +} diff --git a/test/foundry-tests/util/DeployPermit2.sol b/test/foundry-tests/util/DeployPermit2.sol new file mode 100644 index 00000000..4d4aaff5 --- /dev/null +++ b/test/foundry-tests/util/DeployPermit2.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Script} from "forge-std/Script.sol"; + +contract DeployPermit2 is Script { + address constant PERMIT2_ADDRESS = 0x000000000022D473030F116dDEE9F6B43aC78BA3; + + function deployPermit2() internal returns (address) { + bytes memory bytecode = + hex"6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f000000000000000000000000000000000000000000000000000000000000000003611b69577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a"; + + vm.etch(PERMIT2_ADDRESS, bytecode); + return PERMIT2_ADDRESS; + } +} diff --git a/test/foundry-tests/util/mock/MockExecutor.sol b/test/foundry-tests/util/mock/MockExecutor.sol index 28398c5a..b5f0d91b 100644 --- a/test/foundry-tests/util/mock/MockExecutor.sol +++ b/test/foundry-tests/util/mock/MockExecutor.sol @@ -5,7 +5,8 @@ import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {CurrencyLibrary} from "UniswapX/src/lib/CurrencyLibrary.sol"; import {IReactor} from "UniswapX/src/interfaces/IReactor.sol"; import {IReactorCallback} from "UniswapX/src/interfaces/IReactorCallback.sol"; -import {ResolvedOrder, OutputToken, SignedOrder} from "UniswapX/src/base/ReactorStructs.sol"; +import {OutputToken, SignedOrder} from "UniswapX/src/base/ReactorStructs.sol"; +import {ResolvedRelayOrder, InputTokenWithRecipient} from "../../../../src/base/ReactorStructs.sol"; contract MockFillContract { using CurrencyLibrary for address; @@ -26,15 +27,17 @@ contract MockFillContract { reactor.executeBatchWithCallback(orders, hex""); } - /// @notice assume that we already have all output tokens - function reactorCallback(ResolvedOrder[] memory resolvedOrders, bytes memory) external { + function reactorCallback(ResolvedRelayOrder[] 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]; - if (output.token.isNative()) { - CurrencyLibrary.transferNative(address(reactor), output.amount); - } else { - ERC20(output.token).approve(address(reactor), type(uint256).max); + for (uint256 j = 0; j < resolvedOrders[i].inputs.length; j++) { + InputTokenWithRecipient memory input = resolvedOrders[i].inputs[j]; + // only need to transfer to reactor if negative (swapper is owed) + if (input.amount < 0) { + if (address(input.token).isNative()) { + CurrencyLibrary.transferNative(address(reactor), uint256(-input.amount)); + } else { + input.token.transfer(address(reactor), uint256(-input.amount)); + } } } } From c6154d7b7b70d0f80c9b244bf47afb58de2e78fa Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Mon, 11 Dec 2023 15:32:30 -0500 Subject: [PATCH 22/33] Add unit test for negative fee --- .forge-snapshots/ExecuteSingle.snap | 2 +- .../reactors/RelayOrderReactorTest.sol | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.forge-snapshots/ExecuteSingle.snap b/.forge-snapshots/ExecuteSingle.snap index da14ac66..94f166c6 100644 --- a/.forge-snapshots/ExecuteSingle.snap +++ b/.forge-snapshots/ExecuteSingle.snap @@ -1 +1 @@ -107641 \ No newline at end of file +108435 \ No newline at end of file diff --git a/test/foundry-tests/reactors/RelayOrderReactorTest.sol b/test/foundry-tests/reactors/RelayOrderReactorTest.sol index 14ae4303..d8e04af7 100644 --- a/test/foundry-tests/reactors/RelayOrderReactorTest.sol +++ b/test/foundry-tests/reactors/RelayOrderReactorTest.sol @@ -28,6 +28,9 @@ contract RelayOrderReactorTest is GasSnapshot, Test, PermitSignature, DeployPerm uint256 swapperPrivateKey; address swapper; + event Fill(bytes32 indexed orderHash, address indexed filler, address indexed swapper, uint256 nonce); + event Transfer(address indexed from, address indexed to, uint256 amount); + function setUp() public { tokenIn = new MockERC20("Input", "IN", 18); tokenOut = new MockERC20("Output", "OUT", 18); @@ -63,12 +66,13 @@ contract RelayOrderReactorTest is GasSnapshot, Test, PermitSignature, DeployPerm uint256 deadline = block.timestamp + 1000; tokenIn.mint(address(swapper), uint256(inputAmount) * 100); + tokenIn.mint(address(fillContract), uint256(inputAmount) * 100); tokenIn.forceApprove(swapper, address(permit2), inputAmount); InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](1); inputTokens[0] = InputTokenWithRecipient({ token: tokenIn, - amount: int256(inputAmount), + amount: -int256(inputAmount), maxAmount: int256(inputAmount), // sending to filler recipient: address(0) @@ -87,8 +91,15 @@ contract RelayOrderReactorTest is GasSnapshot, Test, PermitSignature, DeployPerm (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - // vm.expectEmit(true, true, true, true, address(reactor)); - // emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); + // warp to precisely 25% way through the decay + vm.warp(block.timestamp + 250); + + vm.expectEmit(true, true, true, false); + emit Transfer(address(fillContract), address(reactor), 500000000000000000); + vm.expectEmit(true, true, true, false); + emit Transfer(address(reactor), swapper, 500000000000000000); + vm.expectEmit(true, true, true, true, address(reactor)); + emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); // execute order snapStart("ExecuteSingle"); fillContract.execute(signedOrder); From de96ef5a5ce56701bba5d91de3af8154ec3014f2 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Tue, 12 Dec 2023 12:17:24 -0500 Subject: [PATCH 23/33] Remove unused func --- src/lib/ResolvedRelayOrderLib.sol | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/lib/ResolvedRelayOrderLib.sol b/src/lib/ResolvedRelayOrderLib.sol index e095d67a..5458cd94 100644 --- a/src/lib/ResolvedRelayOrderLib.sol +++ b/src/lib/ResolvedRelayOrderLib.sol @@ -24,15 +24,4 @@ library ResolvedRelayOrderLib { revert DeadlinePassed(); } } - - function hasNegativeInputs(ResolvedRelayOrder memory resolvedOrder) internal pure returns (bool) { - unchecked { - for (uint256 i = 0; i < resolvedOrder.inputs.length; i++) { - if (resolvedOrder.inputs[i].amount < 0) { - return true; - } - } - return false; - } - } } From 094f1bbf5e63e0361c5d2ed829dcb9ed8e671e8f Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Tue, 12 Dec 2023 12:19:06 -0500 Subject: [PATCH 24/33] nit: comment --- src/reactors/RelayOrderReactor.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactors/RelayOrderReactor.sol b/src/reactors/RelayOrderReactor.sol index 9f54ef49..4c50d2f1 100644 --- a/src/reactors/RelayOrderReactor.sol +++ b/src/reactors/RelayOrderReactor.sol @@ -140,7 +140,7 @@ contract RelayOrderReactor is ReactorEvents, ReactorErrors, ReentrancyGuard, IRe } /// @notice emits a Fill event for each order - /// @notice all output token checks must be done in the encoded actions within the order + /// @notice any output token checks must be encoded in the order specified actions /// @param orders The orders that have been filled function _fill(ResolvedRelayOrder[] memory orders) internal { uint256 ordersLength = orders.length; From 874b4fa1e4473fe71a412195fbcd55be65fd5505 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Fri, 15 Dec 2023 11:45:20 -0500 Subject: [PATCH 25/33] Add unit tests but one broken --- .forge-snapshots/ExecuteSingleWithRebate.snap | 1 + ...derReactorIntegrationTest-testExecute.snap | 2 +- ...ionTest-testExecuteWithNativeAsOutput.snap | 2 +- ...rIntegrationTest-testPermitAndExecute.snap | 2 +- src/lib/RelayOrderLib.sol | 2 +- .../reactors/RelayOrderReactorTest.sol | 60 +++++++++++++++++-- test/foundry-tests/util/PermitSignature.sol | 19 +----- 7 files changed, 62 insertions(+), 26 deletions(-) create mode 100644 .forge-snapshots/ExecuteSingleWithRebate.snap diff --git a/.forge-snapshots/ExecuteSingleWithRebate.snap b/.forge-snapshots/ExecuteSingleWithRebate.snap new file mode 100644 index 00000000..4b7dbc68 --- /dev/null +++ b/.forge-snapshots/ExecuteSingleWithRebate.snap @@ -0,0 +1 @@ +108487 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap index 3dac74b6..c2a582b8 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap @@ -1 +1 @@ -256904 +257089 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap index bef3c821..64b97512 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap @@ -1 +1 @@ -283270 +283455 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap index 7c05e821..37af970d 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap @@ -1 +1 @@ -312974 +313159 \ No newline at end of file diff --git a/src/lib/RelayOrderLib.sol b/src/lib/RelayOrderLib.sol index efbd3fc4..114fa3ed 100644 --- a/src/lib/RelayOrderLib.sol +++ b/src/lib/RelayOrderLib.sol @@ -47,7 +47,7 @@ library RelayOrderLib { /// @notice returns the hash of an input token struct function hash(InputTokenWithRecipient memory input) private pure returns (bytes32) { - return keccak256(abi.encode(INPUT_TOKEN_TYPE_HASH, input.token, input.amount, input.maxAmount)); + return keccak256(abi.encode(INPUT_TOKEN_TYPE_HASH, input.token, input.amount, input.maxAmount, input.recipient)); } /// @notice returns the hash of an input token struct diff --git a/test/foundry-tests/reactors/RelayOrderReactorTest.sol b/test/foundry-tests/reactors/RelayOrderReactorTest.sol index d8e04af7..a0cff7e0 100644 --- a/test/foundry-tests/reactors/RelayOrderReactorTest.sol +++ b/test/foundry-tests/reactors/RelayOrderReactorTest.sol @@ -21,7 +21,6 @@ contract RelayOrderReactorTest is GasSnapshot, Test, PermitSignature, DeployPerm using RelayOrderLib for RelayOrder; MockERC20 tokenIn; - MockERC20 tokenOut; MockFillContract fillContract; IPermit2 permit2; RelayOrderReactor reactor; @@ -33,7 +32,6 @@ contract RelayOrderReactorTest is GasSnapshot, Test, PermitSignature, DeployPerm function setUp() public { tokenIn = new MockERC20("Input", "IN", 18); - tokenOut = new MockERC20("Output", "OUT", 18); swapperPrivateKey = 0x12341234; swapper = vm.addr(swapperPrivateKey); @@ -41,6 +39,10 @@ contract RelayOrderReactorTest is GasSnapshot, Test, PermitSignature, DeployPerm reactor = new RelayOrderReactor(permit2); fillContract = new MockFillContract(address(reactor)); + + // swapper approves permit2 to transfer tokens + tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); + assertEq(tokenIn.allowance(swapper, address(permit2)), type(uint256).max); } /// @notice Create and return a basic single Relay order along with its signature, orderHash, and orderInfo @@ -61,7 +63,57 @@ contract RelayOrderReactorTest is GasSnapshot, Test, PermitSignature, DeployPerm } /// @dev Test of a simple execute - function testBaseExecute() public { + /// @dev this order has no actions and its inputs decay from 0 ether to 1 ether + function testExecuteSingle() public { + uint256 inputAmount = 1 ether; + uint256 deadline = block.timestamp + 1000; + + tokenIn.mint(address(swapper), uint256(inputAmount) * 100); + tokenIn.mint(address(fillContract), uint256(inputAmount) * 100); + tokenIn.forceApprove(swapper, address(permit2), inputAmount); + + InputTokenWithRecipient[] memory inputTokens = new InputTokenWithRecipient[](1); + inputTokens[0] = InputTokenWithRecipient({ + token: tokenIn, + amount: 0, + maxAmount: int256(inputAmount), + // sending to filler + recipient: address(0) + }); + + bytes[] memory actions = new bytes[](1); + actions[0] = ""; + + RelayOrder memory order = RelayOrder({ + info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline), + decayStartTime: block.timestamp, + decayEndTime: deadline, + actions: actions, + inputs: inputTokens + }); + bytes32 orderHash = order.hash(); + + SignedOrder memory signedOrder = + SignedOrder(abi.encode(order), signOrder(swapperPrivateKey, address(permit2), order)); + + uint256 fillContractTokenInBefore = tokenIn.balanceOf(address(fillContract)); + + // warp to precisely 25% way through the decay + vm.warp(block.timestamp + 250); + + vm.expectEmit(true, true, true, true, address(reactor)); + emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); + // execute order + snapStart("ExecuteSingleWithRebate"); + fillContract.execute(signedOrder); + snapEnd(); + + assertEq(tokenIn.balanceOf(address(fillContract)), fillContractTokenInBefore + 250000000000000000); + } + + /// @dev Test of a simple execute + /// @dev this order has no actions and its inputs decay from -1 ether to 1 ether + function testExecuteSingleWithRebate() public { uint256 inputAmount = 1 ether; uint256 deadline = block.timestamp + 1000; @@ -101,7 +153,7 @@ contract RelayOrderReactorTest is GasSnapshot, Test, PermitSignature, DeployPerm vm.expectEmit(true, true, true, true, address(reactor)); emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); // execute order - snapStart("ExecuteSingle"); + snapStart("ExecuteSingleWithRebate"); fillContract.execute(signedOrder); snapEnd(); } diff --git a/test/foundry-tests/util/PermitSignature.sol b/test/foundry-tests/util/PermitSignature.sol index 885a036d..5847f9ad 100644 --- a/test/foundry-tests/util/PermitSignature.sol +++ b/test/foundry-tests/util/PermitSignature.sol @@ -80,24 +80,7 @@ contract PermitSignature is Test { (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); sig = bytes.concat(r, s, bytes1(v)); } - - function signOrder( - uint256 privateKey, - address permit2, - OrderInfo memory info, - address inputToken, - uint256 inputAmount, - bytes32 typeHash, - bytes32 orderHash - ) internal view returns (bytes memory sig) { - ISignatureTransfer.PermitTransferFrom memory permit = ISignatureTransfer.PermitTransferFrom({ - permitted: ISignatureTransfer.TokenPermissions({token: inputToken, amount: inputAmount}), - nonce: info.nonce, - deadline: info.deadline - }); - return getPermitSignature(privateKey, permit2, permit, address(info.reactor), typeHash, orderHash); - } - + function signOrder( uint256 privateKey, address permit2, From d7633407e4ed5c4b42b96bef9536c734be7f55be Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Fri, 15 Dec 2023 11:46:32 -0500 Subject: [PATCH 26/33] update 712 hashing --- .../RelayOrderReactorIntegrationTest-testExecute.snap | 2 +- ...rReactorIntegrationTest-testExecuteWithNativeAsOutput.snap | 2 +- ...RelayOrderReactorIntegrationTest-testPermitAndExecute.snap | 2 +- src/lib/RelayOrderLib.sol | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap index 1155bb98..e09a28cc 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap @@ -1 +1 @@ -256912 \ No newline at end of file +257017 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap index cb34a42c..f5a5bc01 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap @@ -1 +1 @@ -283278 \ No newline at end of file +283383 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap index a3e37c42..d7454514 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap @@ -1 +1 @@ -312982 \ No newline at end of file +313087 \ No newline at end of file diff --git a/src/lib/RelayOrderLib.sol b/src/lib/RelayOrderLib.sol index efbd3fc4..0f7135c2 100644 --- a/src/lib/RelayOrderLib.sol +++ b/src/lib/RelayOrderLib.sol @@ -24,7 +24,7 @@ library RelayOrderLib { using OrderInfoLib for OrderInfo; bytes private constant INPUT_TOKEN_TYPE = - "InputTokenWithRecipient(address token,uint256 amount,uint256 maxAmount,address recipient)"; + "InputTokenWithRecipient(address token,int256 amount,int256 maxAmount,address recipient)"; bytes32 private constant INPUT_TOKEN_TYPE_HASH = keccak256(INPUT_TOKEN_TYPE); @@ -47,7 +47,7 @@ library RelayOrderLib { /// @notice returns the hash of an input token struct function hash(InputTokenWithRecipient memory input) private pure returns (bytes32) { - return keccak256(abi.encode(INPUT_TOKEN_TYPE_HASH, input.token, input.amount, input.maxAmount)); + return keccak256(abi.encode(INPUT_TOKEN_TYPE_HASH, input.token, input.amount, input.maxAmount, input.recipient)); } /// @notice returns the hash of an input token struct From 47a42fe53730f4e228d1c6397eb728aee35de5b0 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Fri, 15 Dec 2023 11:59:31 -0500 Subject: [PATCH 27/33] Fix 712 ordering --- .../RelayOrderReactorIntegrationTest-testExecute.snap | 2 +- ...actorIntegrationTest-testExecuteWithNativeAsOutput.snap | 2 +- ...ayOrderReactorIntegrationTest-testPermitAndExecute.snap | 2 +- src/lib/RelayOrderLib.sol | 7 +++---- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap index e09a28cc..bf498460 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap @@ -1 +1 @@ -257017 \ No newline at end of file +256830 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap index f5a5bc01..1f633680 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap @@ -1 +1 @@ -283383 \ No newline at end of file +283200 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap index d7454514..2a825293 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap @@ -1 +1 @@ -313087 \ No newline at end of file +312901 \ No newline at end of file diff --git a/src/lib/RelayOrderLib.sol b/src/lib/RelayOrderLib.sol index 0f7135c2..f10068bb 100644 --- a/src/lib/RelayOrderLib.sol +++ b/src/lib/RelayOrderLib.sol @@ -34,10 +34,9 @@ library RelayOrderLib { "uint256 decayStartTime,", "uint256 decayEndTime,", "bytes[] actions,", - "InputTokenWithRecipient[] inputs,", - "OutputToken[] outputs)", - OrderInfoLib.ORDER_INFO_TYPE, - INPUT_TOKEN_TYPE + "InputTokenWithRecipient[] inputs)", + INPUT_TOKEN_TYPE, + OrderInfoLib.ORDER_INFO_TYPE ); bytes32 internal constant ORDER_TYPE_HASH = keccak256(ORDER_TYPE); From 3cbb671bc9b345e32c927876daf755d178763092 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Fri, 15 Dec 2023 13:07:01 -0500 Subject: [PATCH 28/33] Fix 712 ordering --- ...elayOrderReactorIntegrationTest-testExecute.snap | 2 +- ...tegrationTest-testExecuteWithNativeAsOutput.snap | 2 +- ...ReactorIntegrationTest-testPermitAndExecute.snap | 2 +- src/lib/RelayOrderLib.sol | 13 ++++++++----- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap index bf498460..0ce16029 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecute.snap @@ -1 +1 @@ -256830 \ No newline at end of file +256168 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap index 1f633680..c590e396 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testExecuteWithNativeAsOutput.snap @@ -1 +1 @@ -283200 \ No newline at end of file +282546 \ No newline at end of file diff --git a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap index 2a825293..cc804663 100644 --- a/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap +++ b/.forge-snapshots/RelayOrderReactorIntegrationTest-testPermitAndExecute.snap @@ -1 +1 @@ -312901 \ No newline at end of file +312232 \ No newline at end of file diff --git a/src/lib/RelayOrderLib.sol b/src/lib/RelayOrderLib.sol index f10068bb..df1b035d 100644 --- a/src/lib/RelayOrderLib.sol +++ b/src/lib/RelayOrderLib.sol @@ -28,21 +28,24 @@ library RelayOrderLib { bytes32 private constant INPUT_TOKEN_TYPE_HASH = keccak256(INPUT_TOKEN_TYPE); - bytes internal constant ORDER_TYPE = abi.encodePacked( + bytes internal constant RELAY_ORDER_TYPE = abi.encodePacked( "RelayOrder(", "OrderInfo info,", "uint256 decayStartTime,", "uint256 decayEndTime,", "bytes[] actions,", - "InputTokenWithRecipient[] inputs)", - INPUT_TOKEN_TYPE, - OrderInfoLib.ORDER_INFO_TYPE + "InputTokenWithRecipient[] inputs)" ); + + bytes internal constant ORDER_TYPE = abi.encodePacked( + RELAY_ORDER_TYPE, INPUT_TOKEN_TYPE, OrderInfoLib.ORDER_INFO_TYPE + ); + bytes32 internal constant ORDER_TYPE_HASH = keccak256(ORDER_TYPE); string private constant TOKEN_PERMISSIONS_TYPE = "TokenPermissions(address token,uint256 amount)"; string internal constant PERMIT2_ORDER_TYPE = - string(abi.encodePacked("RelayOrder witness)", ORDER_TYPE, TOKEN_PERMISSIONS_TYPE)); + string(abi.encodePacked("RelayOrder witness)", RELAY_ORDER_TYPE, OrderInfoLib.ORDER_INFO_TYPE, TOKEN_PERMISSIONS_TYPE)); /// @notice returns the hash of an input token struct function hash(InputTokenWithRecipient memory input) private pure returns (bytes32) { From a69858de09557696f9d93d71fac2a71113448d1b Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Mon, 18 Dec 2023 10:41:12 -0500 Subject: [PATCH 29/33] gas snap --- .forge-snapshots/ExecuteSingleWithRebate.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forge-snapshots/ExecuteSingleWithRebate.snap b/.forge-snapshots/ExecuteSingleWithRebate.snap index 4b7dbc68..a7fb7e64 100644 --- a/.forge-snapshots/ExecuteSingleWithRebate.snap +++ b/.forge-snapshots/ExecuteSingleWithRebate.snap @@ -1 +1 @@ -108487 \ No newline at end of file +107639 \ No newline at end of file From 2303bfc821b0452ecd83306bc9e0b700dd7c7522 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Mon, 18 Dec 2023 10:41:37 -0500 Subject: [PATCH 30/33] forge fmt --- src/lib/RelayOrderLib.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/RelayOrderLib.sol b/src/lib/RelayOrderLib.sol index df1b035d..6536865d 100644 --- a/src/lib/RelayOrderLib.sol +++ b/src/lib/RelayOrderLib.sol @@ -37,15 +37,15 @@ library RelayOrderLib { "InputTokenWithRecipient[] inputs)" ); - bytes internal constant ORDER_TYPE = abi.encodePacked( - RELAY_ORDER_TYPE, INPUT_TOKEN_TYPE, OrderInfoLib.ORDER_INFO_TYPE - ); + bytes internal constant ORDER_TYPE = + abi.encodePacked(RELAY_ORDER_TYPE, INPUT_TOKEN_TYPE, OrderInfoLib.ORDER_INFO_TYPE); bytes32 internal constant ORDER_TYPE_HASH = keccak256(ORDER_TYPE); string private constant TOKEN_PERMISSIONS_TYPE = "TokenPermissions(address token,uint256 amount)"; - string internal constant PERMIT2_ORDER_TYPE = - string(abi.encodePacked("RelayOrder witness)", RELAY_ORDER_TYPE, OrderInfoLib.ORDER_INFO_TYPE, TOKEN_PERMISSIONS_TYPE)); + string internal constant PERMIT2_ORDER_TYPE = string( + abi.encodePacked("RelayOrder witness)", RELAY_ORDER_TYPE, OrderInfoLib.ORDER_INFO_TYPE, TOKEN_PERMISSIONS_TYPE) + ); /// @notice returns the hash of an input token struct function hash(InputTokenWithRecipient memory input) private pure returns (bytes32) { From a8c95f2318d497aa924855aca194b30c241b2ac3 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Mon, 18 Dec 2023 14:06:21 -0500 Subject: [PATCH 31/33] vm.chainId(1) --- test/foundry-tests/reactors/RelayOrderReactorTest.sol | 8 +++++++- test/foundry-tests/util/PermitSignature.sol | 3 --- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/test/foundry-tests/reactors/RelayOrderReactorTest.sol b/test/foundry-tests/reactors/RelayOrderReactorTest.sol index a0cff7e0..a431d786 100644 --- a/test/foundry-tests/reactors/RelayOrderReactorTest.sol +++ b/test/foundry-tests/reactors/RelayOrderReactorTest.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +import {console2} from "forge-std/console2.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; import {Test} from "forge-std/Test.sol"; import {OrderInfoBuilder} from "UniswapX/test/util/OrderInfoBuilder.sol"; @@ -31,11 +32,16 @@ contract RelayOrderReactorTest is GasSnapshot, Test, PermitSignature, DeployPerm event Transfer(address indexed from, address indexed to, uint256 amount); function setUp() public { + vm.chainId(1); + tokenIn = new MockERC20("Input", "IN", 18); swapperPrivateKey = 0x12341234; swapper = vm.addr(swapperPrivateKey); permit2 = IPermit2(deployPermit2()); + console2.logBytes32(permit2.DOMAIN_SEPARATOR()); + console2.log(block.chainid); + reactor = new RelayOrderReactor(permit2); fillContract = new MockFillContract(address(reactor)); @@ -82,7 +88,7 @@ contract RelayOrderReactorTest is GasSnapshot, Test, PermitSignature, DeployPerm }); bytes[] memory actions = new bytes[](1); - actions[0] = ""; + actions[0] = hex""; RelayOrder memory order = RelayOrder({ info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline), diff --git a/test/foundry-tests/util/PermitSignature.sol b/test/foundry-tests/util/PermitSignature.sol index 5847f9ad..9f3c4b67 100644 --- a/test/foundry-tests/util/PermitSignature.sol +++ b/test/foundry-tests/util/PermitSignature.sol @@ -17,9 +17,6 @@ contract PermitSignature is Test { bytes32 internal constant TOKEN_PERMISSIONS_TYPEHASH = keccak256("TokenPermissions(address token,uint256 amount)"); - string constant TYPEHASH_STUB = - "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,"; - string public constant _PERMIT_BATCH_WITNESS_TRANSFER_TYPEHASH_STUB = "PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,"; From 6eca9c41229be127fa38eb62f85d0ab435dbd569 Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Mon, 18 Dec 2023 14:53:33 -0500 Subject: [PATCH 32/33] permit maxAmount in tests --- test/foundry-tests/util/PermitSignature.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/foundry-tests/util/PermitSignature.sol b/test/foundry-tests/util/PermitSignature.sol index 9f3c4b67..8f0e0125 100644 --- a/test/foundry-tests/util/PermitSignature.sol +++ b/test/foundry-tests/util/PermitSignature.sol @@ -92,7 +92,7 @@ contract PermitSignature is Test { if (inputs[i].amount > 0) { permissions[i] = ISignatureTransfer.TokenPermissions({ token: address(inputs[i].token), - amount: uint256(inputs[i].amount) + amount: uint256(inputs[i].maxAmount) }); } } From 00aaa0994e8cb5d0a2816c76ffe74c6aea39a61d Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Tue, 19 Dec 2023 14:29:33 -0500 Subject: [PATCH 33/33] warping timestamp is causing ecrecover to fail? --- test/foundry-tests/reactors/RelayOrderReactorTest.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/foundry-tests/reactors/RelayOrderReactorTest.sol b/test/foundry-tests/reactors/RelayOrderReactorTest.sol index a431d786..0962117b 100644 --- a/test/foundry-tests/reactors/RelayOrderReactorTest.sol +++ b/test/foundry-tests/reactors/RelayOrderReactorTest.sol @@ -105,7 +105,7 @@ contract RelayOrderReactorTest is GasSnapshot, Test, PermitSignature, DeployPerm uint256 fillContractTokenInBefore = tokenIn.balanceOf(address(fillContract)); // warp to precisely 25% way through the decay - vm.warp(block.timestamp + 250); + // vm.warp(block.timestamp + 250); vm.expectEmit(true, true, true, true, address(reactor)); emit Fill(orderHash, address(fillContract), swapper, order.info.nonce);