diff --git a/foundry/src/uniswap_x/UniswapXFiller.sol b/foundry/src/uniswap_x/UniswapXFiller.sol index 1156c31..bc5a3f9 100644 --- a/foundry/src/uniswap_x/UniswapXFiller.sol +++ b/foundry/src/uniswap_x/UniswapXFiller.sol @@ -9,6 +9,7 @@ import { } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/utils/Address.sol"; +import {TychoRouter} from "../TychoRouter.sol"; error UniswapXFiller__AddressZero(); error UniswapXFiller__BatchExecutionNotSupported(); @@ -58,12 +59,34 @@ contract UniswapXFiller is AccessControl, IReactorCallback { ResolvedOrder memory order = resolvedOrders[0]; + ( + bool tokenInApprovalNeeded, + bool tokenOutApprovalNeeded, + bytes memory tychoCalldata + ) = abi.decode(callbackData, (bool, bool, bytes)); + + // The TychoRouter will take the input tokens from the filler + if (tokenInApprovalNeeded) { + // TODO only if ERC20 + IERC20(order.input.token).forceApprove( + tychoRouter, order.input.maxAmount + ); + } else { + // The filler will manually transfer into the TychoRouter + // Note: We are using the balance of our filler instead of the order + // input amount to avoid having to do a decay calculation in the filler. + IERC20 inputToken = IERC20(order.input.token); + inputToken.transfer( + tychoRouter, inputToken.balanceOf(address(this)) + ); + } + // TODO properly handle native in and out tokens uint256 ethValue = 0; // slither-disable-next-line low-level-calls (bool success, bytes memory result) = - tychoRouter.call{value: ethValue}(callbackData); + tychoRouter.call{value: ethValue}(tychoCalldata); if (!success) { revert( @@ -75,10 +98,13 @@ contract UniswapXFiller is AccessControl, IReactorCallback { ); } - // Multiple outputs are possible when taking fees - but token itself should - // not change. - IERC20 token = IERC20(order.outputs[0].token); - token.forceApprove(address(reactor), type(uint256).max); + if (tokenOutApprovalNeeded) { + // Multiple outputs are possible when taking fees - but token itself should + // not change. + // TODO only if ERC20 + IERC20 token = IERC20(order.outputs[0].token); + token.forceApprove(address(reactor), type(uint256).max); + } } /** diff --git a/foundry/test/uniswap_x/UniswapXFiller.t.sol b/foundry/test/uniswap_x/UniswapXFiller.t.sol index 8102e4f..49f9e90 100644 --- a/foundry/test/uniswap_x/UniswapXFiller.t.sol +++ b/foundry/test/uniswap_x/UniswapXFiller.t.sol @@ -55,7 +55,7 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup { bytes memory swap = encodeSingleSwap(address(usv2Executor), protocolData); - bytes memory callbackData = abi.encodeWithSelector( + bytes memory tychoRouterData = abi.encodeWithSelector( tychoRouter.singleSwap.selector, amountIn, WETH_ADDR, @@ -68,6 +68,8 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup { swap ); + bytes memory callbackData = abi.encode(true, true, tychoRouterData); + deal(WETH_ADDR, address(filler), amountIn); vm.startPrank(address(filler)); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn); @@ -76,11 +78,11 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup { OutputToken[] memory outputs = new OutputToken[](1); outputs[0] = OutputToken({ token: address(DAI_ADDR), - // Irrelevant fields - we only need the token address for approval amount: 1847751195973566072891, recipient: BOB }); - // All irrelevant fields for this test - we only need the output token address + // Mostly irrelevant fields for this test - we only need token input and outputs + // info for the sake of testing. orders[0] = ResolvedOrder({ info: OrderInfo({ reactor: address(0), @@ -90,7 +92,12 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup { additionalValidationContract: address(0), additionalValidationData: "" }), - input: InputToken({token: address(WETH_ADDR), amount: 0, maxAmount: 0}), + input: InputToken({ + token: address(WETH_ADDR), + amount: amountIn, + // We need the proper maxAmount for our approval to work + maxAmount: amountIn + }), outputs: outputs, sig: "", hash: "" @@ -133,7 +140,7 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup { bytes memory swap = encodeSingleSwap(address(usv2Executor), protocolData); - bytes memory callbackData = abi.encodeWithSelector( + bytes memory tychoRouterData = abi.encodeWithSelector( tychoRouter.singleSwap.selector, amountIn, WBTC_ADDR, @@ -146,6 +153,12 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup { swap ); + bytes memory callbackData = abi.encode( + true, // tokenIn approval needed + true, // tokenOut approval needed + tychoRouterData + ); + vm.startPrank(address(filler)); IERC20(WBTC_ADDR).approve(tychoRouterAddr, amountIn); vm.stopPrank();