diff --git a/foundry/interfaces/IExecutor.sol b/foundry/interfaces/IExecutor.sol index 764623a..9c5a55c 100644 --- a/foundry/interfaces/IExecutor.sol +++ b/foundry/interfaces/IExecutor.sol @@ -22,6 +22,7 @@ interface IExecutor { */ function swap(uint256 givenAmount, bytes calldata data) external + payable returns (uint256 calculatedAmount); } diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index d2badbb..24a1497 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -87,32 +87,60 @@ contract TychoRouter is } /** - * @dev Executes a swap graph supporting internal token amount - * splits, checking that the user gets more than minUserAmount of buyToken. + * @notice Executes a swap operation based on a predefined swap graph, supporting internal token amount splits. + * This function enables multi-step swaps, optional ETH wrapping/unwrapping, and validates the output amount + * against a user-specified minimum. + * + * @dev + * - If `wrapEth` is true, the contract wraps the provided native ETH into WETH and uses it as the sell token. + * - If `unwrapEth` is true, the contract converts the resulting WETH back into native ETH before sending it to the receiver. + * - For ERC20 tokens, Permit2 is used to approve and transfer tokens from the caller to the router. + * - Swaps are executed sequentially using the `_splitSwap` function. + * - A fee is deducted from the output token if `fee > 0`, and the remaining amount is sent to the receiver. + * - Reverts with `TychoRouter__NegativeSlippage` if the output amount is less than `minAmountOut` and `checkMinAmount` is true. + * + * @param amountIn The input token amount to be swapped. + * @param tokenIn The address of the input token. Use `address(0)` for native ETH when `wrapEth` is true. + * @param tokenOut The address of the output token. Use `address(0)` for native ETH when `unwrapEth` is true. + * @param checkMinAmount A boolean indicating whether to enforce the `minAmountOut` check. + * @param minAmountOut The minimum acceptable amount of the output token. Reverts if this condition is not met. + * @param wrapEth If true, treats the input token as native ETH and wraps it into WETH. + * @param unwrapEth If true, unwraps the resulting WETH into native ETH and sends it to the receiver. + * @param nTokens The total number of tokens involved in the swap graph (used to initialize arrays for internal calculations). + * @param receiver The address to receive the output tokens. + * @param permitSingle A Permit2 structure containing token approval details for the input token. Ignored if `wrapEth` is true. + * @param signature A valid signature authorizing the Permit2 approval. Ignored if `wrapEth` is true. + * @param swaps Encoded swap graph data containing details of each swap. + * + * @return amountOut The total amount of the output token received by the receiver, after deducting fees if applicable. */ function swap( uint256 amountIn, + address tokenIn, address tokenOut, - uint256 checkAmountOut, - bool wrapEth, // This means ETH is the sell token - bool unwrapEth, // This means ETH is the buy token + bool checkMinAmount, + uint256 minAmountOut, + bool wrapEth, + bool unwrapEth, uint256 nTokens, address receiver, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature, bytes calldata swaps - ) external whenNotPaused returns (uint256 amountOut) { + ) external payable whenNotPaused returns (uint256 amountOut) { // For native ETH, assume funds already in our router. Else, transfer and handle approval. if (wrapEth) { _wrapETH(amountIn); } else { - permit2.permit(msg.sender, permitSingle, signature); - permit2.transferFrom( - msg.sender, - address(this), - uint160(amountIn), - permitSingle.details.token - ); + if (tokenIn != address(0)) { + permit2.permit(msg.sender, permitSingle, signature); + permit2.transferFrom( + msg.sender, + address(this), + uint160(amountIn), + permitSingle.details.token + ); + } } amountOut = _splitSwap(amountIn, nTokens, swaps); @@ -121,10 +149,13 @@ contract TychoRouter is uint256 feeAmount = (amountOut * fee) / 10000; amountOut -= feeAmount; IERC20(tokenOut).safeTransfer(feeReceiver, feeAmount); + if (unwrapEth == false) { + IERC20(tokenOut).safeTransfer(receiver, amountOut); + } } - if (amountOut < checkAmountOut) { - revert TychoRouter__NegativeSlippage(amountOut, checkAmountOut); + if (checkMinAmount && amountOut < minAmountOut) { + revert TychoRouter__NegativeSlippage(amountOut, minAmountOut); } if (unwrapEth) { @@ -152,15 +183,14 @@ contract TychoRouter is while (swaps_.length > 0) { (swapData, swaps_) = swaps_.next(); - split = swapData.splitPercentage(); tokenInIndex = swapData.tokenInIndex(); tokenOutIndex = swapData.tokenOutIndex(); + split = swapData.splitPercentage(); currentAmountIn = split > 0 ? (amounts[tokenInIndex] * split) / 0xffffff : remainingAmounts[tokenInIndex]; currentAmountOut = _callExecutor(currentAmountIn, swapData.protocolData()); - amounts[tokenOutIndex] += currentAmountOut; remainingAmounts[tokenOutIndex] += currentAmountOut; remainingAmounts[tokenInIndex] -= currentAmountIn; diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index 01a507e..d892122 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -12,6 +12,7 @@ contract UniswapV2Executor is IExecutor { function swap(uint256 givenAmount, bytes calldata data) external + payable returns (uint256 calculatedAmount) { address target; diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index ed1b66f..0feba9d 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -9,6 +9,10 @@ contract Constants is Test { address FUND_RESCUER = makeAddr("fundRescuer"); address FEE_SETTER = makeAddr("feeSetter"); address FEE_RECEIVER = makeAddr("feeReceiver"); + address EXECUTOR_SETTER = makeAddr("executorSetter"); + address ALICE = 0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2; + uint256 ALICE_PK = + 0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234; // Dummy contracts address DUMMY = makeAddr("dummy"); @@ -18,6 +22,14 @@ contract Constants is Test { // Assets address WETH_ADDR = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); address DAI_ADDR = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); + address USDC_ADDR = address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + address WBTC_ADDR = address(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); + + // uniswap v2 + address WETH_DAI_POOL = 0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11; + address DAI_USDC_POOL = 0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5; + address WETH_WBTC_POOL = 0xBb2b8038a1640196FbE3e38816F3e67Cba72D940; + address USDC_WBTC_POOL = 0x004375Dff511095CC5A197A54140a24eFEF3A416; /** * @dev Deploys a dummy contract with non-empty bytecode diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index 5458016..37ab4fd 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -206,11 +206,355 @@ contract TychoRouterTest is TychoRouterTestSetup { uint256 amount = 1 ether; deal(WETH_ADDR, address(tychoRouter), amount); - vm.startPrank(BOB); tychoRouter.unwrapETH(amount); - vm.stopPrank(); assertEq(address(tychoRouter).balance, amount); assertEq(IERC20(WETH_ADDR).balanceOf(address(tychoRouter)), 0); } + + function testSplitSwapSimple() public { + // Trade 1 WETH for DAI with 1 swap on Uniswap V2 + // 1 WETH -> DAI + // (univ2) + uint256 amount_in = 1 ether; + deal(WETH_ADDR, address(tychoRouter), amount_in); + + bytes memory protocolData = + encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false); + + bytes memory swap = + encodeSwap(uint8(0), uint8(1), uint24(0), protocolData); + bytes[] memory swaps = new bytes[](1); + swaps[0] = swap; + + uint256 minAmountOut = 2600 * 1e18; + uint256 amountOut = + tychoRouter.splitSwap(amount_in, 2, pleEncode(swaps)); + + uint256 daiBalance = IERC20(DAI_ADDR).balanceOf(ALICE); + assertEq(daiBalance, 2630432278145144658455); + assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 0); + } + + function testSplitSwapMultipleHops() public { + // Trade 1 WETH for USDC through DAI with 2 swaps on Uniswap V2 + // 1 WETH -> DAI -> USDC + // (univ2) (univ2) + uint256 amount_in = 1 ether; + deal(WETH_ADDR, address(tychoRouter), amount_in); + + bytes[] memory swaps = new bytes[](2); + // WETH -> DAI + swaps[0] = encodeSwap( + uint8(0), + uint8(1), + uint24(0), + encodeUniswapV2Swap( + WETH_ADDR, WETH_DAI_POOL, address(tychoRouter), false + ) + ); + + // DAI -> USDC + swaps[1] = encodeSwap( + uint8(1), + uint8(2), + uint24(0), + encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, ALICE, true) + ); + + uint256 minAmountOut = 2600 * 1e6; + uint256 amountOut = + tychoRouter.splitSwap(amount_in, 3, pleEncode(swaps)); + + uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(ALICE); + assertEq(usdcBalance, 2610580090); + assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 0); + } + + function testSplitSwapSplitHops() public { + // Trade 1 WETH for USDC through DAI and WBTC with 4 swaps on Uniswap V2 + // -> DAI -> + // 1 WETH USDC + // -> WBTC -> + // (univ2) (univ2) + uint256 amount_in = 1 ether; + deal(WETH_ADDR, address(tychoRouter), amount_in); + + bytes[] memory swaps = new bytes[](4); + // WETH -> WBTC (60%) + swaps[0] = encodeSwap( + uint8(0), + uint8(1), + (0xffffff * 60) / 100, // 60% + encodeUniswapV2Swap( + WETH_ADDR, WETH_WBTC_POOL, address(tychoRouter), false + ) + ); + // WBTC -> USDC + swaps[1] = encodeSwap( + uint8(1), + uint8(2), + uint24(0), + encodeUniswapV2Swap(WBTC_ADDR, USDC_WBTC_POOL, ALICE, true) + ); + // WETH -> DAI + swaps[2] = encodeSwap( + uint8(0), + uint8(3), + uint24(0), + encodeUniswapV2Swap( + WETH_ADDR, WETH_DAI_POOL, address(tychoRouter), false + ) + ); + + // DAI -> USDC + swaps[3] = encodeSwap( + uint8(3), + uint8(2), + uint24(0), + encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, ALICE, true) + ); + + uint256 minAmountOut = 2580 * 1e6; + uint256 amountOut = + tychoRouter.splitSwap(amount_in, 4, pleEncode(swaps)); + + uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(ALICE); + assertEq(usdcBalance, 2581503157); + assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 0); + } + + function testSwapChecked() public { + // Trade 1 WETH for DAI with 1 swap on Uniswap V2 + // Does permit2 token approval and transfer + // Checks amount out at the end + uint256 amount_in = 1 ether; + deal(WETH_ADDR, ALICE, amount_in); + + vm.startPrank(ALICE); + + ( + IAllowanceTransfer.PermitSingle memory permitSingle, + bytes memory signature + ) = handlePermit2Approval(WETH_ADDR, amount_in); + + bytes memory protocolData = + encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false); + + bytes memory swap = + encodeSwap(uint8(0), uint8(1), uint24(0), protocolData); + bytes[] memory swaps = new bytes[](1); + swaps[0] = swap; + + uint256 minAmountOut = 2600 * 1e18; + uint256 amountOut = tychoRouter.swap( + amount_in, + WETH_ADDR, + DAI_ADDR, + true, + minAmountOut, + false, + false, + 2, + ALICE, + permitSingle, + signature, + pleEncode(swaps) + ); + + uint256 daiBalance = IERC20(DAI_ADDR).balanceOf(ALICE); + assertEq(daiBalance, 2630432278145144658455); + assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 0); + + vm.stopPrank(); + } + + function testSwapCheckedFailure() public { + // Trade 1 WETH for DAI with 1 swap on Uniswap V2 + // Does permit2 token approval and transfer + // Checks amount out at the end and fails + uint256 amount_in = 1 ether; + deal(WETH_ADDR, ALICE, amount_in); + + vm.startPrank(ALICE); + + ( + IAllowanceTransfer.PermitSingle memory permitSingle, + bytes memory signature + ) = handlePermit2Approval(WETH_ADDR, amount_in); + + bytes memory protocolData = + encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false); + + bytes memory swap = + encodeSwap(uint8(0), uint8(1), uint24(0), protocolData); + bytes[] memory swaps = new bytes[](1); + swaps[0] = swap; + + uint256 minAmountOut = 3000 * 1e18; + vm.expectRevert( + abi.encodeWithSelector( + TychoRouter__NegativeSlippage.selector, + 2630432278145144658455, // actual amountOut + minAmountOut + ) + ); + uint256 amountOut = tychoRouter.swap( + amount_in, + WETH_ADDR, + DAI_ADDR, + true, + minAmountOut, + false, + false, + 2, + ALICE, + permitSingle, + signature, + pleEncode(swaps) + ); + vm.stopPrank(); + } + + function testSwapFee() public { + // Trade 1 WETH for DAI with 1 swap on Uniswap V2 + // Does permit2 token approval and transfer + // Takes fee at the end + + vm.startPrank(FEE_SETTER); + tychoRouter.setFee(100); + tychoRouter.setFeeReceiver(FEE_RECEIVER); + vm.stopPrank(); + + uint256 amount_in = 1 ether; + deal(WETH_ADDR, ALICE, amount_in); + + vm.startPrank(ALICE); + + ( + IAllowanceTransfer.PermitSingle memory permitSingle, + bytes memory signature + ) = handlePermit2Approval(WETH_ADDR, amount_in); + + bytes memory protocolData = encodeUniswapV2Swap( + WETH_ADDR, WETH_DAI_POOL, address(tychoRouter), false + ); + + bytes memory swap = + encodeSwap(uint8(0), uint8(1), uint24(0), protocolData); + bytes[] memory swaps = new bytes[](1); + swaps[0] = swap; + + uint256 amountOut = tychoRouter.swap( + amount_in, + WETH_ADDR, + DAI_ADDR, + false, + 0, + false, + false, + 2, + ALICE, + permitSingle, + signature, + pleEncode(swaps) + ); + + uint256 daiBalance = IERC20(DAI_ADDR).balanceOf(ALICE); + assertEq(daiBalance, 2604127955363693211871); + assertEq(IERC20(DAI_ADDR).balanceOf(FEE_RECEIVER), 26304322781451446584); + + vm.stopPrank(); + } + + function testSwapWrapETH() public { + // Trade 1 ETH (and wrap it) for DAI with 1 swap on Uniswap V2 + + uint256 amount_in = 1 ether; + deal(ALICE, amount_in); + + vm.startPrank(ALICE); + + IAllowanceTransfer.PermitSingle memory emptyPermitSingle = + IAllowanceTransfer.PermitSingle({ + details: IAllowanceTransfer.PermitDetails({ + token: address(0), + amount: 0, + expiration: 0, + nonce: 0 + }), + spender: address(0), + sigDeadline: 0 + }); + bytes memory protocolData = + encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false); + + bytes memory swap = + encodeSwap(uint8(0), uint8(1), uint24(0), protocolData); + bytes[] memory swaps = new bytes[](1); + swaps[0] = swap; + + uint256 amountOut = tychoRouter.swap{value: amount_in}( + amount_in, + address(0), + DAI_ADDR, + false, + 0, + true, + false, + 2, + ALICE, + emptyPermitSingle, + "", + pleEncode(swaps) + ); + + uint256 daiBalance = IERC20(DAI_ADDR).balanceOf(ALICE); + assertEq(daiBalance, 2630432278145144658455); + assertEq(ALICE.balance, 0); + + vm.stopPrank(); + } + + function testSwapUnwrapETH() public { + // Trade 3k DAI for WETH with 1 swap on Uniswap V2 and unwrap it at the end + + uint256 amount_in = 3_000 * 10 ** 18; + deal(DAI_ADDR, ALICE, amount_in); + + vm.startPrank(ALICE); + + ( + IAllowanceTransfer.PermitSingle memory permitSingle, + bytes memory signature + ) = handlePermit2Approval(DAI_ADDR, amount_in); + + bytes memory protocolData = encodeUniswapV2Swap( + DAI_ADDR, WETH_DAI_POOL, address(tychoRouter), true + ); + + bytes memory swap = + encodeSwap(uint8(0), uint8(1), uint24(0), protocolData); + bytes[] memory swaps = new bytes[](1); + swaps[0] = swap; + + uint256 amountOut = tychoRouter.swap( + amount_in, + DAI_ADDR, + address(0), + false, + 0, + false, + true, + 2, + ALICE, + permitSingle, + signature, + pleEncode(swaps) + ); + + assertEq(ALICE.balance, 1132829934891544187); // 1.13 ETH + + vm.stopPrank(); + } } diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 345f7f0..d1bb5ea 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "@src/TychoRouter.sol"; +import "../src/executors/UniswapV2Executor.sol"; import "./Constants.sol"; import "./mock/MockERC20.sol"; +import "@src/TychoRouter.sol"; import {WETH} from "../lib/permit2/lib/solmate/src/tokens/WETH.sol"; contract TychoRouterExposed is TychoRouter { @@ -16,12 +17,20 @@ contract TychoRouterExposed is TychoRouter { function unwrapETH(uint256 amount) external { return _unwrapETH(amount); } + + function splitSwap(uint256 amountIn, uint256 nTokens, bytes calldata swaps) + external + returns (uint256) + { + return _splitSwap(amountIn, nTokens, swaps); + } } contract TychoRouterTestSetup is Test, Constants { TychoRouterExposed tychoRouter; address executorSetter; address permit2Address = address(0x000000000022D473030F116dDEE9F6B43aC78BA3); + UniswapV2Executor public usv2Executor; MockERC20[] tokens; function setUp() public { @@ -35,10 +44,18 @@ contract TychoRouterTestSetup is Test, Constants { tychoRouter.grantRole(keccak256("FEE_SETTER_ROLE"), FEE_SETTER); tychoRouter.grantRole(keccak256("PAUSER_ROLE"), PAUSER); tychoRouter.grantRole(keccak256("UNPAUSER_ROLE"), UNPAUSER); + tychoRouter.grantRole( + keccak256("EXECUTOR_SETTER_ROLE"), EXECUTOR_SETTER + ); executorSetter = BOB; deployDummyContract(); vm.stopPrank(); + usv2Executor = new UniswapV2Executor(); + vm.startPrank(EXECUTOR_SETTER); + tychoRouter.setExecutor(address(usv2Executor)); + vm.stopPrank(); + vm.startPrank(BOB); tokens.push(new MockERC20("Token A", "A")); tokens.push(new MockERC20("Token B", "B")); @@ -57,4 +74,116 @@ contract TychoRouterTestSetup is Test, Constants { tokens[i].mint(to, amount); } } + + /** + * @dev Handles the Permit2 approval process for Alice, allowing the TychoRouter contract + * to spend `amount_in` of `tokenIn` on her behalf. + * + * This function approves the Permit2 contract to transfer the specified token amount + * and constructs a `PermitSingle` struct for the approval. It also generates a valid + * EIP-712 signature for the approval using Alice's private key. + * + * @param tokenIn The address of the token being approved. + * @param amount_in The amount of tokens to approve for transfer. + * @return permitSingle The `PermitSingle` struct containing the approval details. + * @return signature The EIP-712 signature for the approval. + */ + function handlePermit2Approval(address tokenIn, uint256 amount_in) + internal + returns (IAllowanceTransfer.PermitSingle memory, bytes memory) + { + IERC20(tokenIn).approve(permit2Address, amount_in); + IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer + .PermitSingle({ + details: IAllowanceTransfer.PermitDetails({ + token: tokenIn, + amount: uint160(amount_in), + expiration: uint48(block.timestamp + 1 days), + nonce: 0 + }), + spender: address(tychoRouter), + sigDeadline: block.timestamp + 1 days + }); + + bytes memory signature = signPermit2(permitSingle, ALICE_PK); + return (permitSingle, signature); + } + + /** + * @dev Signs a Permit2 `PermitSingle` struct with the given private key. + * @param permit The `PermitSingle` struct to sign. + * @param privateKey The private key of the signer. + * @return The signature as a `bytes` array. + */ + function signPermit2( + IAllowanceTransfer.PermitSingle memory permit, + uint256 privateKey + ) internal returns (bytes memory) { + bytes32 _PERMIT_DETAILS_TYPEHASH = keccak256( + "PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); + bytes32 _PERMIT_SINGLE_TYPEHASH = keccak256( + "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); + bytes32 domainSeparator = keccak256( + abi.encode( + keccak256( + "EIP712Domain(string name,uint256 chainId,address verifyingContract)" + ), + keccak256("Permit2"), + block.chainid, + permit2Address + ) + ); + bytes32 detailsHash = + keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details)); + bytes32 permitHash = keccak256( + abi.encode( + _PERMIT_SINGLE_TYPEHASH, + detailsHash, + permit.spender, + permit.sigDeadline + ) + ); + + bytes32 digest = + keccak256(abi.encodePacked("\x19\x01", domainSeparator, permitHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + + return abi.encodePacked(r, s, v); + } + + function pleEncode(bytes[] memory data) + public + pure + returns (bytes memory encoded) + { + for (uint256 i = 0; i < data.length; i++) { + encoded = bytes.concat( + encoded, + abi.encodePacked(bytes2(uint16(data[i].length)), data[i]) + ); + } + } + + function encodeSwap( + uint8 tokenInIndex, + uint8 tokenOutIndex, + uint24 split, + bytes memory protocolData + ) internal pure returns (bytes memory) { + return + abi.encodePacked(tokenInIndex, tokenOutIndex, split, protocolData); + } + + function encodeUniswapV2Swap( + address tokenIn, + address target, + address receiver, + bool zero2one + ) internal view returns (bytes memory) { + return abi.encodePacked( + usv2Executor, bytes4(0), tokenIn, target, receiver, zero2one + ); + } } diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 1598a6a..cdc0c89 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -34,7 +34,6 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { UniswapV2ExecutorExposed uniswapV2Exposed; IERC20 WETH = IERC20(WETH_ADDR); IERC20 DAI = IERC20(DAI_ADDR); - address WETH_DAI_POOL = 0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11; function setUp() public { uint256 forkBlock = 17323404;