328 lines
12 KiB
Solidity
328 lines
12 KiB
Solidity
// SPDX-License-Identifier: BUSL-1.1
|
|
pragma solidity ^0.8.26;
|
|
|
|
import "@src/executors/UniswapV4Executor.sol";
|
|
import {TychoRouter} from "@src/TychoRouter.sol";
|
|
import "./TychoRouterTestSetup.sol";
|
|
import "./executors/UniswapV4Utils.sol";
|
|
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
|
|
|
|
contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
|
|
function testSingleSwapPermit2() public {
|
|
// Trade 1 WETH for DAI with 1 swap on Uniswap V2 using Permit2
|
|
// 1 WETH -> DAI
|
|
// (USV2)
|
|
vm.startPrank(ALICE);
|
|
|
|
uint256 amountIn = 1 ether;
|
|
deal(WETH_ADDR, ALICE, amountIn);
|
|
(
|
|
IAllowanceTransfer.PermitSingle memory permitSingle,
|
|
bytes memory signature
|
|
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
|
|
|
|
bytes memory protocolData = encodeUniswapV2Swap(
|
|
WETH_ADDR,
|
|
WETH_DAI_POOL,
|
|
tychoRouterAddr,
|
|
false,
|
|
TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL
|
|
);
|
|
|
|
bytes memory swap =
|
|
encodeSingleSwap(address(usv2Executor), protocolData);
|
|
|
|
tychoRouter.singleSwapPermit2(
|
|
amountIn,
|
|
WETH_ADDR,
|
|
DAI_ADDR,
|
|
2659881924818443699786,
|
|
false,
|
|
false,
|
|
ALICE,
|
|
permitSingle,
|
|
signature,
|
|
swap
|
|
);
|
|
|
|
uint256 daiBalance = IERC20(DAI_ADDR).balanceOf(ALICE);
|
|
assertEq(daiBalance, 2659881924818443699787);
|
|
assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 0);
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testSingleSwapNoPermit2() public {
|
|
// Trade 1 WETH for DAI with 1 swap on Uniswap V2
|
|
// Checks amount out at the end
|
|
uint256 amountIn = 1 ether;
|
|
|
|
deal(WETH_ADDR, ALICE, amountIn);
|
|
vm.startPrank(ALICE);
|
|
// Approve the tokenIn to be transferred to the router
|
|
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn);
|
|
|
|
bytes memory protocolData = encodeUniswapV2Swap(
|
|
WETH_ADDR,
|
|
WETH_DAI_POOL,
|
|
tychoRouterAddr,
|
|
false,
|
|
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL
|
|
);
|
|
|
|
bytes memory swap =
|
|
encodeSingleSwap(address(usv2Executor), protocolData);
|
|
|
|
uint256 minAmountOut = 2600 * 1e18;
|
|
uint256 amountOut = tychoRouter.singleSwap(
|
|
amountIn,
|
|
WETH_ADDR,
|
|
DAI_ADDR,
|
|
minAmountOut,
|
|
false,
|
|
false,
|
|
ALICE,
|
|
swap
|
|
);
|
|
|
|
uint256 expectedAmount = 2659881924818443699787;
|
|
assertEq(amountOut, expectedAmount);
|
|
uint256 daiBalance = IERC20(DAI_ADDR).balanceOf(ALICE);
|
|
assertEq(daiBalance, expectedAmount);
|
|
assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 0);
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testSingleSwapUndefinedMinAmount() public {
|
|
// Trade 1 WETH for DAI with 1 swap on Uniswap V2
|
|
// Checks amount out at the end
|
|
uint256 amountIn = 1 ether;
|
|
|
|
deal(WETH_ADDR, ALICE, amountIn);
|
|
vm.startPrank(ALICE);
|
|
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn);
|
|
|
|
bytes memory protocolData = encodeUniswapV2Swap(
|
|
WETH_ADDR,
|
|
WETH_DAI_POOL,
|
|
tychoRouterAddr,
|
|
false,
|
|
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL
|
|
);
|
|
|
|
bytes memory swap =
|
|
encodeSingleSwap(address(usv2Executor), protocolData);
|
|
|
|
vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector);
|
|
tychoRouter.singleSwap(
|
|
amountIn, WETH_ADDR, DAI_ADDR, 0, false, false, ALICE, swap
|
|
);
|
|
}
|
|
|
|
function testSingleSwapInsufficientApproval() public {
|
|
// Trade 1 WETH for DAI with 1 swap on Uniswap V2
|
|
// Checks amount out at the end
|
|
uint256 amountIn = 1 ether;
|
|
|
|
deal(WETH_ADDR, ALICE, amountIn);
|
|
vm.startPrank(ALICE);
|
|
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn - 1);
|
|
|
|
bytes memory protocolData = encodeUniswapV2Swap(
|
|
WETH_ADDR,
|
|
WETH_DAI_POOL,
|
|
tychoRouterAddr,
|
|
false,
|
|
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL
|
|
);
|
|
|
|
bytes memory swap =
|
|
encodeSingleSwap(address(usv2Executor), protocolData);
|
|
|
|
uint256 minAmountOut = 2600 * 1e18;
|
|
vm.expectRevert();
|
|
tychoRouter.singleSwap(
|
|
amountIn,
|
|
WETH_ADDR,
|
|
DAI_ADDR,
|
|
minAmountOut,
|
|
false,
|
|
false,
|
|
ALICE,
|
|
swap
|
|
);
|
|
}
|
|
|
|
function testSingleSwapNegativeSlippageFailure() public {
|
|
// Trade 1 WETH for DAI with 1 swap on Uniswap V2
|
|
// Checks amount out at the end
|
|
uint256 amountIn = 1 ether;
|
|
|
|
deal(WETH_ADDR, ALICE, amountIn);
|
|
vm.startPrank(ALICE);
|
|
// Approve the tokenIn to be transferred to the router
|
|
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn);
|
|
|
|
bytes memory protocolData = encodeUniswapV2Swap(
|
|
WETH_ADDR,
|
|
WETH_DAI_POOL,
|
|
tychoRouterAddr,
|
|
false,
|
|
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL
|
|
);
|
|
|
|
bytes memory swap =
|
|
encodeSingleSwap(address(usv2Executor), protocolData);
|
|
|
|
uint256 minAmountOut = 5600 * 1e18;
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(
|
|
TychoRouter__NegativeSlippage.selector,
|
|
2659881924818443699787, // actual amountOut
|
|
minAmountOut
|
|
)
|
|
);
|
|
tychoRouter.singleSwap(
|
|
amountIn,
|
|
WETH_ADDR,
|
|
DAI_ADDR,
|
|
minAmountOut,
|
|
false,
|
|
false,
|
|
ALICE,
|
|
swap
|
|
);
|
|
}
|
|
|
|
function testSingleSwapWrapETH() public {
|
|
uint256 amountIn = 1 ether;
|
|
deal(ALICE, amountIn);
|
|
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,
|
|
tychoRouterAddr,
|
|
false,
|
|
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
|
);
|
|
|
|
bytes memory swap =
|
|
encodeSingleSwap(address(usv2Executor), protocolData);
|
|
|
|
uint256 amountOut = tychoRouter.singleSwapPermit2{value: amountIn}(
|
|
amountIn,
|
|
address(0),
|
|
DAI_ADDR,
|
|
1000_000000,
|
|
true,
|
|
false,
|
|
ALICE,
|
|
emptyPermitSingle,
|
|
"",
|
|
swap
|
|
);
|
|
uint256 expectedAmount = 2659881924818443699787;
|
|
assertEq(amountOut, expectedAmount);
|
|
uint256 daiBalance = IERC20(DAI_ADDR).balanceOf(ALICE);
|
|
assertEq(daiBalance, expectedAmount);
|
|
assertEq(ALICE.balance, 0);
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testSingleSwapUnwrapETH() public {
|
|
// DAI -> WETH with unwrapping to ETH
|
|
uint256 amountIn = 3000 ether;
|
|
deal(DAI_ADDR, ALICE, amountIn);
|
|
|
|
vm.startPrank(ALICE);
|
|
(
|
|
IAllowanceTransfer.PermitSingle memory permitSingle,
|
|
bytes memory signature
|
|
) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn);
|
|
|
|
bytes memory protocolData = encodeUniswapV2Swap(
|
|
DAI_ADDR,
|
|
WETH_DAI_POOL,
|
|
tychoRouterAddr,
|
|
true,
|
|
TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL
|
|
);
|
|
|
|
bytes memory swap =
|
|
encodeSingleSwap(address(usv2Executor), protocolData);
|
|
|
|
uint256 amountOut = tychoRouter.singleSwapPermit2(
|
|
amountIn,
|
|
DAI_ADDR,
|
|
address(0),
|
|
1000_000000,
|
|
false,
|
|
true,
|
|
ALICE,
|
|
permitSingle,
|
|
signature,
|
|
swap
|
|
);
|
|
|
|
uint256 expectedAmount = 1120007305574805922;
|
|
assertEq(amountOut, expectedAmount);
|
|
assertEq(ALICE.balance, expectedAmount);
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testSingleSwapIntegration() public {
|
|
// Tests swapping WETH -> DAI on a USV2 pool with regular approvals
|
|
deal(WETH_ADDR, ALICE, 1 ether);
|
|
uint256 balancerBefore = IERC20(DAI_ADDR).balanceOf(ALICE);
|
|
|
|
vm.startPrank(ALICE);
|
|
IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
|
// Encoded solution generated using `test_single_swap_strategy_encoder_no_permit2`
|
|
(bool success,) = tychoRouterAddr.call(
|
|
hex"20144a070000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae3740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500010000000000000000000000000000"
|
|
);
|
|
|
|
vm.stopPrank();
|
|
|
|
uint256 balancerAfter = IERC20(DAI_ADDR).balanceOf(ALICE);
|
|
assertTrue(success, "Call Failed");
|
|
assertEq(balancerAfter - balancerBefore, 2659881924818443699787);
|
|
}
|
|
|
|
function testSingleSwapIntegrationPermit2() public {
|
|
// Tests swapping WETH -> DAI on a USV2 pool with permit2
|
|
deal(WETH_ADDR, ALICE, 1 ether);
|
|
uint256 balancerBefore = IERC20(DAI_ADDR).balanceOf(ALICE);
|
|
|
|
vm.startPrank(ALICE);
|
|
IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
|
// Encoded solution generated using `test_single_swap_strategy_encoder`
|
|
(bool success,) = tychoRouterAddr.call(
|
|
hex"30ace1b10000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000682169c100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f9e3c900000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000412d3f0fee3fc61987512f024f20b1448eb934f82105a91653dd169179c693aaf95d09ef666ce1d38be70b8156fa6e4ea3e8717204e02fe7ba99a1fc4e5a26e6e11b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500020000000000000000000000000000"
|
|
);
|
|
|
|
vm.stopPrank();
|
|
|
|
uint256 balancerAfter = IERC20(DAI_ADDR).balanceOf(ALICE);
|
|
assertTrue(success, "Call Failed");
|
|
assertEq(balancerAfter - balancerBefore, 2659881924818443699787);
|
|
}
|
|
}
|