Merge branch 'refs/heads/main' into feat/bebop-rfq-encoder-and-executor

# Conflicts:
#	foundry/test/TychoRouterProtocolIntegration.t.sol
#	foundry/test/TychoRouterTestSetup.sol
#	foundry/test/assets/calldata.txt
#	foundry/test/protocols/BebopExecutor.t.sol
#	src/encoding/evm/tycho_encoders.rs

Took 4 minutes
This commit is contained in:
Diana Carvalho
2025-06-24 10:17:33 +01:00
40 changed files with 3218 additions and 991 deletions

View File

@@ -1,11 +1,8 @@
// 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 TychoRouterTest is TychoRouterTestSetup {
bytes32 public constant EXECUTOR_SETTER_ROLE =
@@ -91,16 +88,16 @@ contract TychoRouterTest is TychoRouterTestSetup {
}
function testWithdrawERC20Tokens() public {
vm.startPrank(BOB);
mintTokens(100 ether, tychoRouterAddr);
vm.stopPrank();
IERC20[] memory tokens = new IERC20[](2);
tokens[0] = IERC20(WETH_ADDR);
tokens[1] = IERC20(USDC_ADDR);
for (uint256 i = 0; i < tokens.length; i++) {
deal(address(tokens[i]), tychoRouterAddr, 100 ether);
}
vm.startPrank(FUND_RESCUER);
IERC20[] memory tokensArray = new IERC20[](3);
tokensArray[0] = IERC20(address(tokens[0]));
tokensArray[1] = IERC20(address(tokens[1]));
tokensArray[2] = IERC20(address(tokens[2]));
tychoRouter.withdraw(tokensArray, FUND_RESCUER);
tychoRouter.withdraw(tokens, FUND_RESCUER);
// Check balances after withdrawing
for (uint256 i = 0; i < tokens.length; i++) {
@@ -113,21 +110,22 @@ contract TychoRouterTest is TychoRouterTestSetup {
}
function testWithdrawERC20TokensFailures() public {
mintTokens(100 ether, tychoRouterAddr);
IERC20[] memory tokensArray = new IERC20[](3);
tokensArray[0] = IERC20(address(tokens[0]));
tokensArray[1] = IERC20(address(tokens[1]));
tokensArray[2] = IERC20(address(tokens[2]));
IERC20[] memory tokens = new IERC20[](2);
tokens[0] = IERC20(WETH_ADDR);
tokens[1] = IERC20(USDC_ADDR);
for (uint256 i = 0; i < tokens.length; i++) {
deal(address(tokens[i]), tychoRouterAddr, 100 ether);
}
vm.startPrank(FUND_RESCUER);
vm.expectRevert(TychoRouter__AddressZero.selector);
tychoRouter.withdraw(tokensArray, address(0));
tychoRouter.withdraw(tokens, address(0));
vm.stopPrank();
// Not role FUND_RESCUER
vm.startPrank(BOB);
vm.expectRevert();
tychoRouter.withdraw(tokensArray, FUND_RESCUER);
tychoRouter.withdraw(tokens, FUND_RESCUER);
vm.stopPrank();
}

View File

@@ -2,131 +2,10 @@
pragma solidity ^0.8.26;
import "./TychoRouterTestSetup.sol";
import "./executors/UniswapV4Utils.sol";
import "./protocols/UniswapV4Utils.sol";
import "@src/executors/BebopExecutor.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,
RestrictTransferFrom.TransferType.TransferFrom,
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, ALICE, 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,
RestrictTransferFrom.TransferType.TransferFrom,
ALICE,
pools
);
bytes memory swap =
encodeSingleSwap(address(usv4Executor), protocolData);
vm.startPrank(ALICE);
IERC20(USDE_ADDR).approve(tychoRouterAddr, amountIn);
tychoRouter.singleSwap(
amountIn,
USDE_ADDR,
WBTC_ADDR,
118280,
false,
false,
ALICE,
true,
swap
);
assertEq(IERC20(WBTC_ADDR).balanceOf(ALICE), 118281);
}
function testSingleUSV4IntegrationGroupedSwap() public {
// Test created with calldata from our router encoder.
// Performs a single swap from USDC to PEPE though ETH using two
// consecutive USV4 pools. It's a single swap because it is a consecutive grouped swaps
//
// USDC ──(USV4)──> ETH ───(USV4)──> PEPE
//
deal(USDC_ADDR, ALICE, 1 ether);
uint256 balanceBefore = IERC20(PEPE_ADDR).balanceOf(ALICE);
// Approve permit2
vm.startPrank(ALICE);
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
bytes memory callData = loadCallDataFromFile(
"test_single_encoding_strategy_usv4_grouped_swap"
);
(bool success,) = tychoRouterAddr.call(callData);
vm.stopPrank();
uint256 balanceAfter = IERC20(PEPE_ADDR).balanceOf(ALICE);
assertTrue(success, "Call Failed");
assertEq(balanceAfter - balanceBefore, 123172000092711286554274694);
}
function testMultiProtocolIntegration() public {
// Test created with calldata from our router encoder.
//
@@ -171,130 +50,6 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
assertEq(balanceAfter - balanceBefore, 235610487387677804636755778);
}
function testSingleUSV4IntegrationOutputETH() public {
// Test created with calldata from our router encoder.
// Performs a single swap from USDC to ETH without wrapping or unwrapping
//
// USDC ───(USV4)──> ETH
//
deal(USDC_ADDR, ALICE, 3000_000000);
uint256 balanceBefore = ALICE.balance;
// Approve permit2
vm.startPrank(ALICE);
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
bytes memory callData =
loadCallDataFromFile("test_single_encoding_strategy_usv4_eth_out");
(bool success,) = tychoRouterAddr.call(callData);
vm.stopPrank();
uint256 balanceAfter = ALICE.balance;
assertTrue(success, "Call Failed");
console.logUint(balanceAfter - balanceBefore);
assertEq(balanceAfter - balanceBefore, 1474406268748155809);
}
function testSingleMaverickIntegration() public {
deal(GHO_ADDR, ALICE, 1 ether);
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
vm.startPrank(ALICE);
IERC20(GHO_ADDR).approve(tychoRouterAddr, type(uint256).max);
bytes memory callData =
loadCallDataFromFile("test_single_encoding_strategy_maverick");
(bool success,) = tychoRouterAddr.call(callData);
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
assertTrue(success, "Call Failed");
assertGe(balanceAfter - balanceBefore, 999725);
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
}
function testSingleEkuboIntegration() public {
vm.stopPrank();
deal(ALICE, 1 ether);
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
// Approve permit2
vm.startPrank(ALICE);
bytes memory callData =
loadCallDataFromFile("test_single_encoding_strategy_ekubo");
(bool success,) = tychoRouterAddr.call{value: 1 ether}(callData);
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
assertTrue(success, "Call Failed");
assertGe(balanceAfter - balanceBefore, 26173932);
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
}
function testSingleCurveIntegration() public {
deal(UWU_ADDR, ALICE, 1 ether);
vm.startPrank(ALICE);
IERC20(UWU_ADDR).approve(tychoRouterAddr, type(uint256).max);
bytes memory callData =
loadCallDataFromFile("test_single_encoding_strategy_curve");
(bool success,) = tychoRouterAddr.call(callData);
assertTrue(success, "Call Failed");
assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 2877855391767);
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,
RestrictTransferFrom.TransferType.TransferFrom
);
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();
}
function testSingleBebopIntegration() public {
// The calldata swaps 200 USDC for ONDO
// The receiver in the order is 0xc5564C13A157E6240659fb81882A28091add8670

View File

@@ -4,8 +4,6 @@ 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 TychoRouterSequentialSwapTest is TychoRouterTestSetup {
function _getSequentialSwaps() internal view returns (bytes[] memory) {

View File

@@ -4,8 +4,6 @@ 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 {

View File

@@ -4,8 +4,6 @@ pragma solidity ^0.8.26;
import "@src/executors/UniswapV4Executor.sol";
import {TychoRouter, RestrictTransferFrom} from "@src/TychoRouter.sol";
import "./TychoRouterTestSetup.sol";
import "./executors/UniswapV4Utils.sol";
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
function _getSplitSwaps(bool transferFrom)

View File

@@ -1,23 +1,28 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import "../src/executors/BalancerV2Executor.sol";
import "../src/executors/CurveExecutor.sol";
import "../src/executors/EkuboExecutor.sol";
import "../src/executors/UniswapV2Executor.sol";
import "../src/executors/UniswapV3Executor.sol";
import "../src/executors/UniswapV4Executor.sol";
// Executors
import {BalancerV2Executor} from "../src/executors/BalancerV2Executor.sol";
import {BalancerV3Executor} from "../src/executors/BalancerV3Executor.sol";
import {CurveExecutor} from "../src/executors/CurveExecutor.sol";
import {EkuboExecutor} from "../src/executors/EkuboExecutor.sol";
import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol";
import {UniswapV2Executor} from "../src/executors/UniswapV2Executor.sol";
import {
UniswapV3Executor,
IUniswapV3Pool
} from "../src/executors/UniswapV3Executor.sol";
import {UniswapV4Executor} from "../src/executors/UniswapV4Executor.sol";
import {BebopExecutorHarness} from "./executors/BebopExecutor.t.sol";
// Test utilities and mocks
import "./Constants.sol";
import "./mock/MockERC20.sol";
import "./TestUtils.sol";
import {Permit2TestHelper} from "./Permit2TestHelper.sol";
// Core contracts and interfaces
import "@src/TychoRouter.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol";
import {WETH} from "../lib/permit2/lib/solmate/src/tokens/WETH.sol";
import {Permit2TestHelper} from "./Permit2TestHelper.sol";
import "./TestUtils.sol";
import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol";
import {BalancerV3Executor} from "../src/executors/BalancerV3Executor.sol";
import {BebopExecutorHarness} from "./executors/BebopExecutor.t.sol";
contract TychoRouterExposed is TychoRouter {
constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {}
@@ -70,7 +75,6 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
MaverickV2Executor public maverickv2Executor;
BalancerV3Executor public balancerV3Executor;
BebopExecutorHarness public bebopExecutor;
MockERC20[] tokens;
function getForkBlock() public view virtual returns (uint256) {
return 22082754;
@@ -89,12 +93,6 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
vm.startPrank(EXECUTOR_SETTER);
tychoRouter.setExecutors(executors);
vm.stopPrank();
vm.startPrank(BOB);
tokens.push(new MockERC20("Token A", "A"));
tokens.push(new MockERC20("Token B", "B"));
tokens.push(new MockERC20("Token C", "C"));
vm.stopPrank();
}
function deployRouter() public returns (TychoRouterExposed) {
@@ -152,18 +150,6 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
return executors;
}
/**
* @dev Mints tokens to the given address
* @param amount The amount of tokens to mint
* @param to The address to mint tokens to
*/
function mintTokens(uint256 amount, address to) internal {
for (uint256 i = 0; i < tokens.length; i++) {
// slither-disable-next-line calls-loop
tokens[i].mint(to, amount);
}
}
function pleEncode(bytes[] memory data)
public
pure

File diff suppressed because one or more lines are too long

View File

@@ -1,18 +0,0 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.26;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockERC20 is ERC20 {
constructor(string memory name_, string memory symbol_)
ERC20(name_, symbol_)
{}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
function decimals() public view virtual override returns (uint8) {
return 18;
}
}

View File

@@ -1,9 +1,10 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import "../TychoRouterTestSetup.sol";
import "@src/executors/CurveExecutor.sol";
import {Test} from "../../lib/forge-std/src/Test.sol";
import {Constants} from "../Constants.sol";
import {Test} from "../../lib/forge-std/src/Test.sol";
interface ICurvePool {
function coins(uint256 i) external view returns (address);
@@ -393,3 +394,20 @@ contract CurveExecutorTest is Test, Constants {
return (coinInIndex, coinOutIndex);
}
}
contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
function testSingleCurveIntegration() public {
deal(UWU_ADDR, ALICE, 1 ether);
vm.startPrank(ALICE);
IERC20(UWU_ADDR).approve(tychoRouterAddr, type(uint256).max);
bytes memory callData =
loadCallDataFromFile("test_single_encoding_strategy_curve");
(bool success,) = tychoRouterAddr.call(callData);
assertTrue(success, "Call Failed");
assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 2877855391767);
vm.stopPrank();
}
}

View File

@@ -2,8 +2,9 @@
pragma solidity ^0.8.26;
import "../TestUtils.sol";
import {Constants} from "../Constants.sol";
import "../TychoRouterTestSetup.sol";
import "@src/executors/EkuboExecutor.sol";
import {Constants} from "../Constants.sol";
import {ICore} from "@ekubo/interfaces/ICore.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {NATIVE_TOKEN_ADDRESS} from "@ekubo/math/constants.sol";
@@ -158,3 +159,24 @@ contract EkuboExecutorTest is Constants, TestUtils {
multiHopSwap(loadCallDataFromFile("test_ekubo_encode_swap_multi"));
}
}
contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
function testSingleEkuboIntegration() public {
vm.stopPrank();
deal(ALICE, 1 ether);
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
// Approve permit2
vm.startPrank(ALICE);
bytes memory callData =
loadCallDataFromFile("test_single_encoding_strategy_ekubo");
(bool success,) = tychoRouterAddr.call{value: 1 ether}(callData);
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
assertTrue(success, "Call Failed");
assertGe(balanceAfter - balanceBefore, 26173932);
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
}
}

View File

@@ -1,9 +1,10 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import "../TestUtils.sol";
import "../TychoRouterTestSetup.sol";
import "@src/executors/MaverickV2Executor.sol";
import {Constants} from "../Constants.sol";
import "../TestUtils.sol";
contract MaverickV2ExecutorExposed is MaverickV2Executor {
constructor(address _factory, address _permit2)
@@ -126,3 +127,23 @@ contract MaverickV2ExecutorTest is TestUtils, Constants {
assertEq(balanceAfter - balanceBefore, amountOut);
}
}
contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
function testSingleMaverickIntegration() public {
deal(GHO_ADDR, ALICE, 1 ether);
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
vm.startPrank(ALICE);
IERC20(GHO_ADDR).approve(tychoRouterAddr, type(uint256).max);
bytes memory callData =
loadCallDataFromFile("test_single_encoding_strategy_maverick");
(bool success,) = tychoRouterAddr.call(callData);
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
assertTrue(success, "Call Failed");
assertGe(balanceAfter - balanceBefore, 999725);
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
}
}

View File

@@ -1,11 +1,12 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import "@src/executors/UniswapV3Executor.sol";
import "../TychoRouterTestSetup.sol";
import "@permit2/src/interfaces/IAllowanceTransfer.sol";
import {Test} from "../../lib/forge-std/src/Test.sol";
import "@src/executors/UniswapV3Executor.sol";
import {Constants} from "../Constants.sol";
import {Permit2TestHelper} from "../Permit2TestHelper.sol";
import {Test} from "../../lib/forge-std/src/Test.sol";
contract UniswapV3ExecutorExposed is UniswapV3Executor {
constructor(address _factory, bytes32 _initCode, address _permit2)
@@ -43,7 +44,6 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
UniswapV3ExecutorExposed uniswapV3Exposed;
UniswapV3ExecutorExposed pancakeV3Exposed;
IERC20 WETH = IERC20(WETH_ADDR);
IERC20 DAI = IERC20(DAI_ADDR);
IAllowanceTransfer permit2;
@@ -211,3 +211,50 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
);
}
}
contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
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,
RestrictTransferFrom.TransferType.TransferFrom
);
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();
}
}

View File

@@ -3,6 +3,7 @@ pragma solidity ^0.8.26;
import "../../src/executors/UniswapV4Executor.sol";
import "../TestUtils.sol";
import "../TychoRouterTestSetup.sol";
import "./UniswapV4Utils.sol";
import "@src/executors/UniswapV4Executor.sol";
import {Constants} from "../Constants.sol";
@@ -211,3 +212,175 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
assertTrue(IERC20(WBTC_ADDR).balanceOf(ALICE) == amountOut);
}
}
contract TychoRouterForBalancerV3Test 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,
RestrictTransferFrom.TransferType.TransferFrom,
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, ALICE, 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,
RestrictTransferFrom.TransferType.TransferFrom,
ALICE,
pools
);
bytes memory swap =
encodeSingleSwap(address(usv4Executor), protocolData);
vm.startPrank(ALICE);
IERC20(USDE_ADDR).approve(tychoRouterAddr, amountIn);
tychoRouter.singleSwap(
amountIn,
USDE_ADDR,
WBTC_ADDR,
118280,
false,
false,
ALICE,
true,
swap
);
assertEq(IERC20(WBTC_ADDR).balanceOf(ALICE), 118281);
}
function testSingleUSV4IntegrationGroupedSwap() public {
// Test created with calldata from our router encoder.
// Performs a single swap from USDC to PEPE though ETH using two
// consecutive USV4 pools. It's a single swap because it is a consecutive grouped swaps
//
// USDC (USV4)> ETH (USV4)> PEPE
//
deal(USDC_ADDR, ALICE, 1 ether);
uint256 balanceBefore = IERC20(PEPE_ADDR).balanceOf(ALICE);
// Approve permit2
vm.startPrank(ALICE);
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
bytes memory callData = loadCallDataFromFile(
"test_single_encoding_strategy_usv4_grouped_swap"
);
(bool success,) = tychoRouterAddr.call(callData);
vm.stopPrank();
uint256 balanceAfter = IERC20(PEPE_ADDR).balanceOf(ALICE);
assertTrue(success, "Call Failed");
assertEq(balanceAfter - balanceBefore, 123172000092711286554274694);
}
function testSingleUSV4IntegrationInputETH() public {
// Test created with calldata from our router encoder.
// Performs a single swap from ETH to PEPE without wrapping or unwrapping
//
// ETH (USV4)> PEPE
//
deal(ALICE, 1 ether);
uint256 balanceBefore = IERC20(PEPE_ADDR).balanceOf(ALICE);
bytes memory callData =
loadCallDataFromFile("test_single_encoding_strategy_usv4_eth_in");
(bool success,) = tychoRouterAddr.call{value: 1 ether}(callData);
vm.stopPrank();
uint256 balanceAfter = IERC20(PEPE_ADDR).balanceOf(ALICE);
assertTrue(success, "Call Failed");
assertEq(balanceAfter - balanceBefore, 235610487387677804636755778);
}
function testSingleUSV4IntegrationOutputETH() public {
// Test created with calldata from our router encoder.
// Performs a single swap from USDC to ETH without wrapping or unwrapping
//
// USDC (USV4)> ETH
//
deal(USDC_ADDR, ALICE, 3000_000000);
uint256 balanceBefore = ALICE.balance;
// Approve permit2
vm.startPrank(ALICE);
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
bytes memory callData =
loadCallDataFromFile("test_single_encoding_strategy_usv4_eth_out");
(bool success,) = tychoRouterAddr.call(callData);
vm.stopPrank();
uint256 balanceAfter = ALICE.balance;
assertTrue(success, "Call Failed");
console.logUint(balanceAfter - balanceBefore);
assertEq(balanceAfter - balanceBefore, 1474406268748155809);
}
}