From 53d48f1bac4bd2f6ac84b62a3ffa427a1770bc45 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 26 Feb 2025 18:05:29 +0530 Subject: [PATCH 01/11] refactor: centralize swap logic to reduce duplication between swap and swapPermit2 --- foundry/src/TychoRouter.sol | 71 +++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index c032a97..0896279 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -151,37 +151,16 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { tokenIn = address(_weth); } - amountOut = _swap(amountIn, nTokens, swaps); - - if (fee > 0) { - uint256 feeAmount = (amountOut * fee) / 10000; - amountOut -= feeAmount; - IERC20(tokenOut).safeTransfer(feeReceiver, feeAmount); - } - - if (minAmountOut > 0 && amountOut < minAmountOut) { - revert TychoRouter__NegativeSlippage(amountOut, minAmountOut); - } - - uint256 leftoverAmountIn; - if (tokenIn == address(0)) { - leftoverAmountIn = address(this).balance; - } else { - leftoverAmountIn = IERC20(tokenIn).balanceOf(address(this)); - } - - if (leftoverAmountIn > 0) { - revert TychoRouter__AmountInNotFullySpent(leftoverAmountIn); - } - - if (unwrapEth) { - _unwrapETH(amountOut); - } - if (tokenOut == address(0)) { - Address.sendValue(payable(receiver), amountOut); - } else { - IERC20(tokenOut).safeTransfer(receiver, amountOut); - } + return _executeSwap( + amountIn, + tokenIn, + tokenOut, + minAmountOut, + unwrapEth, + nTokens, + receiver, + swaps + ); } /** @@ -242,6 +221,36 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { ); } + return _executeSwap( + amountIn, + tokenIn, + tokenOut, + minAmountOut, + unwrapEth, + nTokens, + receiver, + swaps + ); + } + + /** + * @notice Internal implementation of the core swap logic shared between swap() and swapPermit2(). + * + * @notice This function centralizes the swap execution logic. + * @notice For detailed documentation on parameters and behavior, see the documentation for + * swap() and swapPermit2() functions. + * + */ + function _executeSwap( + uint256 amountIn, + address tokenIn, + address tokenOut, + uint256 minAmountOut, + bool unwrapEth, + uint256 nTokens, + address receiver, + bytes calldata swaps + ) internal returns (uint256 amountOut) { amountOut = _swap(amountIn, nTokens, swaps); if (fee > 0) { From 0be69c9aea78f486338ab50ba803ac4869df93ac Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 26 Feb 2025 19:04:26 +0530 Subject: [PATCH 02/11] refactor: check if amountIn was fully consumed based on balance changes --- foundry/src/TychoRouter.sol | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 0896279..6f10fed 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -251,8 +251,23 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { address receiver, bytes calldata swaps ) internal returns (uint256 amountOut) { + uint256 initialBalance = tokenIn == address(0) + ? address(this).balance + : IERC20(tokenIn).balanceOf(address(this)); + amountOut = _swap(amountIn, nTokens, swaps); + uint256 currentBalance = tokenIn == address(0) + ? address(this).balance + : IERC20(tokenIn).balanceOf(address(this)); + + uint256 amountConsumed = initialBalance - currentBalance; + + if (amountConsumed < amountIn) { + uint256 leftoverAmount = amountIn - amountConsumed; + revert TychoRouter__AmountInNotFullySpent(leftoverAmount); + } + if (fee > 0) { uint256 feeAmount = (amountOut * fee) / 10000; amountOut -= feeAmount; @@ -263,17 +278,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { revert TychoRouter__NegativeSlippage(amountOut, minAmountOut); } - uint256 leftoverAmountIn; - if (tokenIn == address(0)) { - leftoverAmountIn = address(this).balance; - } else { - leftoverAmountIn = IERC20(tokenIn).balanceOf(address(this)); - } - - if (leftoverAmountIn > 0) { - revert TychoRouter__AmountInNotFullySpent(leftoverAmountIn); - } - if (unwrapEth) { _unwrapETH(amountOut); } From 2323ad3fd980ef6828938a274ab8e5ac277b6004 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 26 Feb 2025 10:16:44 +0530 Subject: [PATCH 03/11] test: add simple base network tests in usv2 and router --- foundry/test/Constants.sol | 10 ++++++++- foundry/test/TychoRouter.t.sol | 22 +++++++++++++++++++ .../test/executors/UniswapV2Executor.t.sol | 17 ++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index 6675759..2f77465 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -3,7 +3,15 @@ pragma solidity ^0.8.26; import "forge-std/Test.sol"; -contract Constants is Test { +contract BaseConstants { + address BASE_USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; + address BASE_MAG7 = 0x9E6A46f294bB67c20F1D1E7AfB0bBEf614403B55; + + // uniswap v2 + address USDC_MAG7_POOL = 0x739c2431670A12E2cF8e11E3603eB96e6728a789; +} + +contract Constants is Test, BaseConstants { address ADMIN = makeAddr("admin"); //admin=us address BOB = makeAddr("bob"); //bob=someone!=us address FUND_RESCUER = makeAddr("fundRescuer"); diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index e503f7e..89e4d74 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -978,4 +978,26 @@ contract TychoRouterTest is TychoRouterTestSetup { assertEq(IERC20(WBTC_ADDR).balanceOf(tychoRouterAddr), 102718); } + + // Base Network Tests + // Make sure to set the fork block to 26857267 for base network + function testSwapSingleBase() public { + vm.skip(true); + uint256 amountIn = 100 * 1e6; + deal(BASE_USDC, tychoRouterAddr, amountIn); + + bytes memory protocolData = encodeUniswapV2Swap( + BASE_USDC, USDC_MAG7_POOL, tychoRouterAddr, true + ); + + bytes memory swap = encodeSwap( + uint8(0), uint8(1), uint24(0), address(usv2Executor), protocolData + ); + bytes[] memory swaps = new bytes[](1); + swaps[0] = swap; + + tychoRouter.exposedSwap(amountIn, 2, pleEncode(swaps)); + assertEq(IERC20(BASE_USDC).balanceOf(tychoRouterAddr), 0); + assertGt(IERC20(BASE_MAG7).balanceOf(tychoRouterAddr), 0); + } } diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 01722eb..92b7640 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -156,4 +156,21 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { vm.expectRevert(UniswapV2Executor__InvalidTarget.selector); uniswapV2Exposed.swap(amountIn, protocolData); } + + // Base Network Tests + // Make sure to set the fork block to 26857267 for base network + function testSwapBaseNetwork() public { + vm.skip(true); + uint256 amountIn = 10 * 10 ** 6; + bool zeroForOne = true; + bytes memory protocolData = + abi.encodePacked(BASE_USDC, USDC_MAG7_POOL, BOB, zeroForOne); + + deal(BASE_USDC, address(uniswapV2Exposed), amountIn); + + uniswapV2Exposed.swap(amountIn, protocolData); + + assertEq(IERC20(BASE_USDC).balanceOf(BOB), 0); + assertGe(IERC20(BASE_MAG7).balanceOf(BOB), 0); + } } From ed2bc414d8c7fff7fcf3a9362a253cebf0c6fb44 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 26 Feb 2025 17:34:22 +0530 Subject: [PATCH 04/11] test: update asserts in base network test --- foundry/test/TychoRouter.t.sol | 5 ++--- foundry/test/executors/UniswapV2Executor.t.sol | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index 89e4d74..be68fbc 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -983,7 +983,7 @@ contract TychoRouterTest is TychoRouterTestSetup { // Make sure to set the fork block to 26857267 for base network function testSwapSingleBase() public { vm.skip(true); - uint256 amountIn = 100 * 1e6; + uint256 amountIn = 10 * 10 ** 6; deal(BASE_USDC, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap( @@ -997,7 +997,6 @@ contract TychoRouterTest is TychoRouterTestSetup { swaps[0] = swap; tychoRouter.exposedSwap(amountIn, 2, pleEncode(swaps)); - assertEq(IERC20(BASE_USDC).balanceOf(tychoRouterAddr), 0); - assertGt(IERC20(BASE_MAG7).balanceOf(tychoRouterAddr), 0); + assertGt(IERC20(BASE_MAG7).balanceOf(tychoRouterAddr), 1379830606); } } diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 92b7640..d905802 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -170,7 +170,6 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { uniswapV2Exposed.swap(amountIn, protocolData); - assertEq(IERC20(BASE_USDC).balanceOf(BOB), 0); - assertGe(IERC20(BASE_MAG7).balanceOf(BOB), 0); + assertEq(IERC20(BASE_MAG7).balanceOf(BOB), 1379830606); } } From 030505c497cc29bc9f2f2ed6f123d03c4dfe823c Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 26 Feb 2025 23:04:40 +0530 Subject: [PATCH 05/11] test: add vm.rollFork with correct block in base network tests --- foundry/test/TychoRouter.t.sol | 3 ++- foundry/test/executors/UniswapV2Executor.t.sol | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index be68fbc..6033feb 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -980,9 +980,10 @@ contract TychoRouterTest is TychoRouterTestSetup { } // Base Network Tests - // Make sure to set the fork block to 26857267 for base network + // Make sure to set the RPC_URL to base network function testSwapSingleBase() public { vm.skip(true); + vm.rollFork(26857267); uint256 amountIn = 10 * 10 ** 6; deal(BASE_USDC, tychoRouterAddr, amountIn); diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index d905802..0b4d705 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -158,9 +158,10 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { } // Base Network Tests - // Make sure to set the fork block to 26857267 for base network + // Make sure to set the RPC_URL to base network function testSwapBaseNetwork() public { vm.skip(true); + vm.rollFork(26857267); uint256 amountIn = 10 * 10 ** 6; bool zeroForOne = true; bytes memory protocolData = From d1f7f6dde129fd366e3f10aae1c23424343baa4a Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 26 Feb 2025 18:05:29 +0530 Subject: [PATCH 06/11] refactor: centralize swap logic to reduce duplication between swap and swapPermit2 --- foundry/src/TychoRouter.sol | 71 +++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index c032a97..0896279 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -151,37 +151,16 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { tokenIn = address(_weth); } - amountOut = _swap(amountIn, nTokens, swaps); - - if (fee > 0) { - uint256 feeAmount = (amountOut * fee) / 10000; - amountOut -= feeAmount; - IERC20(tokenOut).safeTransfer(feeReceiver, feeAmount); - } - - if (minAmountOut > 0 && amountOut < minAmountOut) { - revert TychoRouter__NegativeSlippage(amountOut, minAmountOut); - } - - uint256 leftoverAmountIn; - if (tokenIn == address(0)) { - leftoverAmountIn = address(this).balance; - } else { - leftoverAmountIn = IERC20(tokenIn).balanceOf(address(this)); - } - - if (leftoverAmountIn > 0) { - revert TychoRouter__AmountInNotFullySpent(leftoverAmountIn); - } - - if (unwrapEth) { - _unwrapETH(amountOut); - } - if (tokenOut == address(0)) { - Address.sendValue(payable(receiver), amountOut); - } else { - IERC20(tokenOut).safeTransfer(receiver, amountOut); - } + return _executeSwap( + amountIn, + tokenIn, + tokenOut, + minAmountOut, + unwrapEth, + nTokens, + receiver, + swaps + ); } /** @@ -242,6 +221,36 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { ); } + return _executeSwap( + amountIn, + tokenIn, + tokenOut, + minAmountOut, + unwrapEth, + nTokens, + receiver, + swaps + ); + } + + /** + * @notice Internal implementation of the core swap logic shared between swap() and swapPermit2(). + * + * @notice This function centralizes the swap execution logic. + * @notice For detailed documentation on parameters and behavior, see the documentation for + * swap() and swapPermit2() functions. + * + */ + function _executeSwap( + uint256 amountIn, + address tokenIn, + address tokenOut, + uint256 minAmountOut, + bool unwrapEth, + uint256 nTokens, + address receiver, + bytes calldata swaps + ) internal returns (uint256 amountOut) { amountOut = _swap(amountIn, nTokens, swaps); if (fee > 0) { From ff83693b2872743f1673091fd0906299e8dd1579 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 26 Feb 2025 20:44:29 +0530 Subject: [PATCH 07/11] refactor: move wrap operation and receiver check to _executeSwap --- foundry/src/TychoRouter.sol | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 0896279..ac50628 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -141,21 +141,12 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { address receiver, bytes calldata swaps ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { - if (receiver == address(0)) { - revert TychoRouter__AddressZero(); - } - - // Assume funds already in our router. - if (wrapEth) { - _wrapETH(amountIn); - tokenIn = address(_weth); - } - return _executeSwap( amountIn, tokenIn, tokenOut, minAmountOut, + wrapEth, unwrapEth, nTokens, receiver, @@ -203,15 +194,8 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { bytes calldata signature, bytes calldata swaps ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { - if (receiver == address(0)) { - revert TychoRouter__AddressZero(); - } - - // For native ETH, assume funds already in our router. Else, transfer and handle approval. - if (wrapEth) { - _wrapETH(amountIn); - tokenIn = address(_weth); - } else if (tokenIn != address(0)) { + // For non native ETH, transfer and handle approval. + if (tokenIn != address(0)) { permit2.permit(msg.sender, permitSingle, signature); permit2.transferFrom( msg.sender, @@ -226,6 +210,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { tokenIn, tokenOut, minAmountOut, + wrapEth, unwrapEth, nTokens, receiver, @@ -246,11 +231,21 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { address tokenIn, address tokenOut, uint256 minAmountOut, + bool wrapEth, bool unwrapEth, uint256 nTokens, address receiver, bytes calldata swaps ) internal returns (uint256 amountOut) { + if (receiver == address(0)) { + revert TychoRouter__AddressZero(); + } + // For native ETH, assume funds are already in the router. + if (wrapEth) { + _wrapETH(amountIn); + tokenIn = address(_weth); + } + amountOut = _swap(amountIn, nTokens, swaps); if (fee > 0) { From 07987a3584a38311c770c6007021ca1751879d73 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 26 Feb 2025 21:31:14 +0530 Subject: [PATCH 08/11] refactor: rm _executeSwap and move core logic back to swap, make swapPermit2 use swap --- foundry/src/TychoRouter.sol | 120 +++++++++++++----------------------- 1 file changed, 44 insertions(+), 76 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index ac50628..83303e9 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -140,18 +140,47 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { uint256 nTokens, address receiver, bytes calldata swaps - ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { - return _executeSwap( - amountIn, - tokenIn, - tokenOut, - minAmountOut, - wrapEth, - unwrapEth, - nTokens, - receiver, - swaps - ); + ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { + if (receiver == address(0)) { + revert TychoRouter__AddressZero(); + } + // Assume funds are already in the router. + if (wrapEth) { + _wrapETH(amountIn); + tokenIn = address(_weth); + } + + amountOut = _swap(amountIn, nTokens, swaps); + + if (fee > 0) { + uint256 feeAmount = (amountOut * fee) / 10000; + amountOut -= feeAmount; + IERC20(tokenOut).safeTransfer(feeReceiver, feeAmount); + } + + if (minAmountOut > 0 && amountOut < minAmountOut) { + revert TychoRouter__NegativeSlippage(amountOut, minAmountOut); + } + + uint256 leftoverAmountIn; + if (tokenIn == address(0)) { + leftoverAmountIn = address(this).balance; + } else { + leftoverAmountIn = IERC20(tokenIn).balanceOf(address(this)); + } + + if (leftoverAmountIn > 0) { + revert TychoRouter__AmountInNotFullySpent(leftoverAmountIn); + } + + if (unwrapEth) { + _unwrapETH(amountOut); + } + if (tokenOut == address(0)) { + Address.sendValue(payable(receiver), amountOut); + } else { + IERC20(tokenOut).safeTransfer(receiver, amountOut); + } } /** @@ -193,8 +222,8 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature, bytes calldata swaps - ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { - // For non native ETH, transfer and handle approval. + ) external payable whenNotPaused returns (uint256 amountOut) { + // For native ETH, assume funds already in our router. Else, transfer and handle approval. if (tokenIn != address(0)) { permit2.permit(msg.sender, permitSingle, signature); permit2.transferFrom( @@ -205,7 +234,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { ); } - return _executeSwap( + return swap( amountIn, tokenIn, tokenOut, @@ -218,67 +247,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { ); } - /** - * @notice Internal implementation of the core swap logic shared between swap() and swapPermit2(). - * - * @notice This function centralizes the swap execution logic. - * @notice For detailed documentation on parameters and behavior, see the documentation for - * swap() and swapPermit2() functions. - * - */ - function _executeSwap( - uint256 amountIn, - address tokenIn, - address tokenOut, - uint256 minAmountOut, - bool wrapEth, - bool unwrapEth, - uint256 nTokens, - address receiver, - bytes calldata swaps - ) internal returns (uint256 amountOut) { - if (receiver == address(0)) { - revert TychoRouter__AddressZero(); - } - // For native ETH, assume funds are already in the router. - if (wrapEth) { - _wrapETH(amountIn); - tokenIn = address(_weth); - } - - amountOut = _swap(amountIn, nTokens, swaps); - - if (fee > 0) { - uint256 feeAmount = (amountOut * fee) / 10000; - amountOut -= feeAmount; - IERC20(tokenOut).safeTransfer(feeReceiver, feeAmount); - } - - if (minAmountOut > 0 && amountOut < minAmountOut) { - revert TychoRouter__NegativeSlippage(amountOut, minAmountOut); - } - - uint256 leftoverAmountIn; - if (tokenIn == address(0)) { - leftoverAmountIn = address(this).balance; - } else { - leftoverAmountIn = IERC20(tokenIn).balanceOf(address(this)); - } - - if (leftoverAmountIn > 0) { - revert TychoRouter__AmountInNotFullySpent(leftoverAmountIn); - } - - if (unwrapEth) { - _unwrapETH(amountOut); - } - if (tokenOut == address(0)) { - Address.sendValue(payable(receiver), amountOut); - } else { - IERC20(tokenOut).safeTransfer(receiver, amountOut); - } - } - /** * @dev Executes sequential swaps as defined by the provided swap graph. * From 09354b501aca7785ee5761627f2dc78cb4bc2120 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 26 Feb 2025 17:47:24 +0000 Subject: [PATCH 09/11] chore(release): 0.52.1 [skip ci] ## [0.52.1](https://github.com/propeller-heads/tycho-execution/compare/0.52.0...0.52.1) (2025-02-26) --- CHANGELOG.md | 2 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2337514..c7ef3b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## [0.52.1](https://github.com/propeller-heads/tycho-execution/compare/0.52.0...0.52.1) (2025-02-26) + ## [0.52.0](https://github.com/propeller-heads/tycho-execution/compare/0.51.2...0.52.0) (2025-02-26) diff --git a/Cargo.lock b/Cargo.lock index a4565d9..fb11e71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4340,7 +4340,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.52.0" +version = "0.52.1" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index c473f1f..62f3f59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.52.0" +version = "0.52.1" edition = "2021" [[bin]] From 7b0dd5872c7bc04790663cc5578e18c2d6889642 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 26 Feb 2025 23:26:27 +0530 Subject: [PATCH 10/11] chore: merge main --- foundry/src/TychoRouter.sol | 119 ++++++------------ foundry/test/Constants.sol | 10 +- foundry/test/TychoRouter.t.sol | 22 ++++ .../test/executors/UniswapV2Executor.t.sol | 17 +++ 4 files changed, 89 insertions(+), 79 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 6f10fed..c29ad40 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -140,27 +140,51 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { uint256 nTokens, address receiver, bytes calldata swaps - ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { + ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { if (receiver == address(0)) { revert TychoRouter__AddressZero(); } - - // Assume funds already in our router. + // Assume funds are already in the router. if (wrapEth) { _wrapETH(amountIn); tokenIn = address(_weth); } - return _executeSwap( - amountIn, - tokenIn, - tokenOut, - minAmountOut, - unwrapEth, - nTokens, - receiver, - swaps - ); + uint256 initialBalance = tokenIn == address(0) + ? address(this).balance + : IERC20(tokenIn).balanceOf(address(this)); + + amountOut = _swap(amountIn, nTokens, swaps); + + uint256 currentBalance = tokenIn == address(0) + ? address(this).balance + : IERC20(tokenIn).balanceOf(address(this)); + + uint256 amountConsumed = initialBalance - currentBalance; + + if (amountConsumed < amountIn) { + uint256 leftoverAmount = amountIn - amountConsumed; + revert TychoRouter__AmountInNotFullySpent(leftoverAmount); + } + + if (fee > 0) { + uint256 feeAmount = (amountOut * fee) / 10000; + amountOut -= feeAmount; + IERC20(tokenOut).safeTransfer(feeReceiver, feeAmount); + } + + if (minAmountOut > 0 && amountOut < minAmountOut) { + revert TychoRouter__NegativeSlippage(amountOut, minAmountOut); + } + + if (unwrapEth) { + _unwrapETH(amountOut); + } + if (tokenOut == address(0)) { + Address.sendValue(payable(receiver), amountOut); + } else { + IERC20(tokenOut).safeTransfer(receiver, amountOut); + } } /** @@ -202,16 +226,9 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature, bytes calldata swaps - ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { - if (receiver == address(0)) { - revert TychoRouter__AddressZero(); - } - + ) external payable whenNotPaused returns (uint256 amountOut) { // For native ETH, assume funds already in our router. Else, transfer and handle approval. - if (wrapEth) { - _wrapETH(amountIn); - tokenIn = address(_weth); - } else if (tokenIn != address(0)) { + if (tokenIn != address(0)) { permit2.permit(msg.sender, permitSingle, signature); permit2.transferFrom( msg.sender, @@ -221,11 +238,12 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { ); } - return _executeSwap( + return swap( amountIn, tokenIn, tokenOut, minAmountOut, + wrapEth, unwrapEth, nTokens, receiver, @@ -233,61 +251,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { ); } - /** - * @notice Internal implementation of the core swap logic shared between swap() and swapPermit2(). - * - * @notice This function centralizes the swap execution logic. - * @notice For detailed documentation on parameters and behavior, see the documentation for - * swap() and swapPermit2() functions. - * - */ - function _executeSwap( - uint256 amountIn, - address tokenIn, - address tokenOut, - uint256 minAmountOut, - bool unwrapEth, - uint256 nTokens, - address receiver, - bytes calldata swaps - ) internal returns (uint256 amountOut) { - uint256 initialBalance = tokenIn == address(0) - ? address(this).balance - : IERC20(tokenIn).balanceOf(address(this)); - - amountOut = _swap(amountIn, nTokens, swaps); - - uint256 currentBalance = tokenIn == address(0) - ? address(this).balance - : IERC20(tokenIn).balanceOf(address(this)); - - uint256 amountConsumed = initialBalance - currentBalance; - - if (amountConsumed < amountIn) { - uint256 leftoverAmount = amountIn - amountConsumed; - revert TychoRouter__AmountInNotFullySpent(leftoverAmount); - } - - if (fee > 0) { - uint256 feeAmount = (amountOut * fee) / 10000; - amountOut -= feeAmount; - IERC20(tokenOut).safeTransfer(feeReceiver, feeAmount); - } - - if (minAmountOut > 0 && amountOut < minAmountOut) { - revert TychoRouter__NegativeSlippage(amountOut, minAmountOut); - } - - if (unwrapEth) { - _unwrapETH(amountOut); - } - if (tokenOut == address(0)) { - Address.sendValue(payable(receiver), amountOut); - } else { - IERC20(tokenOut).safeTransfer(receiver, amountOut); - } - } - /** * @dev Executes sequential swaps as defined by the provided swap graph. * diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index 6675759..2f77465 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -3,7 +3,15 @@ pragma solidity ^0.8.26; import "forge-std/Test.sol"; -contract Constants is Test { +contract BaseConstants { + address BASE_USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; + address BASE_MAG7 = 0x9E6A46f294bB67c20F1D1E7AfB0bBEf614403B55; + + // uniswap v2 + address USDC_MAG7_POOL = 0x739c2431670A12E2cF8e11E3603eB96e6728a789; +} + +contract Constants is Test, BaseConstants { address ADMIN = makeAddr("admin"); //admin=us address BOB = makeAddr("bob"); //bob=someone!=us address FUND_RESCUER = makeAddr("fundRescuer"); diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index e503f7e..6033feb 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -978,4 +978,26 @@ contract TychoRouterTest is TychoRouterTestSetup { assertEq(IERC20(WBTC_ADDR).balanceOf(tychoRouterAddr), 102718); } + + // Base Network Tests + // Make sure to set the RPC_URL to base network + function testSwapSingleBase() public { + vm.skip(true); + vm.rollFork(26857267); + uint256 amountIn = 10 * 10 ** 6; + deal(BASE_USDC, tychoRouterAddr, amountIn); + + bytes memory protocolData = encodeUniswapV2Swap( + BASE_USDC, USDC_MAG7_POOL, tychoRouterAddr, true + ); + + bytes memory swap = encodeSwap( + uint8(0), uint8(1), uint24(0), address(usv2Executor), protocolData + ); + bytes[] memory swaps = new bytes[](1); + swaps[0] = swap; + + tychoRouter.exposedSwap(amountIn, 2, pleEncode(swaps)); + assertGt(IERC20(BASE_MAG7).balanceOf(tychoRouterAddr), 1379830606); + } } diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 01722eb..0b4d705 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -156,4 +156,21 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { vm.expectRevert(UniswapV2Executor__InvalidTarget.selector); uniswapV2Exposed.swap(amountIn, protocolData); } + + // Base Network Tests + // Make sure to set the RPC_URL to base network + function testSwapBaseNetwork() public { + vm.skip(true); + vm.rollFork(26857267); + uint256 amountIn = 10 * 10 ** 6; + bool zeroForOne = true; + bytes memory protocolData = + abi.encodePacked(BASE_USDC, USDC_MAG7_POOL, BOB, zeroForOne); + + deal(BASE_USDC, address(uniswapV2Exposed), amountIn); + + uniswapV2Exposed.swap(amountIn, protocolData); + + assertEq(IERC20(BASE_MAG7).balanceOf(BOB), 1379830606); + } } From d7ddb274973c4b68b412056f81952039a481bd20 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 26 Feb 2025 18:01:34 +0000 Subject: [PATCH 11/11] chore(release): 0.52.2 [skip ci] ## [0.52.2](https://github.com/propeller-heads/tycho-execution/compare/0.52.1...0.52.2) (2025-02-26) --- CHANGELOG.md | 2 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7ef3b8..3b91d38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## [0.52.2](https://github.com/propeller-heads/tycho-execution/compare/0.52.1...0.52.2) (2025-02-26) + ## [0.52.1](https://github.com/propeller-heads/tycho-execution/compare/0.52.0...0.52.1) (2025-02-26) ## [0.52.0](https://github.com/propeller-heads/tycho-execution/compare/0.51.2...0.52.0) (2025-02-26) diff --git a/Cargo.lock b/Cargo.lock index fb11e71..3e55963 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4340,7 +4340,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.52.1" +version = "0.52.2" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 62f3f59..63c5594 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.52.1" +version = "0.52.2" edition = "2021" [[bin]]