feat: Assume that funds will never go straight from a pool to the receiver

- The funds will always go through the router
- Rename splitSwap for Swap
- Improve tests

--- don't change below this line ---
ENG-4041 Took 24 minutes


Took 23 seconds

Took 42 seconds
This commit is contained in:
Diana Carvalho
2025-01-28 16:13:24 +00:00
parent dfa7033d2e
commit 655cf91984
6 changed files with 69 additions and 63 deletions

View File

@@ -143,15 +143,12 @@ contract TychoRouter is
); );
} }
amountOut = _splitSwap(amountIn, nTokens, swaps); amountOut = _swap(amountIn, nTokens, swaps);
if (fee > 0) { if (fee > 0) {
uint256 feeAmount = (amountOut * fee) / 10000; uint256 feeAmount = (amountOut * fee) / 10000;
amountOut -= feeAmount; amountOut -= feeAmount;
IERC20(tokenOut).safeTransfer(feeReceiver, feeAmount); IERC20(tokenOut).safeTransfer(feeReceiver, feeAmount);
if (!unwrapEth) {
IERC20(tokenOut).safeTransfer(receiver, amountOut);
}
} }
if (minAmountOut > 0 && amountOut < minAmountOut) { if (minAmountOut > 0 && amountOut < minAmountOut) {
@@ -162,14 +159,15 @@ contract TychoRouter is
_unwrapETH(amountOut); _unwrapETH(amountOut);
// slither-disable-next-line arbitrary-send-eth // slither-disable-next-line arbitrary-send-eth
payable(receiver).transfer(amountOut); payable(receiver).transfer(amountOut);
} else {
IERC20(tokenOut).safeTransfer(receiver, amountOut);
} }
} }
function _splitSwap( function _swap(uint256 amountIn, uint256 nTokens, bytes calldata swaps_)
uint256 amountIn, internal
uint256 nTokens, returns (uint256)
bytes calldata swaps_ {
) internal returns (uint256) {
uint256 currentAmountIn; uint256 currentAmountIn;
uint256 currentAmountOut; uint256 currentAmountOut;
uint8 tokenInIndex; uint8 tokenInIndex;

View File

@@ -72,19 +72,19 @@ contract TychoRouterTest is TychoRouterTestSetup {
function testWithdrawNative() public { function testWithdrawNative() public {
vm.startPrank(FUND_RESCUER); vm.startPrank(FUND_RESCUER);
// Send 100 ether to tychoRouter // Send 100 ether to tychoRouter
assertEq(address(tychoRouter).balance, 0); assertEq(tychoRouterAddr.balance, 0);
assertEq(FUND_RESCUER.balance, 0); assertEq(FUND_RESCUER.balance, 0);
vm.deal(address(tychoRouter), 100 ether); vm.deal(tychoRouterAddr, 100 ether);
vm.expectEmit(); vm.expectEmit();
emit Withdrawal(address(0), 100 ether, FUND_RESCUER); emit Withdrawal(address(0), 100 ether, FUND_RESCUER);
tychoRouter.withdrawNative(FUND_RESCUER); tychoRouter.withdrawNative(FUND_RESCUER);
assertEq(address(tychoRouter).balance, 0); assertEq(tychoRouterAddr.balance, 0);
assertEq(FUND_RESCUER.balance, 100 ether); assertEq(FUND_RESCUER.balance, 100 ether);
vm.stopPrank(); vm.stopPrank();
} }
function testWithdrawNativeFailures() public { function testWithdrawNativeFailures() public {
vm.deal(address(tychoRouter), 100 ether); vm.deal(tychoRouterAddr, 100 ether);
vm.startPrank(FUND_RESCUER); vm.startPrank(FUND_RESCUER);
vm.expectRevert(TychoRouter__AddressZero.selector); vm.expectRevert(TychoRouter__AddressZero.selector);
tychoRouter.withdrawNative(address(0)); tychoRouter.withdrawNative(address(0));
@@ -99,7 +99,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
function testWithdrawERC20Tokens() public { function testWithdrawERC20Tokens() public {
vm.startPrank(BOB); vm.startPrank(BOB);
mintTokens(100 ether, address(tychoRouter)); mintTokens(100 ether, tychoRouterAddr);
vm.stopPrank(); vm.stopPrank();
vm.startPrank(FUND_RESCUER); vm.startPrank(FUND_RESCUER);
@@ -112,7 +112,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
// Check balances after withdrawing // Check balances after withdrawing
for (uint256 i = 0; i < tokens.length; i++) { for (uint256 i = 0; i < tokens.length; i++) {
// slither-disable-next-line calls-loop // slither-disable-next-line calls-loop
assertEq(tokens[i].balanceOf(address(tychoRouter)), 0); assertEq(tokens[i].balanceOf(tychoRouterAddr), 0);
// slither-disable-next-line calls-loop // slither-disable-next-line calls-loop
assertEq(tokens[i].balanceOf(FUND_RESCUER), 100 ether); assertEq(tokens[i].balanceOf(FUND_RESCUER), 100 ether);
} }
@@ -120,7 +120,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
} }
function testWithdrawERC20TokensFailures() public { function testWithdrawERC20TokensFailures() public {
mintTokens(100 ether, address(tychoRouter)); mintTokens(100 ether, tychoRouterAddr);
IERC20[] memory tokensArray = new IERC20[](3); IERC20[] memory tokensArray = new IERC20[](3);
tokensArray[0] = IERC20(address(tokens[0])); tokensArray[0] = IERC20(address(tokens[0]));
tokensArray[1] = IERC20(address(tokens[1])); tokensArray[1] = IERC20(address(tokens[1]));
@@ -198,29 +198,30 @@ contract TychoRouterTest is TychoRouterTestSetup {
tychoRouter.wrapETH{value: amount}(amount); tychoRouter.wrapETH{value: amount}(amount);
vm.stopPrank(); vm.stopPrank();
assertEq(address(tychoRouter).balance, 0); assertEq(tychoRouterAddr.balance, 0);
assertEq(IERC20(WETH_ADDR).balanceOf(address(tychoRouter)), amount); assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), amount);
} }
function testUnwrapETH() public { function testUnwrapETH() public {
uint256 amount = 1 ether; uint256 amount = 1 ether;
deal(WETH_ADDR, address(tychoRouter), amount); deal(WETH_ADDR, tychoRouterAddr, amount);
tychoRouter.unwrapETH(amount); tychoRouter.unwrapETH(amount);
assertEq(address(tychoRouter).balance, amount); assertEq(tychoRouterAddr.balance, amount);
assertEq(IERC20(WETH_ADDR).balanceOf(address(tychoRouter)), 0); assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
} }
function testSplitSwapSimple() public { function testSwapSimple() public {
// Trade 1 WETH for DAI with 1 swap on Uniswap V2 // Trade 1 WETH for DAI with 1 swap on Uniswap V2
// 1 WETH -> DAI // 1 WETH -> DAI
// (univ2) // (univ2)
uint256 amountIn = 1 ether; uint256 amountIn = 1 ether;
deal(WETH_ADDR, address(tychoRouter), amountIn); deal(WETH_ADDR, tychoRouterAddr, amountIn);
bytes memory protocolData = bytes memory protocolData = encodeUniswapV2Swap(
encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false); WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
);
bytes memory swap = encodeSwap( bytes memory swap = encodeSwap(
uint8(0), uint8(0),
@@ -233,19 +234,19 @@ contract TychoRouterTest is TychoRouterTestSetup {
bytes[] memory swaps = new bytes[](1); bytes[] memory swaps = new bytes[](1);
swaps[0] = swap; swaps[0] = swap;
tychoRouter.splitSwap(amountIn, 2, pleEncode(swaps)); tychoRouter.ExposedSwap(amountIn, 2, pleEncode(swaps));
uint256 daiBalance = IERC20(DAI_ADDR).balanceOf(ALICE); uint256 daiBalance = IERC20(DAI_ADDR).balanceOf(tychoRouterAddr);
assertEq(daiBalance, 2630432278145144658455); assertEq(daiBalance, 2630432278145144658455);
assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 0); assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
} }
function testSplitSwapMultipleHops() public { function testSwapMultipleHops() public {
// Trade 1 WETH for USDC through DAI with 2 swaps on Uniswap V2 // Trade 1 WETH for USDC through DAI with 2 swaps on Uniswap V2
// 1 WETH -> DAI -> USDC // 1 WETH -> DAI -> USDC
// (univ2) (univ2) // (univ2) (univ2)
uint256 amountIn = 1 ether; uint256 amountIn = 1 ether;
deal(WETH_ADDR, address(tychoRouter), amountIn); deal(WETH_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = new bytes[](2); bytes[] memory swaps = new bytes[](2);
// WETH -> DAI // WETH -> DAI
@@ -256,7 +257,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
address(usv2Executor), address(usv2Executor),
bytes4(0), bytes4(0),
encodeUniswapV2Swap( encodeUniswapV2Swap(
WETH_ADDR, WETH_DAI_POOL, address(tychoRouter), false WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
) )
); );
@@ -267,24 +268,24 @@ contract TychoRouterTest is TychoRouterTestSetup {
uint24(0), uint24(0),
address(usv2Executor), address(usv2Executor),
bytes4(0), bytes4(0),
encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, ALICE, true) encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, tychoRouterAddr, true)
); );
tychoRouter.splitSwap(amountIn, 3, pleEncode(swaps)); tychoRouter.ExposedSwap(amountIn, 3, pleEncode(swaps));
uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(ALICE); uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(tychoRouterAddr);
assertEq(usdcBalance, 2610580090); assertEq(usdcBalance, 2610580090);
assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 0); assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
} }
function testSplitSwapSplitHops() public { function testSwapSplitHops() public {
// Trade 1 WETH for USDC through DAI and WBTC with 4 swaps on Uniswap V2 // Trade 1 WETH for USDC through DAI and WBTC with 4 swaps on Uniswap V2
// -> DAI -> // -> DAI ->
// 1 WETH USDC // 1 WETH USDC
// -> WBTC -> // -> WBTC ->
// (univ2) (univ2) // (univ2) (univ2)
uint256 amountIn = 1 ether; uint256 amountIn = 1 ether;
deal(WETH_ADDR, address(tychoRouter), amountIn); deal(WETH_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = new bytes[](4); bytes[] memory swaps = new bytes[](4);
// WETH -> WBTC (60%) // WETH -> WBTC (60%)
@@ -295,7 +296,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
address(usv2Executor), address(usv2Executor),
bytes4(0), bytes4(0),
encodeUniswapV2Swap( encodeUniswapV2Swap(
WETH_ADDR, WETH_WBTC_POOL, address(tychoRouter), false WETH_ADDR, WETH_WBTC_POOL, tychoRouterAddr, false
) )
); );
// WBTC -> USDC // WBTC -> USDC
@@ -305,7 +306,9 @@ contract TychoRouterTest is TychoRouterTestSetup {
uint24(0), uint24(0),
address(usv2Executor), address(usv2Executor),
bytes4(0), bytes4(0),
encodeUniswapV2Swap(WBTC_ADDR, USDC_WBTC_POOL, ALICE, true) encodeUniswapV2Swap(
WBTC_ADDR, USDC_WBTC_POOL, tychoRouterAddr, true
)
); );
// WETH -> DAI // WETH -> DAI
swaps[2] = encodeSwap( swaps[2] = encodeSwap(
@@ -315,7 +318,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
address(usv2Executor), address(usv2Executor),
bytes4(0), bytes4(0),
encodeUniswapV2Swap( encodeUniswapV2Swap(
WETH_ADDR, WETH_DAI_POOL, address(tychoRouter), false WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
) )
); );
@@ -326,14 +329,14 @@ contract TychoRouterTest is TychoRouterTestSetup {
uint24(0), uint24(0),
address(usv2Executor), address(usv2Executor),
bytes4(0), bytes4(0),
encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, ALICE, true) encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, tychoRouterAddr, true)
); );
tychoRouter.splitSwap(amountIn, 4, pleEncode(swaps)); tychoRouter.ExposedSwap(amountIn, 4, pleEncode(swaps));
uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(ALICE); uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(tychoRouterAddr);
assertEq(usdcBalance, 2581503157); assertEq(usdcBalance, 2581503157);
assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 0); assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
} }
function testSwapChecked() public { function testSwapChecked() public {
@@ -350,8 +353,9 @@ contract TychoRouterTest is TychoRouterTestSetup {
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, amountIn); ) = handlePermit2Approval(WETH_ADDR, amountIn);
bytes memory protocolData = bytes memory protocolData = encodeUniswapV2Swap(
encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false); WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
);
bytes memory swap = encodeSwap( bytes memory swap = encodeSwap(
uint8(0), uint8(0),
@@ -402,8 +406,9 @@ contract TychoRouterTest is TychoRouterTestSetup {
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, amountIn); ) = handlePermit2Approval(WETH_ADDR, amountIn);
bytes memory protocolData = bytes memory protocolData = encodeUniswapV2Swap(
encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false); WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
);
bytes memory swap = encodeSwap( bytes memory swap = encodeSwap(
uint8(0), uint8(0),
@@ -461,7 +466,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
) = handlePermit2Approval(WETH_ADDR, amountIn); ) = handlePermit2Approval(WETH_ADDR, amountIn);
bytes memory protocolData = encodeUniswapV2Swap( bytes memory protocolData = encodeUniswapV2Swap(
WETH_ADDR, WETH_DAI_POOL, address(tychoRouter), false WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
); );
bytes memory swap = encodeSwap( bytes memory swap = encodeSwap(
@@ -517,8 +522,9 @@ contract TychoRouterTest is TychoRouterTestSetup {
spender: address(0), spender: address(0),
sigDeadline: 0 sigDeadline: 0
}); });
bytes memory protocolData = bytes memory protocolData = encodeUniswapV2Swap(
encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false); WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
);
bytes memory swap = encodeSwap( bytes memory swap = encodeSwap(
uint8(0), uint8(0),
@@ -566,9 +572,8 @@ contract TychoRouterTest is TychoRouterTestSetup {
bytes memory signature bytes memory signature
) = handlePermit2Approval(DAI_ADDR, amountIn); ) = handlePermit2Approval(DAI_ADDR, amountIn);
bytes memory protocolData = encodeUniswapV2Swap( bytes memory protocolData =
DAI_ADDR, WETH_DAI_POOL, address(tychoRouter), true encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true);
);
bytes memory swap = encodeSwap( bytes memory swap = encodeSwap(
uint8(0), uint8(0),

View File

@@ -18,16 +18,18 @@ contract TychoRouterExposed is TychoRouter {
return _unwrapETH(amount); return _unwrapETH(amount);
} }
function splitSwap(uint256 amountIn, uint256 nTokens, bytes calldata swaps) function ExposedSwap(
external uint256 amountIn,
returns (uint256) uint256 nTokens,
{ bytes calldata swaps
return _splitSwap(amountIn, nTokens, swaps); ) external returns (uint256) {
return _swap(amountIn, nTokens, swaps);
} }
} }
contract TychoRouterTestSetup is Test, Constants { contract TychoRouterTestSetup is Test, Constants {
TychoRouterExposed tychoRouter; TychoRouterExposed tychoRouter;
address tychoRouterAddr;
address permit2Address = address(0x000000000022D473030F116dDEE9F6B43aC78BA3); address permit2Address = address(0x000000000022D473030F116dDEE9F6B43aC78BA3);
UniswapV2Executor public usv2Executor; UniswapV2Executor public usv2Executor;
MockERC20[] tokens; MockERC20[] tokens;
@@ -38,6 +40,7 @@ contract TychoRouterTestSetup is Test, Constants {
vm.startPrank(ADMIN); vm.startPrank(ADMIN);
tychoRouter = new TychoRouterExposed(permit2Address, WETH_ADDR); tychoRouter = new TychoRouterExposed(permit2Address, WETH_ADDR);
tychoRouterAddr = address(tychoRouter);
tychoRouter.grantRole(keccak256("FUND_RESCUER_ROLE"), FUND_RESCUER); tychoRouter.grantRole(keccak256("FUND_RESCUER_ROLE"), FUND_RESCUER);
tychoRouter.grantRole(keccak256("FEE_SETTER_ROLE"), FEE_SETTER); tychoRouter.grantRole(keccak256("FEE_SETTER_ROLE"), FEE_SETTER);
tychoRouter.grantRole(keccak256("PAUSER_ROLE"), PAUSER); tychoRouter.grantRole(keccak256("PAUSER_ROLE"), PAUSER);
@@ -98,7 +101,7 @@ contract TychoRouterTestSetup is Test, Constants {
expiration: uint48(block.timestamp + 1 days), expiration: uint48(block.timestamp + 1 days),
nonce: 0 nonce: 0
}), }),
spender: address(tychoRouter), spender: tychoRouterAddr,
sigDeadline: block.timestamp + 1 days sigDeadline: block.timestamp + 1 days
}); });