diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 2f9683f..3c62380 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -790,6 +790,9 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { return result; } + /** + * @dev Gets balance of a token for a given address. Supports both native ETH and ERC20 tokens. + */ function _balanceOf(address token, address owner) internal view diff --git a/foundry/test/TychoRouterProtocolIntegration.t.sol b/foundry/test/TychoRouterProtocolIntegration.t.sol index 331ef7c..684c384 100644 --- a/foundry/test/TychoRouterProtocolIntegration.t.sol +++ b/foundry/test/TychoRouterProtocolIntegration.t.sol @@ -2,8 +2,93 @@ pragma solidity ^0.8.26; import "./TychoRouterTestSetup.sol"; +import "./executors/UniswapV4Utils.sol"; contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { + function testSingleSwapUSV4CallbackPermit2() public { + vm.startPrank(ALICE); + uint256 amountIn = 100 ether; + deal(USDE_ADDR, ALICE, amountIn); + ( + IAllowanceTransfer.PermitSingle memory permitSingle, + bytes memory signature + ) = handlePermit2Approval(USDE_ADDR, tychoRouterAddr, amountIn); + + UniswapV4Executor.UniswapV4Pool[] memory pools = + new UniswapV4Executor.UniswapV4Pool[](1); + pools[0] = UniswapV4Executor.UniswapV4Pool({ + intermediaryToken: USDT_ADDR, + fee: uint24(100), + tickSpacing: int24(1) + }); + + bytes memory protocolData = UniswapV4Utils.encodeExactInput( + USDE_ADDR, + USDT_ADDR, + true, + TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL, + ALICE, + pools + ); + + bytes memory swap = + encodeSingleSwap(address(usv4Executor), protocolData); + + tychoRouter.singleSwapPermit2( + amountIn, + USDE_ADDR, + USDT_ADDR, + 99943850, + false, + false, + ALICE, + permitSingle, + signature, + swap + ); + + assertEq(IERC20(USDT_ADDR).balanceOf(ALICE), 99963618); + vm.stopPrank(); + } + + function testSplitSwapMultipleUSV4Callback() public { + // This test has two uniswap v4 hops that will be executed inside of the V4 pool manager + // USDE -> USDT -> WBTC + uint256 amountIn = 100 ether; + deal(USDE_ADDR, tychoRouterAddr, amountIn); + + UniswapV4Executor.UniswapV4Pool[] memory pools = + new UniswapV4Executor.UniswapV4Pool[](2); + pools[0] = UniswapV4Executor.UniswapV4Pool({ + intermediaryToken: USDT_ADDR, + fee: uint24(100), + tickSpacing: int24(1) + }); + pools[1] = UniswapV4Executor.UniswapV4Pool({ + intermediaryToken: WBTC_ADDR, + fee: uint24(3000), + tickSpacing: int24(60) + }); + + bytes memory protocolData = UniswapV4Utils.encodeExactInput( + USDE_ADDR, + WBTC_ADDR, + true, + TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, + ALICE, + pools + ); + + bytes memory swap = + encodeSingleSwap(address(usv4Executor), protocolData); + + tychoRouter.singleSwap( + amountIn, USDE_ADDR, WBTC_ADDR, 118280, false, false, ALICE, swap + ); + + assertEq(IERC20(WBTC_ADDR).balanceOf(ALICE), 118281); + } + function testSequentialUSV4Integration() public { // Test created with calldata from our router encoder. @@ -141,4 +226,49 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { vm.stopPrank(); } + + function testSingleSwapUSV3Permit2() public { + // Trade 1 WETH for DAI with 1 swap on Uniswap V3 using Permit2 + // Tests entire USV3 flow including callback + // 1 WETH -> DAI + // (USV3) + vm.startPrank(ALICE); + uint256 amountIn = 10 ** 18; + deal(WETH_ADDR, ALICE, amountIn); + ( + IAllowanceTransfer.PermitSingle memory permitSingle, + bytes memory signature + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); + + uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI + bool zeroForOne = false; + bytes memory protocolData = encodeUniswapV3Swap( + WETH_ADDR, + DAI_ADDR, + ALICE, + DAI_WETH_USV3, + zeroForOne, + TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL + ); + bytes memory swap = + encodeSingleSwap(address(usv3Executor), protocolData); + + tychoRouter.singleSwapPermit2( + amountIn, + WETH_ADDR, + DAI_ADDR, + expAmountOut - 1, + false, + false, + ALICE, + permitSingle, + signature, + swap + ); + + uint256 finalBalance = IERC20(DAI_ADDR).balanceOf(ALICE); + assertGe(finalBalance, expAmountOut); + + vm.stopPrank(); + } } diff --git a/foundry/test/TychoRouterSplitSwap.t.sol b/foundry/test/TychoRouterSplitSwap.t.sol index 0f8b678..5246542 100644 --- a/foundry/test/TychoRouterSplitSwap.t.sol +++ b/foundry/test/TychoRouterSplitSwap.t.sol @@ -349,56 +349,6 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { vm.stopPrank(); } - function testSplitSwapSingleUSV3Permit2() public { - // Trade 1 WETH for DAI with 1 swap on Uniswap V3 using Permit2 - // Tests entire USV3 flow including callback - // 1 WETH -> DAI - // (USV3) - vm.startPrank(ALICE); - uint256 amountIn = 10 ** 18; - deal(WETH_ADDR, ALICE, amountIn); - ( - IAllowanceTransfer.PermitSingle memory permitSingle, - bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); - - uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI - bool zeroForOne = false; - bytes memory protocolData = encodeUniswapV3Swap( - WETH_ADDR, - DAI_ADDR, - ALICE, - DAI_WETH_USV3, - zeroForOne, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL - ); - bytes memory swap = encodeSplitSwap( - uint8(0), uint8(1), uint24(0), address(usv3Executor), protocolData - ); - - bytes[] memory swaps = new bytes[](1); - swaps[0] = swap; - - tychoRouter.splitSwapPermit2( - amountIn, - WETH_ADDR, - DAI_ADDR, - expAmountOut - 1, - false, - false, - 2, - ALICE, - permitSingle, - signature, - pleEncode(swaps) - ); - - uint256 finalBalance = IERC20(DAI_ADDR).balanceOf(ALICE); - assertGe(finalBalance, expAmountOut); - - vm.stopPrank(); - } - function testEmptySwapsRevert() public { uint256 amountIn = 10 ** 18; bytes memory swaps = ""; @@ -406,97 +356,6 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouter.exposedSplitSwap(amountIn, 2, swaps); } - function testSplitSwapSingleUSV4CallbackPermit2() public { - vm.startPrank(ALICE); - uint256 amountIn = 100 ether; - deal(USDE_ADDR, ALICE, amountIn); - ( - IAllowanceTransfer.PermitSingle memory permitSingle, - bytes memory signature - ) = handlePermit2Approval(USDE_ADDR, tychoRouterAddr, amountIn); - - UniswapV4Executor.UniswapV4Pool[] memory pools = - new UniswapV4Executor.UniswapV4Pool[](1); - pools[0] = UniswapV4Executor.UniswapV4Pool({ - intermediaryToken: USDT_ADDR, - fee: uint24(100), - tickSpacing: int24(1) - }); - - bytes memory protocolData = UniswapV4Utils.encodeExactInput( - USDE_ADDR, - USDT_ADDR, - true, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL, - ALICE, - pools - ); - - bytes memory swap = encodeSplitSwap( - uint8(0), uint8(1), uint24(0), address(usv4Executor), protocolData - ); - - bytes[] memory swaps = new bytes[](1); - swaps[0] = swap; - - tychoRouter.splitSwapPermit2( - amountIn, - USDE_ADDR, - USDT_ADDR, - 99943850, - false, - false, - 2, - ALICE, - permitSingle, - signature, - pleEncode(swaps) - ); - - assertEq(IERC20(USDT_ADDR).balanceOf(ALICE), 99963618); - vm.stopPrank(); - } - - function testSplitSwapMultipleUSV4Callback() public { - // This test has two uniswap v4 hops that will be executed inside of the V4 pool manager - // USDE -> USDT -> WBTC - uint256 amountIn = 100 ether; - deal(USDE_ADDR, tychoRouterAddr, amountIn); - - UniswapV4Executor.UniswapV4Pool[] memory pools = - new UniswapV4Executor.UniswapV4Pool[](2); - pools[0] = UniswapV4Executor.UniswapV4Pool({ - intermediaryToken: USDT_ADDR, - fee: uint24(100), - tickSpacing: int24(1) - }); - pools[1] = UniswapV4Executor.UniswapV4Pool({ - intermediaryToken: WBTC_ADDR, - fee: uint24(3000), - tickSpacing: int24(60) - }); - - bytes memory protocolData = UniswapV4Utils.encodeExactInput( - USDE_ADDR, - WBTC_ADDR, - true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, - ALICE, - pools - ); - - bytes memory swap = encodeSplitSwap( - uint8(0), uint8(1), uint24(0), address(usv4Executor), protocolData - ); - - bytes[] memory swaps = new bytes[](1); - swaps[0] = swap; - - tychoRouter.exposedSplitSwap(amountIn, 2, pleEncode(swaps)); - - assertEq(IERC20(WBTC_ADDR).balanceOf(ALICE), 118281); - } - function testSplitInputCyclicSwapInternalMethod() public { // This test has start and end tokens that are the same // The flow is: