Merge branch 'main' into feat/maverick-v2-executor
This commit is contained in:
@@ -15,8 +15,6 @@ contract Constants is Test, BaseConstants {
|
||||
address ADMIN = makeAddr("admin"); //admin=us
|
||||
address BOB = makeAddr("bob"); //bob=someone!=us
|
||||
address FUND_RESCUER = makeAddr("fundRescuer");
|
||||
address FEE_SETTER = makeAddr("feeSetter");
|
||||
address FEE_RECEIVER = makeAddr("feeReceiver");
|
||||
address EXECUTOR_SETTER = makeAddr("executorSetter");
|
||||
address ALICE = 0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2;
|
||||
uint256 ALICE_PK =
|
||||
|
||||
@@ -10,7 +10,7 @@ contract DispatcherExposed is Dispatcher {
|
||||
uint256 amount,
|
||||
bytes calldata data
|
||||
) external returns (uint256 calculatedAmount) {
|
||||
return _callExecutor(executor, amount, data);
|
||||
return _callSwapOnExecutor(executor, amount, data);
|
||||
}
|
||||
|
||||
function exposedSetExecutor(address target) external {
|
||||
|
||||
@@ -7,7 +7,45 @@ import "../lib/LibSwap.sol";
|
||||
contract LibSwapTest is Test {
|
||||
using LibSwap for bytes;
|
||||
|
||||
function testSwap() public view {
|
||||
function testSingleSwap() public view {
|
||||
address executor = 0x1234567890123456789012345678901234567890;
|
||||
bytes memory protocolData = abi.encodePacked(uint256(123));
|
||||
|
||||
bytes memory swap = abi.encodePacked(executor, protocolData);
|
||||
this.assertSingleSwap(swap, executor, protocolData);
|
||||
}
|
||||
|
||||
function assertSingleSwap(
|
||||
bytes calldata swap,
|
||||
address executor,
|
||||
bytes calldata protocolData
|
||||
) public pure {
|
||||
(address decodedExecutor, bytes memory decodedProtocolData) =
|
||||
swap.decodeSingleSwap();
|
||||
assertEq(decodedExecutor, executor);
|
||||
assertEq(decodedProtocolData, protocolData);
|
||||
}
|
||||
|
||||
function testSequentialSwap() public view {
|
||||
address executor = 0x1234567890123456789012345678901234567890;
|
||||
bytes memory protocolData = abi.encodePacked(uint256(234));
|
||||
|
||||
bytes memory swap = abi.encodePacked(executor, protocolData);
|
||||
this.assertSequentialSwap(swap, executor, protocolData);
|
||||
}
|
||||
|
||||
function assertSequentialSwap(
|
||||
bytes calldata swap,
|
||||
address executor,
|
||||
bytes calldata protocolData
|
||||
) public pure {
|
||||
(address decodedExecutor, bytes memory decodedProtocolData) =
|
||||
swap.decodeSequentialSwap();
|
||||
assertEq(decodedExecutor, executor);
|
||||
assertEq(decodedProtocolData, protocolData);
|
||||
}
|
||||
|
||||
function testSplitSwap() public view {
|
||||
uint8 tokenInIndex = 1;
|
||||
uint8 tokenOutIndex = 2;
|
||||
uint24 split = 3;
|
||||
@@ -17,20 +55,32 @@ contract LibSwapTest is Test {
|
||||
bytes memory swap = abi.encodePacked(
|
||||
tokenInIndex, tokenOutIndex, split, executor, protocolData
|
||||
);
|
||||
this.assertSwap(swap, tokenInIndex, tokenOutIndex, split, executor);
|
||||
this.assertSplitSwap(
|
||||
swap, tokenInIndex, tokenOutIndex, split, executor, protocolData
|
||||
);
|
||||
}
|
||||
|
||||
// This is necessary so that the compiler accepts bytes as a LibSwap.sol
|
||||
function assertSwap(
|
||||
// This is necessary so that the compiler accepts bytes as a LibSwap.sol for testing
|
||||
// This is because this function takes calldata as input
|
||||
function assertSplitSwap(
|
||||
bytes calldata swap,
|
||||
uint8 tokenInIndex,
|
||||
uint8 tokenOutIndex,
|
||||
uint24 split,
|
||||
address executor
|
||||
address executor,
|
||||
bytes calldata protocolData
|
||||
) public pure {
|
||||
assert(swap.tokenInIndex() == tokenInIndex);
|
||||
assert(swap.tokenOutIndex() == tokenOutIndex);
|
||||
assert(swap.splitPercentage() == split);
|
||||
assert(swap.executor() == executor);
|
||||
(
|
||||
uint8 decodedTokenInIndex,
|
||||
uint8 decodedTokenOutIndex,
|
||||
uint24 decodedSplit,
|
||||
address decodedExecutor,
|
||||
bytes memory decodedProtocolData
|
||||
) = swap.decodeSplitSwap();
|
||||
assertEq(decodedTokenInIndex, tokenInIndex);
|
||||
assertEq(decodedTokenOutIndex, tokenOutIndex);
|
||||
assertEq(decodedSplit, split);
|
||||
assertEq(decodedExecutor, executor);
|
||||
assertEq(decodedProtocolData, protocolData);
|
||||
}
|
||||
}
|
||||
|
||||
87
foundry/test/Permit2TestHelper.sol
Normal file
87
foundry/test/Permit2TestHelper.sol
Normal file
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import "./Constants.sol";
|
||||
import "@permit2/src/interfaces/IAllowanceTransfer.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
contract Permit2TestHelper is Constants {
|
||||
/**
|
||||
* @dev Handles the Permit2 approval process for Alice, allowing the TychoRouter contract
|
||||
* to spend `amount_in` of `tokenIn` on her behalf.
|
||||
*
|
||||
* This function approves the Permit2 contract to transfer the specified token amount
|
||||
* and constructs a `PermitSingle` struct for the approval. It also generates a valid
|
||||
* EIP-712 signature for the approval using Alice's private key.
|
||||
*
|
||||
* @param tokenIn The address of the token being approved.
|
||||
* @param amount_in The amount of tokens to approve for transfer.
|
||||
* @return permitSingle The `PermitSingle` struct containing the approval details.
|
||||
* @return signature The EIP-712 signature for the approval.
|
||||
*/
|
||||
function handlePermit2Approval(
|
||||
address tokenIn,
|
||||
address spender,
|
||||
uint256 amount_in
|
||||
) internal returns (IAllowanceTransfer.PermitSingle memory, bytes memory) {
|
||||
IERC20(tokenIn).approve(PERMIT2_ADDRESS, amount_in);
|
||||
IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer
|
||||
.PermitSingle({
|
||||
details: IAllowanceTransfer.PermitDetails({
|
||||
token: tokenIn,
|
||||
amount: uint160(amount_in),
|
||||
expiration: uint48(block.timestamp + 1 days),
|
||||
nonce: 0
|
||||
}),
|
||||
spender: spender,
|
||||
sigDeadline: block.timestamp + 1 days
|
||||
});
|
||||
|
||||
bytes memory signature = signPermit2(permitSingle, ALICE_PK);
|
||||
return (permitSingle, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Signs a Permit2 `PermitSingle` struct with the given private key.
|
||||
* @param permit The `PermitSingle` struct to sign.
|
||||
* @param privateKey The private key of the signer.
|
||||
* @return The signature as a `bytes` array.
|
||||
*/
|
||||
function signPermit2(
|
||||
IAllowanceTransfer.PermitSingle memory permit,
|
||||
uint256 privateKey
|
||||
) internal view returns (bytes memory) {
|
||||
bytes32 _PERMIT_DETAILS_TYPEHASH = keccak256(
|
||||
"PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"
|
||||
);
|
||||
bytes32 _PERMIT_SINGLE_TYPEHASH = keccak256(
|
||||
"PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"
|
||||
);
|
||||
bytes32 domainSeparator = keccak256(
|
||||
abi.encode(
|
||||
keccak256(
|
||||
"EIP712Domain(string name,uint256 chainId,address verifyingContract)"
|
||||
),
|
||||
keccak256("Permit2"),
|
||||
block.chainid,
|
||||
PERMIT2_ADDRESS
|
||||
)
|
||||
);
|
||||
bytes32 detailsHash =
|
||||
keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details));
|
||||
bytes32 permitHash = keccak256(
|
||||
abi.encode(
|
||||
_PERMIT_SINGLE_TYPEHASH,
|
||||
detailsHash,
|
||||
permit.spender,
|
||||
permit.sigDeadline
|
||||
)
|
||||
);
|
||||
|
||||
bytes32 digest =
|
||||
keccak256(abi.encodePacked("\x19\x01", domainSeparator, permitHash));
|
||||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
|
||||
|
||||
return abi.encodePacked(r, s, v);
|
||||
}
|
||||
}
|
||||
29
foundry/test/TestUtils.sol
Normal file
29
foundry/test/TestUtils.sol
Normal file
@@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.10;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
|
||||
contract TestUtils is Test {
|
||||
constructor() {}
|
||||
|
||||
function loadCallDataFromFile(string memory testName)
|
||||
internal
|
||||
view
|
||||
returns (bytes memory)
|
||||
{
|
||||
string memory fileContent = vm.readFile("./test/assets/calldata.txt");
|
||||
string[] memory lines = vm.split(fileContent, "\n");
|
||||
|
||||
for (uint256 i = 0; i < lines.length; i++) {
|
||||
string[] memory parts = vm.split(lines[i], ":");
|
||||
if (
|
||||
parts.length >= 2
|
||||
&& keccak256(bytes(parts[0])) == keccak256(bytes(testName))
|
||||
) {
|
||||
return vm.parseBytes(parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
revert("Test calldata not found");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
267
foundry/test/TychoRouterProtocolIntegration.t.sol
Normal file
267
foundry/test/TychoRouterProtocolIntegration.t.sol
Normal file
@@ -0,0 +1,267 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
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.
|
||||
|
||||
// Performs a sequential swap from USDC to PEPE though ETH using two
|
||||
// consecutive USV4 pools
|
||||
//
|
||||
// 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_sequential_encoding_strategy_usv4");
|
||||
(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.
|
||||
//
|
||||
// DAI ─(USV2)─> WETH ─(bal)─> WBTC ─(curve)─> USDT ─(ekubo)─> ETH ─(USV4)─> USDC
|
||||
|
||||
deal(DAI_ADDR, ALICE, 1500 ether);
|
||||
uint256 balanceBefore = address(ALICE).balance;
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(DAI_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||
bytes memory callData = loadCallDataFromFile("test_multi_protocol");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = address(ALICE).balance;
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 732214216964381330);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
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();
|
||||
}
|
||||
}
|
||||
498
foundry/test/TychoRouterSequentialSwap.t.sol
Normal file
498
foundry/test/TychoRouterSequentialSwap.t.sol
Normal file
@@ -0,0 +1,498 @@
|
||||
// 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 TychoRouterSequentialSwapTest is TychoRouterTestSetup {
|
||||
function _getSequentialSwaps(bool permit2)
|
||||
internal
|
||||
view
|
||||
returns (bytes[] memory)
|
||||
{
|
||||
// Trade 1 WETH for USDC through DAI with 2 swaps on Uniswap V2
|
||||
// 1 WETH -> DAI -> USDC
|
||||
// (univ2) (univ2)
|
||||
|
||||
TokenTransfer.TransferType transferType = permit2
|
||||
? TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL
|
||||
: TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL;
|
||||
|
||||
bytes[] memory swaps = new bytes[](2);
|
||||
// WETH -> DAI
|
||||
swaps[0] = encodeSequentialSwap(
|
||||
address(usv2Executor),
|
||||
encodeUniswapV2Swap(
|
||||
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, transferType
|
||||
)
|
||||
);
|
||||
|
||||
// DAI -> USDC
|
||||
swaps[1] = encodeSequentialSwap(
|
||||
address(usv2Executor),
|
||||
encodeUniswapV2Swap(
|
||||
DAI_ADDR,
|
||||
DAI_USDC_POOL,
|
||||
ALICE,
|
||||
true,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
)
|
||||
);
|
||||
return swaps;
|
||||
}
|
||||
|
||||
function testSequentialSwapPermit2() public {
|
||||
// Trade 1 WETH for USDC through DAI - see _getSequentialSwaps for more info
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(WETH_ADDR, ALICE, amountIn);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
(
|
||||
IAllowanceTransfer.PermitSingle memory permitSingle,
|
||||
bytes memory signature
|
||||
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
|
||||
|
||||
bytes[] memory swaps = _getSequentialSwaps(true);
|
||||
tychoRouter.sequentialSwapPermit2(
|
||||
amountIn,
|
||||
WETH_ADDR,
|
||||
USDC_ADDR,
|
||||
1000_000000, // min amount
|
||||
false,
|
||||
false,
|
||||
ALICE,
|
||||
permitSingle,
|
||||
signature,
|
||||
pleEncode(swaps)
|
||||
);
|
||||
|
||||
uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
assertEq(usdcBalance, 2005810530);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testSequentialSwapNoPermit2() public {
|
||||
// Trade 1 WETH for USDC through DAI - see _getSequentialSwaps for more info
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(WETH_ADDR, ALICE, amountIn);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn);
|
||||
|
||||
bytes[] memory swaps = _getSequentialSwaps(false);
|
||||
tychoRouter.sequentialSwap(
|
||||
amountIn,
|
||||
WETH_ADDR,
|
||||
USDC_ADDR,
|
||||
1000_000000, // min amount
|
||||
false,
|
||||
false,
|
||||
ALICE,
|
||||
pleEncode(swaps)
|
||||
);
|
||||
|
||||
uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
assertEq(usdcBalance, 2005810530);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testSequentialSwapUndefinedMinAmount() public {
|
||||
// Trade 1 WETH for USDC through DAI - see _getSequentialSwaps for more info
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(WETH_ADDR, ALICE, amountIn);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn);
|
||||
|
||||
bytes[] memory swaps = _getSequentialSwaps(false);
|
||||
vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector);
|
||||
tychoRouter.sequentialSwap(
|
||||
amountIn,
|
||||
WETH_ADDR,
|
||||
USDC_ADDR,
|
||||
0, // min amount
|
||||
false,
|
||||
false,
|
||||
ALICE,
|
||||
pleEncode(swaps)
|
||||
);
|
||||
}
|
||||
|
||||
function testSequentialSwapInsufficientApproval() public {
|
||||
// Trade 1 WETH for USDC through DAI - see _getSequentialSwaps for more info
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(WETH_ADDR, ALICE, amountIn);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn - 1);
|
||||
|
||||
bytes[] memory swaps = _getSequentialSwaps(false);
|
||||
vm.expectRevert();
|
||||
tychoRouter.sequentialSwap(
|
||||
amountIn,
|
||||
WETH_ADDR,
|
||||
USDC_ADDR,
|
||||
0, // min amount
|
||||
false,
|
||||
false,
|
||||
ALICE,
|
||||
pleEncode(swaps)
|
||||
);
|
||||
}
|
||||
|
||||
function testSequentialSwapNegativeSlippageFailure() public {
|
||||
// Trade 1 WETH for USDC through DAI - see _getSequentialSwaps for more info
|
||||
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(WETH_ADDR, ALICE, amountIn);
|
||||
vm.startPrank(ALICE);
|
||||
(
|
||||
IAllowanceTransfer.PermitSingle memory permitSingle,
|
||||
bytes memory signature
|
||||
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
|
||||
|
||||
bytes[] memory swaps = _getSequentialSwaps(true);
|
||||
|
||||
uint256 minAmountOut = 3000 * 1e18;
|
||||
|
||||
vm.expectRevert(
|
||||
abi.encodeWithSelector(
|
||||
TychoRouter__NegativeSlippage.selector,
|
||||
2005810530, // actual amountOut
|
||||
minAmountOut
|
||||
)
|
||||
);
|
||||
tychoRouter.sequentialSwapPermit2(
|
||||
amountIn,
|
||||
WETH_ADDR,
|
||||
DAI_ADDR,
|
||||
minAmountOut,
|
||||
false,
|
||||
false,
|
||||
ALICE,
|
||||
permitSingle,
|
||||
signature,
|
||||
pleEncode(swaps)
|
||||
);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testSequentialSwapWrapETH() 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 swaps = new bytes[](2);
|
||||
// WETH -> DAI
|
||||
swaps[0] = encodeSequentialSwap(
|
||||
address(usv2Executor),
|
||||
encodeUniswapV2Swap(
|
||||
WETH_ADDR,
|
||||
WETH_DAI_POOL,
|
||||
tychoRouterAddr,
|
||||
false,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
)
|
||||
);
|
||||
|
||||
// DAI -> USDC
|
||||
swaps[1] = encodeSequentialSwap(
|
||||
address(usv2Executor),
|
||||
encodeUniswapV2Swap(
|
||||
DAI_ADDR,
|
||||
DAI_USDC_POOL,
|
||||
ALICE,
|
||||
true,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
)
|
||||
);
|
||||
|
||||
uint256 amountOut = tychoRouter.sequentialSwapPermit2{value: amountIn}(
|
||||
amountIn,
|
||||
address(0),
|
||||
USDC_ADDR,
|
||||
1000_000000,
|
||||
true,
|
||||
false,
|
||||
ALICE,
|
||||
emptyPermitSingle,
|
||||
"",
|
||||
pleEncode(swaps)
|
||||
);
|
||||
uint256 expectedAmount = 2005810530;
|
||||
assertEq(amountOut, expectedAmount);
|
||||
uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
assertEq(usdcBalance, expectedAmount);
|
||||
assertEq(ALICE.balance, 0);
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testSequentialSwapUnwrapETH() public {
|
||||
// Trade 3k DAI for WETH with 1 swap on Uniswap V2 and unwrap it at the end
|
||||
|
||||
uint256 amountIn = 3_000 * 10 ** 6;
|
||||
deal(USDC_ADDR, ALICE, amountIn);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
|
||||
(
|
||||
IAllowanceTransfer.PermitSingle memory permitSingle,
|
||||
bytes memory signature
|
||||
) = handlePermit2Approval(USDC_ADDR, tychoRouterAddr, amountIn);
|
||||
|
||||
bytes[] memory swaps = new bytes[](2);
|
||||
|
||||
// USDC -> DAI
|
||||
swaps[0] = encodeSequentialSwap(
|
||||
address(usv2Executor),
|
||||
encodeUniswapV2Swap(
|
||||
USDC_ADDR,
|
||||
DAI_USDC_POOL,
|
||||
tychoRouterAddr,
|
||||
false,
|
||||
TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL
|
||||
)
|
||||
);
|
||||
|
||||
// DAI -> WETH
|
||||
swaps[1] = encodeSequentialSwap(
|
||||
address(usv2Executor),
|
||||
encodeUniswapV2Swap(
|
||||
DAI_ADDR,
|
||||
WETH_DAI_POOL,
|
||||
tychoRouterAddr,
|
||||
true,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
)
|
||||
);
|
||||
|
||||
uint256 amountOut = tychoRouter.sequentialSwapPermit2(
|
||||
amountIn,
|
||||
USDC_ADDR,
|
||||
address(0),
|
||||
1 * 10 ** 18, // min amount
|
||||
false,
|
||||
true,
|
||||
ALICE,
|
||||
permitSingle,
|
||||
signature,
|
||||
pleEncode(swaps)
|
||||
);
|
||||
|
||||
uint256 expectedAmount = 1466332452295613768; // 1.11 ETH
|
||||
assertEq(amountOut, expectedAmount);
|
||||
assertEq(ALICE.balance, expectedAmount);
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testCyclicSequentialSwap() public {
|
||||
// This test has start and end tokens that are the same
|
||||
// The flow is:
|
||||
// USDC --(USV3)--> WETH --(USV3)--> USDC
|
||||
uint256 amountIn = 100 * 10 ** 6;
|
||||
deal(USDC_ADDR, tychoRouterAddr, amountIn);
|
||||
|
||||
bytes memory usdcWethV3Pool1ZeroOneData = encodeUniswapV3Swap(
|
||||
USDC_ADDR,
|
||||
WETH_ADDR,
|
||||
tychoRouterAddr,
|
||||
USDC_WETH_USV3,
|
||||
true,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
);
|
||||
|
||||
bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap(
|
||||
WETH_ADDR,
|
||||
USDC_ADDR,
|
||||
tychoRouterAddr,
|
||||
USDC_WETH_USV3_2,
|
||||
false,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
);
|
||||
|
||||
bytes[] memory swaps = new bytes[](2);
|
||||
// USDC -> WETH
|
||||
swaps[0] = encodeSequentialSwap(
|
||||
address(usv3Executor), usdcWethV3Pool1ZeroOneData
|
||||
);
|
||||
// WETH -> USDC
|
||||
swaps[1] = encodeSequentialSwap(
|
||||
address(usv3Executor), usdcWethV3Pool2OneZeroData
|
||||
);
|
||||
|
||||
tychoRouter.exposedSequentialSwap(amountIn, pleEncode(swaps));
|
||||
assertEq(IERC20(USDC_ADDR).balanceOf(tychoRouterAddr), 99792554);
|
||||
}
|
||||
|
||||
function testSequentialSwapIntegrationPermit2() public {
|
||||
// Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools
|
||||
//
|
||||
// WETH ──(USV2)──> WBTC ───(USV2)──> USDC
|
||||
deal(WETH_ADDR, ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_sequential_swap_strategy_encoder");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 1951856272);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testSequentialSwapIntegration() public {
|
||||
// Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools
|
||||
//
|
||||
// WETH ──(USV2)──> WBTC ───(USV2)──> USDC
|
||||
deal(WETH_ADDR, ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||
bytes memory callData = loadCallDataFromFile(
|
||||
"test_sequential_swap_strategy_encoder_no_permit2"
|
||||
);
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 1951856272);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testSequentialCyclicSwapIntegration() public {
|
||||
// USDC -> WETH -> USDC using two pools
|
||||
deal(USDC_ADDR, ALICE, 100 * 10 ** 6);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_sequential_strategy_cyclic_swap");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99792554);
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testUSV3USV2Integration() public {
|
||||
// Performs a sequential swap from WETH to USDC though WBTC and DAI using USV3 and USV2 pools
|
||||
//
|
||||
// WETH ──(USV3)──> WBTC ───(USV2)──> USDC
|
||||
deal(WETH_ADDR, ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_uniswap_v3_uniswap_v2");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 1952973189);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testUSV3USV3Integration() public {
|
||||
// Performs a sequential swap from WETH to USDC though WBTC using USV3 pools
|
||||
//
|
||||
// WETH ──(USV3)──> WBTC ───(USV3)──> USDC
|
||||
deal(WETH_ADDR, ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_uniswap_v3_uniswap_v3");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 2015740345);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testUSV3CurveIntegration() public {
|
||||
// Performs a sequential swap from WETH to USDT though WBTC using USV3 and Curve pools
|
||||
//
|
||||
// WETH ──(USV3)──> WBTC ───(USV3)──> USDT
|
||||
deal(WETH_ADDR, ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(USDT_ADDR).balanceOf(ALICE);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||
bytes memory callData = loadCallDataFromFile("test_uniswap_v3_curve");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(USDT_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 2018869128);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testBalancerV2USV2Integration() public {
|
||||
// Performs a sequential swap from WETH to USDC though WBTC using Balancer v2 and USV2 pools
|
||||
//
|
||||
// WETH ──(balancer)──> WBTC ───(USV2)──> USDC
|
||||
deal(WETH_ADDR, ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(USDT_ADDR).balanceOf(ALICE);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_balancer_v2_uniswap_v2");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 1949668893);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
}
|
||||
384
foundry/test/TychoRouterSingleSwap.t.sol
Normal file
384
foundry/test/TychoRouterSingleSwap.t.sol
Normal file
@@ -0,0 +1,384 @@
|
||||
// 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,
|
||||
ALICE,
|
||||
false,
|
||||
TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL
|
||||
);
|
||||
|
||||
bytes memory swap =
|
||||
encodeSingleSwap(address(usv2Executor), protocolData);
|
||||
|
||||
tychoRouter.singleSwapPermit2(
|
||||
amountIn,
|
||||
WETH_ADDR,
|
||||
DAI_ADDR,
|
||||
2008817438608734439722,
|
||||
false,
|
||||
false,
|
||||
ALICE,
|
||||
permitSingle,
|
||||
signature,
|
||||
swap
|
||||
);
|
||||
|
||||
uint256 daiBalance = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||
assertEq(daiBalance, 2018817438608734439722);
|
||||
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,
|
||||
ALICE,
|
||||
false,
|
||||
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL
|
||||
);
|
||||
|
||||
bytes memory swap =
|
||||
encodeSingleSwap(address(usv2Executor), protocolData);
|
||||
|
||||
uint256 minAmountOut = 2000 * 1e18;
|
||||
uint256 amountOut = tychoRouter.singleSwap(
|
||||
amountIn,
|
||||
WETH_ADDR,
|
||||
DAI_ADDR,
|
||||
minAmountOut,
|
||||
false,
|
||||
false,
|
||||
ALICE,
|
||||
swap
|
||||
);
|
||||
|
||||
uint256 expectedAmount = 2018817438608734439722;
|
||||
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,
|
||||
ALICE,
|
||||
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,
|
||||
ALICE,
|
||||
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,
|
||||
ALICE,
|
||||
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,
|
||||
2018817438608734439722, // 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,
|
||||
ALICE,
|
||||
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 = 2018817438608734439722;
|
||||
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 = 1475644707225677606;
|
||||
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 balanceBefore = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_single_swap_strategy_encoder_no_permit2");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 2018817438608734439722);
|
||||
}
|
||||
|
||||
function testSingleSwapIntegrationPermit2() public {
|
||||
// Tests swapping WETH -> DAI on a USV2 pool with permit2
|
||||
deal(WETH_ADDR, ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_single_swap_strategy_encoder");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 2018817438608734439722);
|
||||
}
|
||||
|
||||
function testSingleSwapWithWrapIntegration() public {
|
||||
// Tests swapping WETH -> DAI on a USV2 pool, but ETH is received from the user
|
||||
// and wrapped before the swap
|
||||
deal(ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_single_swap_strategy_encoder_wrap");
|
||||
(bool success,) = tychoRouterAddr.call{value: 1 ether}(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 2018817438608734439722);
|
||||
}
|
||||
|
||||
function testSingleSwapWithUnwrapIntegration() public {
|
||||
// Tests swapping DAI -> WETH on a USV2 pool, and WETH is unwrapped to ETH
|
||||
// before sending back to the user
|
||||
deal(DAI_ADDR, ALICE, 3000 ether);
|
||||
uint256 balanceBefore = ALICE.balance;
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(DAI_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_single_swap_strategy_encoder_unwrap");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = ALICE.balance;
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 1475644707225677606);
|
||||
}
|
||||
|
||||
function testSingleSwapIntegrationNoTransferIn() public {
|
||||
// Tests swapping WETH -> DAI on a USV2 pool assuming that the tokens are already inside the router
|
||||
deal(WETH_ADDR, tychoRouterAddr, 1 ether);
|
||||
uint256 balanceBefore = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
bytes memory callData = loadCallDataFromFile(
|
||||
"test_single_swap_strategy_encoder_no_transfer_in"
|
||||
);
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 2018817438608734439722);
|
||||
}
|
||||
}
|
||||
578
foundry/test/TychoRouterSplitSwap.t.sol
Normal file
578
foundry/test/TychoRouterSplitSwap.t.sol
Normal file
@@ -0,0 +1,578 @@
|
||||
// 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 TychoRouterSplitSwapTest is TychoRouterTestSetup {
|
||||
function _getSplitSwaps(bool permit2)
|
||||
private
|
||||
view
|
||||
returns (bytes[] memory)
|
||||
{
|
||||
// Trade 1 WETH for USDC through DAI and WBTC with 4 swaps on Uniswap V2
|
||||
// -> DAI ->
|
||||
// 1 WETH USDC
|
||||
// -> WBTC ->
|
||||
// (univ2) (univ2)
|
||||
bytes[] memory swaps = new bytes[](4);
|
||||
|
||||
TokenTransfer.TransferType inTransferType = permit2
|
||||
? TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL
|
||||
: TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL;
|
||||
|
||||
// WETH -> WBTC (60%)
|
||||
swaps[0] = encodeSplitSwap(
|
||||
uint8(0),
|
||||
uint8(1),
|
||||
(0xffffff * 60) / 100, // 60%
|
||||
address(usv2Executor),
|
||||
encodeUniswapV2Swap(
|
||||
WETH_ADDR,
|
||||
WETH_WBTC_POOL,
|
||||
tychoRouterAddr,
|
||||
false,
|
||||
inTransferType
|
||||
)
|
||||
);
|
||||
// WBTC -> USDC
|
||||
swaps[1] = encodeSplitSwap(
|
||||
uint8(1),
|
||||
uint8(2),
|
||||
uint24(0),
|
||||
address(usv2Executor),
|
||||
encodeUniswapV2Swap(
|
||||
WBTC_ADDR,
|
||||
USDC_WBTC_POOL,
|
||||
ALICE,
|
||||
true,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
)
|
||||
);
|
||||
// WETH -> DAI
|
||||
swaps[2] = encodeSplitSwap(
|
||||
uint8(0),
|
||||
uint8(3),
|
||||
uint24(0),
|
||||
address(usv2Executor),
|
||||
encodeUniswapV2Swap(
|
||||
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, inTransferType
|
||||
)
|
||||
);
|
||||
|
||||
// DAI -> USDC
|
||||
swaps[3] = encodeSplitSwap(
|
||||
uint8(3),
|
||||
uint8(2),
|
||||
uint24(0),
|
||||
address(usv2Executor),
|
||||
encodeUniswapV2Swap(
|
||||
DAI_ADDR,
|
||||
DAI_USDC_POOL,
|
||||
ALICE,
|
||||
true,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
)
|
||||
);
|
||||
|
||||
return swaps;
|
||||
}
|
||||
|
||||
function testSplitSwapInternalMethod() public {
|
||||
// Trade 1 WETH for USDC through DAI and WBTC - see _getSplitSwaps for more info
|
||||
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(WETH_ADDR, ALICE, amountIn);
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn);
|
||||
bytes[] memory swaps = _getSplitSwaps(false);
|
||||
tychoRouter.exposedSplitSwap(amountIn, 4, pleEncode(swaps));
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
assertEq(usdcBalance, 1989737355);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testSplitSwapPermit2() public {
|
||||
// Trade 1 WETH for USDC through DAI and WBTC - see _getSplitSwaps for more info
|
||||
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(WETH_ADDR, ALICE, amountIn);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
(
|
||||
IAllowanceTransfer.PermitSingle memory permitSingle,
|
||||
bytes memory signature
|
||||
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
|
||||
|
||||
bytes[] memory swaps = _getSplitSwaps(true);
|
||||
|
||||
tychoRouter.splitSwapPermit2(
|
||||
amountIn,
|
||||
WETH_ADDR,
|
||||
USDC_ADDR,
|
||||
1, // min amount
|
||||
false,
|
||||
false,
|
||||
4,
|
||||
ALICE,
|
||||
permitSingle,
|
||||
signature,
|
||||
pleEncode(swaps)
|
||||
);
|
||||
|
||||
uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
assertEq(usdcBalance, 1989737355);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testSplitSwapNoPermit2() public {
|
||||
// Trade 1 WETH for USDC through DAI and WBTC - see _getSplitSwaps for more info
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(WETH_ADDR, ALICE, amountIn);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn);
|
||||
|
||||
bytes[] memory swaps = _getSplitSwaps(false);
|
||||
|
||||
tychoRouter.splitSwap(
|
||||
amountIn,
|
||||
WETH_ADDR,
|
||||
USDC_ADDR,
|
||||
1000_000000, // min amount
|
||||
false,
|
||||
false,
|
||||
4,
|
||||
ALICE,
|
||||
pleEncode(swaps)
|
||||
);
|
||||
|
||||
uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
assertEq(usdcBalance, 1989737355);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 0);
|
||||
}
|
||||
|
||||
function testSplitSwapUndefinedMinAmount() public {
|
||||
// Min amount should always be non-zero. If zero, swap attempt should revert.
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(WETH_ADDR, ALICE, amountIn);
|
||||
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn);
|
||||
|
||||
bytes[] memory swaps = _getSplitSwaps(false);
|
||||
|
||||
vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector);
|
||||
tychoRouter.splitSwap(
|
||||
amountIn,
|
||||
WETH_ADDR,
|
||||
USDC_ADDR,
|
||||
0, // min amount
|
||||
false,
|
||||
false,
|
||||
4,
|
||||
ALICE,
|
||||
pleEncode(swaps)
|
||||
);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testSplitSwapInsufficientApproval() public {
|
||||
// Trade 1 WETH for USDC through DAI and WBTC - see _getSplitSwaps for more info
|
||||
uint256 amountIn = 1 ether;
|
||||
|
||||
deal(WETH_ADDR, ALICE, amountIn);
|
||||
vm.startPrank(ALICE);
|
||||
// Approve less than the amountIn
|
||||
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn - 1);
|
||||
bytes[] memory swaps = _getSplitSwaps(false);
|
||||
|
||||
vm.expectRevert();
|
||||
tychoRouter.splitSwap(
|
||||
amountIn,
|
||||
WETH_ADDR,
|
||||
USDC_ADDR,
|
||||
1000_000000, // min amount
|
||||
false,
|
||||
false,
|
||||
2,
|
||||
ALICE,
|
||||
pleEncode(swaps)
|
||||
);
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testSplitSwapNegativeSlippageFailure() public {
|
||||
// Trade 1 WETH for USDC through DAI and WBTC - see _getSplitSwaps for more info
|
||||
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(WETH_ADDR, ALICE, amountIn);
|
||||
vm.startPrank(ALICE);
|
||||
(
|
||||
IAllowanceTransfer.PermitSingle memory permitSingle,
|
||||
bytes memory signature
|
||||
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
|
||||
|
||||
bytes[] memory swaps = _getSplitSwaps(true);
|
||||
|
||||
uint256 minAmountOut = 3000 * 1e18;
|
||||
|
||||
vm.expectRevert(
|
||||
abi.encodeWithSelector(
|
||||
TychoRouter__NegativeSlippage.selector,
|
||||
1989737355, // actual amountOut
|
||||
minAmountOut
|
||||
)
|
||||
);
|
||||
tychoRouter.splitSwapPermit2(
|
||||
amountIn,
|
||||
WETH_ADDR,
|
||||
DAI_ADDR,
|
||||
minAmountOut,
|
||||
false,
|
||||
false,
|
||||
4,
|
||||
ALICE,
|
||||
permitSingle,
|
||||
signature,
|
||||
pleEncode(swaps)
|
||||
);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testSplitSwapWrapETH() public {
|
||||
// Trade 1 ETH (and wrap it) for DAI with 1 swap on Uniswap V2
|
||||
|
||||
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,
|
||||
ALICE,
|
||||
false,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
);
|
||||
|
||||
bytes memory swap = encodeSplitSwap(
|
||||
uint8(0), uint8(1), uint24(0), address(usv2Executor), protocolData
|
||||
);
|
||||
bytes[] memory swaps = new bytes[](1);
|
||||
swaps[0] = swap;
|
||||
|
||||
uint256 amountOut = tychoRouter.splitSwapPermit2{value: amountIn}(
|
||||
amountIn,
|
||||
address(0),
|
||||
DAI_ADDR,
|
||||
2008817438608734439722,
|
||||
true,
|
||||
false,
|
||||
2,
|
||||
ALICE,
|
||||
emptyPermitSingle,
|
||||
"",
|
||||
pleEncode(swaps)
|
||||
);
|
||||
uint256 expectedAmount = 2018817438608734439722;
|
||||
assertEq(amountOut, expectedAmount);
|
||||
uint256 daiBalance = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||
assertEq(daiBalance, expectedAmount);
|
||||
assertEq(ALICE.balance, 0);
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testSplitSwapUnwrapETH() public {
|
||||
// Trade 3k DAI for WETH with 1 swap on Uniswap V2 and unwrap it at the end
|
||||
|
||||
uint256 amountIn = 3_000 * 10 ** 18;
|
||||
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 = encodeSplitSwap(
|
||||
uint8(0), uint8(1), uint24(0), address(usv2Executor), protocolData
|
||||
);
|
||||
bytes[] memory swaps = new bytes[](1);
|
||||
swaps[0] = swap;
|
||||
|
||||
uint256 amountOut = tychoRouter.splitSwapPermit2(
|
||||
amountIn,
|
||||
DAI_ADDR,
|
||||
address(0),
|
||||
1465644707225677606,
|
||||
false,
|
||||
true,
|
||||
2,
|
||||
ALICE,
|
||||
permitSingle,
|
||||
signature,
|
||||
pleEncode(swaps)
|
||||
);
|
||||
|
||||
uint256 expectedAmount = 1475644707225677606; // 1.12 ETH
|
||||
assertEq(amountOut, expectedAmount);
|
||||
assertEq(ALICE.balance, expectedAmount);
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testEmptySwapsRevert() public {
|
||||
uint256 amountIn = 10 ** 18;
|
||||
bytes memory swaps = "";
|
||||
vm.expectRevert(TychoRouter__EmptySwaps.selector);
|
||||
tychoRouter.exposedSplitSwap(amountIn, 2, swaps);
|
||||
}
|
||||
|
||||
function testSplitInputCyclicSwapInternalMethod() public {
|
||||
// This test has start and end tokens that are the same
|
||||
// The flow is:
|
||||
// ┌─ (USV3, 60% split) ──> WETH ─┐
|
||||
// │ │
|
||||
// USDC ──────┤ ├──(USV2)──> USDC
|
||||
// │ │
|
||||
// └─ (USV3, 40% split) ──> WETH ─┘
|
||||
uint256 amountIn = 100 * 10 ** 6;
|
||||
deal(USDC_ADDR, ALICE, amountIn);
|
||||
vm.startPrank(ALICE);
|
||||
// Approve the TychoRouter to spend USDC
|
||||
IERC20(USDC_ADDR).approve(tychoRouterAddr, amountIn);
|
||||
|
||||
bytes memory usdcWethV3Pool1ZeroOneData = encodeUniswapV3Swap(
|
||||
USDC_ADDR,
|
||||
WETH_ADDR,
|
||||
tychoRouterAddr,
|
||||
USDC_WETH_USV3,
|
||||
true,
|
||||
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL
|
||||
);
|
||||
|
||||
bytes memory usdcWethV3Pool2ZeroOneData = encodeUniswapV3Swap(
|
||||
USDC_ADDR,
|
||||
WETH_ADDR,
|
||||
tychoRouterAddr,
|
||||
USDC_WETH_USV3_2,
|
||||
true,
|
||||
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL
|
||||
);
|
||||
|
||||
bytes memory wethUsdcV2OneZeroData = encodeUniswapV2Swap(
|
||||
WETH_ADDR,
|
||||
USDC_WETH_USV2,
|
||||
tychoRouterAddr,
|
||||
false,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
);
|
||||
|
||||
bytes[] memory swaps = new bytes[](3);
|
||||
// USDC -> WETH (60% split)
|
||||
swaps[0] = encodeSplitSwap(
|
||||
uint8(0),
|
||||
uint8(1),
|
||||
(0xffffff * 60) / 100, // 60%
|
||||
address(usv3Executor),
|
||||
usdcWethV3Pool1ZeroOneData
|
||||
);
|
||||
// USDC -> WETH (40% remainder)
|
||||
swaps[1] = encodeSplitSwap(
|
||||
uint8(0),
|
||||
uint8(1),
|
||||
uint24(0),
|
||||
address(usv3Executor),
|
||||
usdcWethV3Pool2ZeroOneData
|
||||
);
|
||||
// WETH -> USDC
|
||||
swaps[2] = encodeSplitSwap(
|
||||
uint8(1),
|
||||
uint8(0),
|
||||
uint24(0),
|
||||
address(usv2Executor),
|
||||
wethUsdcV2OneZeroData
|
||||
);
|
||||
tychoRouter.exposedSplitSwap(amountIn, 2, pleEncode(swaps));
|
||||
vm.stopPrank();
|
||||
assertEq(IERC20(USDC_ADDR).balanceOf(tychoRouterAddr), 99654537);
|
||||
}
|
||||
|
||||
function testSplitOutputCyclicSwapInternalMethod() public {
|
||||
// This test has start and end tokens that are the same
|
||||
// The flow is:
|
||||
// ┌─── (USV3, 60% split) ───┐
|
||||
// │ │
|
||||
// USDC ──(USV2) ── WETH──| ├─> USDC
|
||||
// │ │
|
||||
// └─── (USV3, 40% split) ───┘
|
||||
|
||||
uint256 amountIn = 100 * 10 ** 6;
|
||||
deal(USDC_ADDR, tychoRouterAddr, amountIn);
|
||||
|
||||
bytes memory usdcWethV2Data = encodeUniswapV2Swap(
|
||||
USDC_ADDR,
|
||||
USDC_WETH_USV2,
|
||||
tychoRouterAddr,
|
||||
true,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
);
|
||||
|
||||
bytes memory usdcWethV3Pool1OneZeroData = encodeUniswapV3Swap(
|
||||
WETH_ADDR,
|
||||
USDC_ADDR,
|
||||
tychoRouterAddr,
|
||||
USDC_WETH_USV3,
|
||||
false,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
);
|
||||
|
||||
bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap(
|
||||
WETH_ADDR,
|
||||
USDC_ADDR,
|
||||
tychoRouterAddr,
|
||||
USDC_WETH_USV3_2,
|
||||
false,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
);
|
||||
|
||||
bytes[] memory swaps = new bytes[](3);
|
||||
// USDC -> WETH
|
||||
swaps[0] = encodeSplitSwap(
|
||||
uint8(0), uint8(1), uint24(0), address(usv2Executor), usdcWethV2Data
|
||||
);
|
||||
// WETH -> USDC
|
||||
swaps[1] = encodeSplitSwap(
|
||||
uint8(1),
|
||||
uint8(0),
|
||||
(0xffffff * 60) / 100,
|
||||
address(usv3Executor),
|
||||
usdcWethV3Pool1OneZeroData
|
||||
);
|
||||
|
||||
// WETH -> USDC
|
||||
swaps[2] = encodeSplitSwap(
|
||||
uint8(1),
|
||||
uint8(0),
|
||||
uint24(0),
|
||||
address(usv3Executor),
|
||||
usdcWethV3Pool2OneZeroData
|
||||
);
|
||||
|
||||
tychoRouter.exposedSplitSwap(amountIn, 2, pleEncode(swaps));
|
||||
assertEq(IERC20(USDC_ADDR).balanceOf(tychoRouterAddr), 99444510);
|
||||
}
|
||||
|
||||
// Base Network Tests
|
||||
// Make sure to set the RPC_URL to base network
|
||||
function testSplitSwapInternalMethodBase() 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,
|
||||
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL
|
||||
);
|
||||
|
||||
bytes memory swap = encodeSplitSwap(
|
||||
uint8(0), uint8(1), uint24(0), address(usv2Executor), protocolData
|
||||
);
|
||||
bytes[] memory swaps = new bytes[](1);
|
||||
swaps[0] = swap;
|
||||
|
||||
tychoRouter.exposedSplitSwap(amountIn, 2, pleEncode(swaps));
|
||||
assertGt(IERC20(BASE_MAG7).balanceOf(tychoRouterAddr), 1379830606);
|
||||
}
|
||||
|
||||
function testSplitSwapIntegration() public {
|
||||
// Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools
|
||||
//
|
||||
// ┌──(USV2)──> WBTC ───(USV2)──> USDC
|
||||
// WETH ─┤
|
||||
// └──(USV2)──> DAI ───(USV2)──> USDC
|
||||
deal(WETH_ADDR, ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_split_swap_strategy_encoder");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertGe(balanceAfter - balanceBefore, 26173932);
|
||||
|
||||
// All input tokens are transferred to the router at first. Make sure we used
|
||||
// all of it (and thus our splits are correct).
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testSplitInputCyclicSwapIntegration() public {
|
||||
deal(USDC_ADDR, ALICE, 100 * 10 ** 6);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_split_input_cyclic_swap");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99654537);
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testSplitOutputCyclicSwapIntegration() public {
|
||||
deal(USDC_ADDR, ALICE, 100 * 10 ** 6);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||
bytes memory callData =
|
||||
loadCallDataFromFile("test_split_output_cyclic_swap");
|
||||
(bool success,) = tychoRouterAddr.call(callData);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99444510);
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ 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";
|
||||
|
||||
contract TychoRouterExposed is TychoRouter {
|
||||
constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {}
|
||||
@@ -25,16 +27,23 @@ contract TychoRouterExposed is TychoRouter {
|
||||
return _unwrapETH(amount);
|
||||
}
|
||||
|
||||
function exposedSwap(
|
||||
function exposedSplitSwap(
|
||||
uint256 amountIn,
|
||||
uint256 nTokens,
|
||||
bytes calldata swaps
|
||||
) external returns (uint256) {
|
||||
return _swap(amountIn, nTokens, swaps);
|
||||
return _splitSwap(amountIn, nTokens, swaps);
|
||||
}
|
||||
|
||||
function exposedSequentialSwap(uint256 amountIn, bytes calldata swaps)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _sequentialSwap(amountIn, swaps);
|
||||
}
|
||||
}
|
||||
|
||||
contract TychoRouterTestSetup is Test, Constants {
|
||||
contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
||||
TychoRouterExposed tychoRouter;
|
||||
address tychoRouterAddr;
|
||||
UniswapV2Executor public usv2Executor;
|
||||
@@ -47,7 +56,7 @@ contract TychoRouterTestSetup is Test, Constants {
|
||||
MockERC20[] tokens;
|
||||
|
||||
function setUp() public {
|
||||
uint256 forkBlock = 21817316;
|
||||
uint256 forkBlock = 22082754;
|
||||
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
||||
|
||||
vm.startPrank(ADMIN);
|
||||
@@ -71,7 +80,6 @@ contract TychoRouterTestSetup is Test, Constants {
|
||||
tychoRouter = new TychoRouterExposed(PERMIT2_ADDRESS, WETH_ADDR);
|
||||
tychoRouterAddr = address(tychoRouter);
|
||||
tychoRouter.grantRole(keccak256("FUND_RESCUER_ROLE"), FUND_RESCUER);
|
||||
tychoRouter.grantRole(keccak256("FEE_SETTER_ROLE"), FEE_SETTER);
|
||||
tychoRouter.grantRole(keccak256("PAUSER_ROLE"), PAUSER);
|
||||
tychoRouter.grantRole(keccak256("UNPAUSER_ROLE"), UNPAUSER);
|
||||
tychoRouter.grantRole(
|
||||
@@ -91,14 +99,17 @@ contract TychoRouterTestSetup is Test, Constants {
|
||||
address ekuboCore = 0xe0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444;
|
||||
|
||||
IPoolManager poolManager = IPoolManager(poolManagerAddress);
|
||||
usv2Executor = new UniswapV2Executor(factoryV2, initCodeV2);
|
||||
usv3Executor = new UniswapV3Executor(factoryV3, initCodeV3);
|
||||
usv4Executor = new UniswapV4Executor(poolManager);
|
||||
pancakev3Executor =
|
||||
new UniswapV3Executor(factoryPancakeV3, initCodePancakeV3);
|
||||
balancerv2Executor = new BalancerV2Executor();
|
||||
ekuboExecutor = new EkuboExecutor(ekuboCore);
|
||||
curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE);
|
||||
usv2Executor =
|
||||
new UniswapV2Executor(factoryV2, initCodeV2, PERMIT2_ADDRESS, 30);
|
||||
usv3Executor =
|
||||
new UniswapV3Executor(factoryV3, initCodeV3, PERMIT2_ADDRESS);
|
||||
usv4Executor = new UniswapV4Executor(poolManager, PERMIT2_ADDRESS);
|
||||
pancakev3Executor = new UniswapV3Executor(
|
||||
factoryPancakeV3, initCodePancakeV3, PERMIT2_ADDRESS
|
||||
);
|
||||
balancerv2Executor = new BalancerV2Executor(PERMIT2_ADDRESS);
|
||||
ekuboExecutor = new EkuboExecutor(ekuboCore, PERMIT2_ADDRESS);
|
||||
curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE, PERMIT2_ADDRESS);
|
||||
|
||||
address[] memory executors = new address[](7);
|
||||
executors[0] = address(usv2Executor);
|
||||
@@ -123,84 +134,6 @@ contract TychoRouterTestSetup is Test, Constants {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Handles the Permit2 approval process for Alice, allowing the TychoRouter contract
|
||||
* to spend `amount_in` of `tokenIn` on her behalf.
|
||||
*
|
||||
* This function approves the Permit2 contract to transfer the specified token amount
|
||||
* and constructs a `PermitSingle` struct for the approval. It also generates a valid
|
||||
* EIP-712 signature for the approval using Alice's private key.
|
||||
*
|
||||
* @param tokenIn The address of the token being approved.
|
||||
* @param amount_in The amount of tokens to approve for transfer.
|
||||
* @return permitSingle The `PermitSingle` struct containing the approval details.
|
||||
* @return signature The EIP-712 signature for the approval.
|
||||
*/
|
||||
function handlePermit2Approval(address tokenIn, uint256 amount_in)
|
||||
internal
|
||||
returns (IAllowanceTransfer.PermitSingle memory, bytes memory)
|
||||
{
|
||||
IERC20(tokenIn).approve(PERMIT2_ADDRESS, amount_in);
|
||||
IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer
|
||||
.PermitSingle({
|
||||
details: IAllowanceTransfer.PermitDetails({
|
||||
token: tokenIn,
|
||||
amount: uint160(amount_in),
|
||||
expiration: uint48(block.timestamp + 1 days),
|
||||
nonce: 0
|
||||
}),
|
||||
spender: tychoRouterAddr,
|
||||
sigDeadline: block.timestamp + 1 days
|
||||
});
|
||||
|
||||
bytes memory signature = signPermit2(permitSingle, ALICE_PK);
|
||||
return (permitSingle, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Signs a Permit2 `PermitSingle` struct with the given private key.
|
||||
* @param permit The `PermitSingle` struct to sign.
|
||||
* @param privateKey The private key of the signer.
|
||||
* @return The signature as a `bytes` array.
|
||||
*/
|
||||
function signPermit2(
|
||||
IAllowanceTransfer.PermitSingle memory permit,
|
||||
uint256 privateKey
|
||||
) internal view returns (bytes memory) {
|
||||
bytes32 _PERMIT_DETAILS_TYPEHASH = keccak256(
|
||||
"PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"
|
||||
);
|
||||
bytes32 _PERMIT_SINGLE_TYPEHASH = keccak256(
|
||||
"PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"
|
||||
);
|
||||
bytes32 domainSeparator = keccak256(
|
||||
abi.encode(
|
||||
keccak256(
|
||||
"EIP712Domain(string name,uint256 chainId,address verifyingContract)"
|
||||
),
|
||||
keccak256("Permit2"),
|
||||
block.chainid,
|
||||
PERMIT2_ADDRESS
|
||||
)
|
||||
);
|
||||
bytes32 detailsHash =
|
||||
keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details));
|
||||
bytes32 permitHash = keccak256(
|
||||
abi.encode(
|
||||
_PERMIT_SINGLE_TYPEHASH,
|
||||
detailsHash,
|
||||
permit.spender,
|
||||
permit.sigDeadline
|
||||
)
|
||||
);
|
||||
|
||||
bytes32 digest =
|
||||
keccak256(abi.encodePacked("\x19\x01", domainSeparator, permitHash));
|
||||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
|
||||
|
||||
return abi.encodePacked(r, s, v);
|
||||
}
|
||||
|
||||
function pleEncode(bytes[] memory data)
|
||||
public
|
||||
pure
|
||||
@@ -214,7 +147,23 @@ contract TychoRouterTestSetup is Test, Constants {
|
||||
}
|
||||
}
|
||||
|
||||
function encodeSwap(
|
||||
function encodeSingleSwap(address executor, bytes memory protocolData)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodePacked(executor, protocolData);
|
||||
}
|
||||
|
||||
function encodeSequentialSwap(address executor, bytes memory protocolData)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodePacked(executor, protocolData);
|
||||
}
|
||||
|
||||
function encodeSplitSwap(
|
||||
uint8 tokenInIndex,
|
||||
uint8 tokenOutIndex,
|
||||
uint24 split,
|
||||
@@ -230,9 +179,11 @@ contract TychoRouterTestSetup is Test, Constants {
|
||||
address tokenIn,
|
||||
address target,
|
||||
address receiver,
|
||||
bool zero2one
|
||||
bool zero2one,
|
||||
TokenTransfer.TransferType transferType
|
||||
) internal pure returns (bytes memory) {
|
||||
return abi.encodePacked(tokenIn, target, receiver, zero2one);
|
||||
return
|
||||
abi.encodePacked(tokenIn, target, receiver, zero2one, transferType);
|
||||
}
|
||||
|
||||
function encodeUniswapV3Swap(
|
||||
@@ -240,11 +191,18 @@ contract TychoRouterTestSetup is Test, Constants {
|
||||
address tokenOut,
|
||||
address receiver,
|
||||
address target,
|
||||
bool zero2one
|
||||
bool zero2one,
|
||||
TokenTransfer.TransferType transferType
|
||||
) internal view returns (bytes memory) {
|
||||
IUniswapV3Pool pool = IUniswapV3Pool(target);
|
||||
return abi.encodePacked(
|
||||
tokenIn, tokenOut, pool.fee(), receiver, target, zero2one
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
pool.fee(),
|
||||
receiver,
|
||||
target,
|
||||
zero2one,
|
||||
transferType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
26
foundry/test/assets/calldata.txt
Normal file
26
foundry/test/assets/calldata.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
test_uniswap_v3_uniswap_v2:e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000bf00692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb8004375dff511095cc5a197a54140a24efef3a416cbcdf9626bc03e24f779434178a73a0b4bad62ed000100525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2010500
|
||||
test_single_encoding_strategy_ekubo:20144a070000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000071a0cb889707d426a7a386870a03bc70d1b069759805cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000000000000000000000000000000000
|
||||
test_uniswap_v3_uniswap_v3:e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb83ede3eca2a72b3aecc820e955b36f38437d01395cbcdf9626bc03e24f779434178a73a0b4bad62ed000100692e234dae75c793f67a35089c9d99245e1c58470b2260fac5e5542a773aa44fbcfedf7c193bc2c599a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc299ac8ca7087fa4a2a1fb6357269965a2014abc35010000000000000000000000
|
||||
test_balancer_v2_uniswap_v2:e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000c80072c7183455a4c133ae270771860664b6b7ec320bb1c02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599a6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e004375dff511095cc5a197a54140a24efef3a416010300525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20105000000000000000000000000000000000000000000000000
|
||||
test_sequential_swap_strategy_encoder_no_permit2:e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d940004375dff511095cc5a197a54140a24efef3a416000100525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20105000000000000000000000000000000000000000000000000
|
||||
test_sequential_encoding_strategy_usv4:51bcc7b6000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000006838268800000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006810a09000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000004182b5da1843c415fd1304215c3049ced9c5e9b1f66463d5b035d67eb6fe8903de2564f5cb6740cdd838d7e497c61a75ccc572273a67e8c4b9e63f5f1ffc31e3bb1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000880086f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d23119330002cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f4000000000000000000000000000000000000000000000000
|
||||
test_single_encoding_strategy_usv4_eth_out:7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000000000000000000000000000000000006838268800000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006810a0900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000418b43338a724ecb56c3c2ba7b10f26445ef901fa89fb585ad97771b24b87b2d71627d7dcd178bce3594dff4e4c024bd34c64ca33eda7ed6419c627e9a9b089e591c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000002cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c00000000000000000000000000
|
||||
test_sequential_swap_strategy_encoder:51bcc7b60000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006838268800000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006810a09000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000411ddba9e27afa444896a84afcbd773cd492da98731f40e28243bf0aae4f345b7d44b7a6d220f7e5345055b7fe382114f0bf9f1ddb9e97987515880311f25f001b1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d940004375dff511095cc5a197a54140a24efef3a416000200525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20105000000000000000000000000000000000000000000000000
|
||||
test_single_swap_strategy_encoder_no_permit2:20144a070000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000058e7926ee858a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200010000000000000000000000000000
|
||||
test_single_swap_strategy_encoder_no_transfer_in:20144a070000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000058e7926ee858a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000
|
||||
test_single_encoding_strategy_usv4_eth_in:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000007e0a55d4322a6e93c2379c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006838268800000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006810a09000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041657dbf542ed8b39f717c204413c77002d0d75fb3f81300bfef8eedf5889b55523868c42c0e2ef53fe7b82229ceceabe1b662201898043519d042e333c7492dad1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006cf62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330105cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc26982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000000000000000
|
||||
test_sequential_strategy_cyclic_swap:51bcc7b60000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ec8f6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006838268800000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006810a09000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041de5b09c674e85fef9de204fd3a03e190b1d06ebb6847543298183db02b46c0123c1433aec9951dfa391bc489c756816c4e87851882c8b4adbd21931490a820781b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f5640010200692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d8000000000000000000000000
|
||||
test_single_encoding_strategy_curve_st_eth:20144a070000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe84000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000691d1499e622d69689cdf9004d05ec547d650ff211eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeae7ab96520de3a18e5e111b5eaab095312d7fe84dc24316b9ae028f1497c275eb9192a3ea0f670220100010005cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000
|
||||
test_single_swap_strategy_encoder:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000006b56051582a970000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006838268900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006810a09100000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041df0cad7d6d1228221ff55af2259877bbc38b6ef4af79f90757eab0576601a2f522e27881b929a5053eccb0d2fddc18db0bf3e905e407b5f17461e1bf64cbe70e1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200020000000000000000000000000000
|
||||
test_single_encoding_strategy_curve:20144a070000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000055c08ca52497e2f1534b59e2917bf524d4765257000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000691d1499e622d69689cdf9004d05ec547d650ff21155c08ca52497e2f1534b59e2917bf524d4765257c02aaa39b223fe8d0a0e5c4f27ead9083c756cc277146b0a1d08b6844376df6d9da99ba7f1b19e710201000103cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000
|
||||
test_single_swap_strategy_encoder_unwrap:30ace1b10000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000a2a15d09519be00000000000000000000000000000000000000000000000000000000000006838268900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006810a09100000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000004148c4a9386769cda15972ca351ee585b996d184bcf3539441f103ca8477ddc35b4f34e91f490a181166440ceabe512ecc71bd00615ef5ba0ac5a7c58f2e0653511c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139501020000000000000000000000000000
|
||||
test_single_swap_strategy_encoder_wrap:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000059fb7d3830e6fc064b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006838268900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006810a09100000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041ca7017e254f4fc333dbe49435283ee7d7bbf0ab261f79a3606848abbc7387dc2563fcdb32e670f1f80e729326d8d9973aef060a7f7e28d9c8ec2420cad05aa6f1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000
|
||||
test_split_output_cyclic_swap:7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005e703f4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006838268900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006810a09100000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004177fcc3f010414cde9910d59346b7cac5341063addb7f041740fd0851cff3cd914b8b63c61edabe968f912b2ed17f273221a995d049e5d198384b5dbd00edb2011c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950102006e01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc288e6a0c2ddd26feeb64f039a2c41296fcb3f56400000006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d8000000000000000000
|
||||
test_split_input_cyclic_swap:7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006838268900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006810a09100000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004177fcc3f010414cde9910d59346b7cac5341063addb7f041740fd0851cff3cd914b8b63c61edabe968f912b2ed17f273221a995d049e5d198384b5dbd00edb2011c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139006e00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400102006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80102005701000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dccd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000
|
||||
test_split_swap_strategy_encoder:7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006838268900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006810a091000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041df0cad7d6d1228221ff55af2259877bbc38b6ef4af79f90757eab0576601a2f522e27881b929a5053eccb0d2fddc18db0bf3e905e407b5f17461e1bf64cbe70e1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164005700028000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950002005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950002005702030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fae461ca67b15dc8dc81ce7615e0320da1a9ab8d5cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20100005701030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2010000000000000000000000000000000000000000000000000000000000
|
||||
test_uniswap_v3_curve:e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb83ede3eca2a72b3aecc820e955b36f38437d01395cbcdf9626bc03e24f779434178a73a0b4bad62ed000100691d1499e622d69689cdf9004d05ec547d650ff2112260fac5e5542a773aa44fbcfedf7c193bc2c599dac17f958d2ee523a2206206994597c13d831ec7d51a44d3fae010294c616388b506acda1bfaae460301000105cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000
|
||||
test_multi_protocol:51bcc7b600000000000000000000000000000000000000000000005150ae84a8cdf000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a2958f36da71a9200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000005150ae84a8cdf00000000000000000000000000000000000000000000000000000000000006838268a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000006810a09200000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041cf9d1ecdf2b9df43bdac811677440db893f99f04f5addb24c82b15d766c87e5c0091919a2c43ed9dc6fbd1237ce2bed73a3ea27cd638af11c1bb5673468e1d051b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021400525615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139501020072c7183455a4c133ae270771860664b6b7ec320bb1c02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599a6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e3ede3eca2a72b3aecc820e955b36f38437d01395010500691d1499e622d69689cdf9004d05ec547d650ff2112260fac5e5542a773aa44fbcfedf7c193bc2c599dac17f958d2ee523a2206206994597c13d831ec7d51a44d3fae010294c616388b506acda1bfaae4603010001053ede3eca2a72b3aecc820e955b36f38437d013950071a0cb889707d426a7a386870a03bc70d1b0697598003ede3eca2a72b3aecc820e955b36f38437d01395dac17f958d2ee523a2206206994597c13d831ec7a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000001a36e2eb1c43200000032006cf62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c000000000000000000000000
|
||||
test_encode_balancer_v2:c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0105
|
||||
test_ekubo_encode_swap_multi:00ca4f73fe97d0b987a0d12b39bbd562c779bab6f60000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000001a36e2eb1c43200000032
|
||||
test_encode_uniswap_v4_sequential_swap:4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c5990100cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c
|
||||
test_encode_uniswap_v4_simple_swap:4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec70100cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2dac17f958d2ee523a2206206994597c13d831ec7000064000001
|
||||
@@ -1,11 +1,13 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import "../TestUtils.sol";
|
||||
import "@src/executors/BalancerV2Executor.sol";
|
||||
import {Test} from "../../lib/forge-std/src/Test.sol";
|
||||
import {Constants} from "../Constants.sol";
|
||||
|
||||
contract BalancerV2ExecutorExposed is BalancerV2Executor {
|
||||
constructor(address _permit2) BalancerV2Executor(_permit2) {}
|
||||
|
||||
function decodeParams(bytes calldata data)
|
||||
external
|
||||
pure
|
||||
@@ -14,18 +16,15 @@ contract BalancerV2ExecutorExposed is BalancerV2Executor {
|
||||
IERC20 tokenOut,
|
||||
bytes32 poolId,
|
||||
address receiver,
|
||||
bool needsApproval
|
||||
bool needsApproval,
|
||||
TransferType transferType
|
||||
)
|
||||
{
|
||||
return _decodeData(data);
|
||||
}
|
||||
}
|
||||
|
||||
contract BalancerV2ExecutorTest is
|
||||
BalancerV2ExecutorExposed,
|
||||
Test,
|
||||
Constants
|
||||
{
|
||||
contract BalancerV2ExecutorTest is Constants, TestUtils {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
BalancerV2ExecutorExposed balancerV2Exposed;
|
||||
@@ -37,12 +36,17 @@ contract BalancerV2ExecutorTest is
|
||||
function setUp() public {
|
||||
uint256 forkBlock = 17323404;
|
||||
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
||||
balancerV2Exposed = new BalancerV2ExecutorExposed();
|
||||
balancerV2Exposed = new BalancerV2ExecutorExposed(PERMIT2_ADDRESS);
|
||||
}
|
||||
|
||||
function testDecodeParams() public view {
|
||||
bytes memory params = abi.encodePacked(
|
||||
WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, address(2), true
|
||||
WETH_ADDR,
|
||||
BAL_ADDR,
|
||||
WETH_BAL_POOL_ID,
|
||||
address(2),
|
||||
true,
|
||||
TokenTransfer.TransferType.NONE
|
||||
);
|
||||
|
||||
(
|
||||
@@ -50,7 +54,8 @@ contract BalancerV2ExecutorTest is
|
||||
IERC20 tokenOut,
|
||||
bytes32 poolId,
|
||||
address receiver,
|
||||
bool needsApproval
|
||||
bool needsApproval,
|
||||
TokenTransfer.TransferType transferType
|
||||
) = balancerV2Exposed.decodeParams(params);
|
||||
|
||||
assertEq(address(tokenIn), WETH_ADDR);
|
||||
@@ -58,6 +63,7 @@ contract BalancerV2ExecutorTest is
|
||||
assertEq(poolId, WETH_BAL_POOL_ID);
|
||||
assertEq(receiver, address(2));
|
||||
assertEq(needsApproval, true);
|
||||
assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE));
|
||||
}
|
||||
|
||||
function testDecodeParamsInvalidDataLength() public {
|
||||
@@ -70,8 +76,14 @@ contract BalancerV2ExecutorTest is
|
||||
|
||||
function testSwap() public {
|
||||
uint256 amountIn = 10 ** 18;
|
||||
bytes memory protocolData =
|
||||
abi.encodePacked(WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, BOB, true);
|
||||
bytes memory protocolData = abi.encodePacked(
|
||||
WETH_ADDR,
|
||||
BAL_ADDR,
|
||||
WETH_BAL_POOL_ID,
|
||||
BOB,
|
||||
true,
|
||||
TokenTransfer.TransferType.NONE
|
||||
);
|
||||
|
||||
deal(WETH_ADDR, address(balancerV2Exposed), amountIn);
|
||||
uint256 balanceBefore = BAL.balanceOf(BOB);
|
||||
@@ -84,16 +96,15 @@ contract BalancerV2ExecutorTest is
|
||||
}
|
||||
|
||||
function testDecodeIntegration() public view {
|
||||
// Generated by the SwapEncoder - test_encode_balancer_v2
|
||||
bytes memory protocolData =
|
||||
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e01";
|
||||
|
||||
loadCallDataFromFile("test_encode_balancer_v2");
|
||||
(
|
||||
IERC20 tokenIn,
|
||||
IERC20 tokenOut,
|
||||
bytes32 poolId,
|
||||
address receiver,
|
||||
bool needsApproval
|
||||
bool needsApproval,
|
||||
TokenTransfer.TransferType transferType
|
||||
) = balancerV2Exposed.decodeParams(protocolData);
|
||||
|
||||
assertEq(address(tokenIn), WETH_ADDR);
|
||||
@@ -101,12 +112,13 @@ contract BalancerV2ExecutorTest is
|
||||
assertEq(poolId, WETH_BAL_POOL_ID);
|
||||
assertEq(receiver, BOB);
|
||||
assertEq(needsApproval, true);
|
||||
assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE));
|
||||
}
|
||||
|
||||
function testSwapIntegration() public {
|
||||
// Generated by the SwapEncoder - test_encode_balancer_v2
|
||||
bytes memory protocolData =
|
||||
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e01";
|
||||
loadCallDataFromFile("test_encode_balancer_v2");
|
||||
|
||||
uint256 amountIn = 10 ** 18;
|
||||
deal(WETH_ADDR, address(balancerV2Exposed), amountIn);
|
||||
|
||||
@@ -22,7 +22,9 @@ interface MetaRegistry {
|
||||
}
|
||||
|
||||
contract CurveExecutorExposed is CurveExecutor {
|
||||
constructor(address _nativeToken) CurveExecutor(_nativeToken) {}
|
||||
constructor(address _nativeToken, address _permit2)
|
||||
CurveExecutor(_nativeToken, _permit2)
|
||||
{}
|
||||
|
||||
function decodeData(bytes calldata data)
|
||||
external
|
||||
@@ -34,7 +36,9 @@ contract CurveExecutorExposed is CurveExecutor {
|
||||
uint8 poolType,
|
||||
int128 i,
|
||||
int128 j,
|
||||
bool tokenApprovalNeeded
|
||||
bool tokenApprovalNeeded,
|
||||
TokenTransfer.TransferType transferType,
|
||||
address receiver
|
||||
)
|
||||
{
|
||||
return _decodeData(data);
|
||||
@@ -50,7 +54,8 @@ contract CurveExecutorTest is Test, Constants {
|
||||
function setUp() public {
|
||||
uint256 forkBlock = 22031795;
|
||||
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
||||
curveExecutorExposed = new CurveExecutorExposed(ETH_ADDR_FOR_CURVE);
|
||||
curveExecutorExposed =
|
||||
new CurveExecutorExposed(ETH_ADDR_FOR_CURVE, PERMIT2_ADDRESS);
|
||||
metaRegistry = MetaRegistry(CURVE_META_REGISTRY);
|
||||
}
|
||||
|
||||
@@ -62,7 +67,9 @@ contract CurveExecutorTest is Test, Constants {
|
||||
uint8(3),
|
||||
uint8(2),
|
||||
uint8(0),
|
||||
true
|
||||
true,
|
||||
TokenTransfer.TransferType.NONE,
|
||||
ALICE
|
||||
);
|
||||
|
||||
(
|
||||
@@ -72,7 +79,9 @@ contract CurveExecutorTest is Test, Constants {
|
||||
uint8 poolType,
|
||||
int128 i,
|
||||
int128 j,
|
||||
bool tokenApprovalNeeded
|
||||
bool tokenApprovalNeeded,
|
||||
TokenTransfer.TransferType transferType,
|
||||
address receiver
|
||||
) = curveExecutorExposed.decodeData(data);
|
||||
|
||||
assertEq(tokenIn, WETH_ADDR);
|
||||
@@ -82,6 +91,8 @@ contract CurveExecutorTest is Test, Constants {
|
||||
assertEq(i, 2);
|
||||
assertEq(j, 0);
|
||||
assertEq(tokenApprovalNeeded, true);
|
||||
assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE));
|
||||
assertEq(receiver, ALICE);
|
||||
}
|
||||
|
||||
function testTriPool() public {
|
||||
@@ -89,15 +100,12 @@ contract CurveExecutorTest is Test, Constants {
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(DAI_ADDR, address(curveExecutorExposed), amountIn);
|
||||
|
||||
bytes memory data = _getData(DAI_ADDR, USDC_ADDR, TRIPOOL, 1);
|
||||
bytes memory data = _getData(DAI_ADDR, USDC_ADDR, TRIPOOL, 1, ALICE);
|
||||
|
||||
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
|
||||
|
||||
assertEq(amountOut, 999797);
|
||||
assertEq(
|
||||
IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)),
|
||||
amountOut
|
||||
);
|
||||
assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), amountOut);
|
||||
}
|
||||
|
||||
function testStEthPool() public {
|
||||
@@ -106,14 +114,14 @@ contract CurveExecutorTest is Test, Constants {
|
||||
deal(address(curveExecutorExposed), amountIn);
|
||||
|
||||
bytes memory data =
|
||||
_getData(ETH_ADDR_FOR_CURVE, STETH_ADDR, STETH_POOL, 1);
|
||||
_getData(ETH_ADDR_FOR_CURVE, STETH_ADDR, STETH_POOL, 1, ALICE);
|
||||
|
||||
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
|
||||
|
||||
assertEq(amountOut, 1001072414418410897);
|
||||
assertEq(
|
||||
IERC20(STETH_ADDR).balanceOf(address(curveExecutorExposed)),
|
||||
amountOut
|
||||
IERC20(STETH_ADDR).balanceOf(ALICE),
|
||||
amountOut - 1 // there is something weird in this pool, but won't investigate for now because we don't currently support it in the simulation
|
||||
);
|
||||
}
|
||||
|
||||
@@ -122,15 +130,13 @@ contract CurveExecutorTest is Test, Constants {
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(WETH_ADDR, address(curveExecutorExposed), amountIn);
|
||||
|
||||
bytes memory data = _getData(WETH_ADDR, WBTC_ADDR, TRICRYPTO2_POOL, 3);
|
||||
bytes memory data =
|
||||
_getData(WETH_ADDR, WBTC_ADDR, TRICRYPTO2_POOL, 3, ALICE);
|
||||
|
||||
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
|
||||
|
||||
assertEq(amountOut, 2279618);
|
||||
assertEq(
|
||||
IERC20(WBTC_ADDR).balanceOf(address(curveExecutorExposed)),
|
||||
amountOut
|
||||
);
|
||||
assertEq(IERC20(WBTC_ADDR).balanceOf(ALICE), amountOut);
|
||||
}
|
||||
|
||||
function testSUSDPool() public {
|
||||
@@ -138,15 +144,12 @@ contract CurveExecutorTest is Test, Constants {
|
||||
uint256 amountIn = 100 * 10 ** 6;
|
||||
deal(USDC_ADDR, address(curveExecutorExposed), amountIn);
|
||||
|
||||
bytes memory data = _getData(USDC_ADDR, SUSD_ADDR, SUSD_POOL, 1);
|
||||
bytes memory data = _getData(USDC_ADDR, SUSD_ADDR, SUSD_POOL, 1, ALICE);
|
||||
|
||||
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
|
||||
|
||||
assertEq(amountOut, 100488101605550214590);
|
||||
assertEq(
|
||||
IERC20(SUSD_ADDR).balanceOf(address(curveExecutorExposed)),
|
||||
amountOut
|
||||
);
|
||||
assertEq(IERC20(SUSD_ADDR).balanceOf(ALICE), amountOut);
|
||||
}
|
||||
|
||||
function testFraxUsdcPool() public {
|
||||
@@ -154,15 +157,13 @@ contract CurveExecutorTest is Test, Constants {
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(FRAX_ADDR, address(curveExecutorExposed), amountIn);
|
||||
|
||||
bytes memory data = _getData(FRAX_ADDR, USDC_ADDR, FRAX_USDC_POOL, 1);
|
||||
bytes memory data =
|
||||
_getData(FRAX_ADDR, USDC_ADDR, FRAX_USDC_POOL, 1, ALICE);
|
||||
|
||||
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
|
||||
|
||||
assertEq(amountOut, 998097);
|
||||
assertEq(
|
||||
IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)),
|
||||
amountOut
|
||||
);
|
||||
assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), amountOut);
|
||||
}
|
||||
|
||||
function testUsdeUsdcPool() public {
|
||||
@@ -170,15 +171,13 @@ contract CurveExecutorTest is Test, Constants {
|
||||
uint256 amountIn = 100 * 10 ** 6;
|
||||
deal(USDC_ADDR, address(curveExecutorExposed), amountIn);
|
||||
|
||||
bytes memory data = _getData(USDC_ADDR, USDE_ADDR, USDE_USDC_POOL, 1);
|
||||
bytes memory data =
|
||||
_getData(USDC_ADDR, USDE_ADDR, USDE_USDC_POOL, 1, ALICE);
|
||||
|
||||
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
|
||||
|
||||
assertEq(amountOut, 100064812138999986170);
|
||||
assertEq(
|
||||
IERC20(USDE_ADDR).balanceOf(address(curveExecutorExposed)),
|
||||
amountOut
|
||||
);
|
||||
assertEq(IERC20(USDE_ADDR).balanceOf(ALICE), amountOut);
|
||||
}
|
||||
|
||||
function testDolaFraxPyusdPool() public {
|
||||
@@ -187,32 +186,27 @@ contract CurveExecutorTest is Test, Constants {
|
||||
deal(DOLA_ADDR, address(curveExecutorExposed), amountIn);
|
||||
|
||||
bytes memory data =
|
||||
_getData(DOLA_ADDR, FRAXPYUSD_POOL, DOLA_FRAXPYUSD_POOL, 1);
|
||||
_getData(DOLA_ADDR, FRAXPYUSD_POOL, DOLA_FRAXPYUSD_POOL, 1, ALICE);
|
||||
|
||||
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
|
||||
|
||||
assertEq(amountOut, 99688992);
|
||||
assertEq(
|
||||
IERC20(FRAXPYUSD_POOL).balanceOf(address(curveExecutorExposed)),
|
||||
amountOut
|
||||
);
|
||||
assertEq(IERC20(FRAXPYUSD_POOL).balanceOf(ALICE), amountOut);
|
||||
}
|
||||
|
||||
function testCryptoPoolWithETH() public {
|
||||
// Swapping XYO -> ETH on a CryptoPool, deployed by factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99
|
||||
uint256 amountIn = 1 ether;
|
||||
uint256 initialBalance = address(curveExecutorExposed).balance; // this address already has some ETH assigned to it
|
||||
uint256 initialBalance = address(ALICE).balance; // this address already has some ETH assigned to it
|
||||
deal(XYO_ADDR, address(curveExecutorExposed), amountIn);
|
||||
|
||||
bytes memory data =
|
||||
_getData(XYO_ADDR, ETH_ADDR_FOR_CURVE, ETH_XYO_POOL, 2);
|
||||
_getData(XYO_ADDR, ETH_ADDR_FOR_CURVE, ETH_XYO_POOL, 2, ALICE);
|
||||
|
||||
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
|
||||
|
||||
assertEq(amountOut, 6081816039338);
|
||||
assertEq(
|
||||
address(curveExecutorExposed).balance, initialBalance + amountOut
|
||||
);
|
||||
assertEq(ALICE.balance, initialBalance + amountOut);
|
||||
}
|
||||
|
||||
function testCryptoPool() public {
|
||||
@@ -220,15 +214,13 @@ contract CurveExecutorTest is Test, Constants {
|
||||
uint256 amountIn = 1000 ether;
|
||||
deal(BSGG_ADDR, address(curveExecutorExposed), amountIn);
|
||||
|
||||
bytes memory data = _getData(BSGG_ADDR, USDT_ADDR, BSGG_USDT_POOL, 2);
|
||||
bytes memory data =
|
||||
_getData(BSGG_ADDR, USDT_ADDR, BSGG_USDT_POOL, 2, ALICE);
|
||||
|
||||
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
|
||||
|
||||
assertEq(amountOut, 23429);
|
||||
assertEq(
|
||||
IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)),
|
||||
amountOut
|
||||
);
|
||||
assertEq(IERC20(USDT_ADDR).balanceOf(ALICE), amountOut);
|
||||
}
|
||||
|
||||
function testTricryptoPool() public {
|
||||
@@ -236,15 +228,13 @@ contract CurveExecutorTest is Test, Constants {
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(WETH_ADDR, address(curveExecutorExposed), amountIn);
|
||||
|
||||
bytes memory data = _getData(WETH_ADDR, USDC_ADDR, TRICRYPTO_POOL, 2);
|
||||
bytes memory data =
|
||||
_getData(WETH_ADDR, USDC_ADDR, TRICRYPTO_POOL, 2, ALICE);
|
||||
|
||||
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
|
||||
|
||||
assertEq(amountOut, 1861130974);
|
||||
assertEq(
|
||||
IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)),
|
||||
amountOut
|
||||
);
|
||||
assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), amountOut);
|
||||
}
|
||||
|
||||
function testTwoCryptoPool() public {
|
||||
@@ -252,32 +242,27 @@ contract CurveExecutorTest is Test, Constants {
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(UWU_ADDR, address(curveExecutorExposed), amountIn);
|
||||
|
||||
bytes memory data = _getData(UWU_ADDR, WETH_ADDR, UWU_WETH_POOL, 2);
|
||||
bytes memory data =
|
||||
_getData(UWU_ADDR, WETH_ADDR, UWU_WETH_POOL, 2, ALICE);
|
||||
|
||||
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
|
||||
|
||||
assertEq(amountOut, 2873786684675);
|
||||
assertEq(
|
||||
IERC20(WETH_ADDR).balanceOf(address(curveExecutorExposed)),
|
||||
amountOut
|
||||
);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), amountOut);
|
||||
}
|
||||
|
||||
function testStableSwapPool() public {
|
||||
// Swapping CRVUSD -> USDT on a StableSwap pool, deployed by factory 0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d (plain pool)
|
||||
uint256 amountIn = 1 ether;
|
||||
deal(CRVUSD_ADDR, address(curveExecutorExposed), amountIn);
|
||||
deal(USDT_ADDR, address(curveExecutorExposed), amountIn);
|
||||
|
||||
bytes memory data =
|
||||
_getData(CRVUSD_ADDR, USDT_ADDR, CRVUSD_USDT_POOL, 1);
|
||||
_getData(USDT_ADDR, CRVUSD_ADDR, CRVUSD_USDT_POOL, 1, ALICE);
|
||||
|
||||
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
|
||||
|
||||
assertEq(amountOut, 999910);
|
||||
assertEq(
|
||||
IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)),
|
||||
amountOut
|
||||
);
|
||||
assertEq(amountOut, 10436946786333182306400100);
|
||||
assertEq(IERC20(CRVUSD_ADDR).balanceOf(ALICE), amountOut);
|
||||
}
|
||||
|
||||
function testMetaPool() public {
|
||||
@@ -286,22 +271,20 @@ contract CurveExecutorTest is Test, Constants {
|
||||
deal(WTAO_ADDR, address(curveExecutorExposed), amountIn);
|
||||
|
||||
bytes memory data =
|
||||
_getData(WTAO_ADDR, WSTTAO_ADDR, WSTTAO_WTAO_POOL, 1);
|
||||
_getData(WTAO_ADDR, WSTTAO_ADDR, WSTTAO_WTAO_POOL, 1, ALICE);
|
||||
|
||||
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
|
||||
|
||||
assertEq(amountOut, 32797923610);
|
||||
assertEq(
|
||||
IERC20(WSTTAO_ADDR).balanceOf(address(curveExecutorExposed)),
|
||||
amountOut
|
||||
);
|
||||
assertEq(IERC20(WSTTAO_ADDR).balanceOf(ALICE), amountOut);
|
||||
}
|
||||
|
||||
function _getData(
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
address pool,
|
||||
uint8 poolType
|
||||
uint8 poolType,
|
||||
address receiver
|
||||
) internal view returns (bytes memory data) {
|
||||
(int128 i, int128 j) = _getIndexes(tokenIn, tokenOut, pool);
|
||||
data = abi.encodePacked(
|
||||
@@ -311,7 +294,9 @@ contract CurveExecutorTest is Test, Constants {
|
||||
poolType,
|
||||
uint8(uint256(uint128(i))),
|
||||
uint8(uint256(uint128(j))),
|
||||
true
|
||||
true,
|
||||
TokenTransfer.TransferType.NONE,
|
||||
receiver
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import {EkuboExecutor} from "@src/executors/EkuboExecutor.sol";
|
||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "../TestUtils.sol";
|
||||
import {Constants} from "../Constants.sol";
|
||||
import {Test, console} from "forge-std/Test.sol";
|
||||
import {NATIVE_TOKEN_ADDRESS} from "@ekubo/math/constants.sol";
|
||||
import {EkuboExecutor, TokenTransfer} from "@src/executors/EkuboExecutor.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";
|
||||
import {console} from "forge-std/Test.sol";
|
||||
|
||||
contract EkuboExecutorTest is Test, Constants {
|
||||
contract EkuboExecutorTest is Constants, TestUtils {
|
||||
address constant EXECUTOR_ADDRESS =
|
||||
0xcA4F73Fe97D0B987a0D12B39BBD562c779BAb6f6; // Same address as in swap_encoder.rs tests
|
||||
EkuboExecutor executor;
|
||||
@@ -26,7 +27,7 @@ contract EkuboExecutorTest is Test, Constants {
|
||||
|
||||
deployCodeTo(
|
||||
"executors/EkuboExecutor.sol",
|
||||
abi.encode(CORE_ADDRESS),
|
||||
abi.encode(CORE_ADDRESS, PERMIT2_ADDRESS),
|
||||
EXECUTOR_ADDRESS
|
||||
);
|
||||
executor = EkuboExecutor(payable(EXECUTOR_ADDRESS));
|
||||
@@ -44,6 +45,7 @@ contract EkuboExecutorTest is Test, Constants {
|
||||
uint256 usdcBalanceBeforeExecutor = USDC.balanceOf(address(executor));
|
||||
|
||||
bytes memory data = abi.encodePacked(
|
||||
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core)
|
||||
address(executor), // receiver
|
||||
NATIVE_TOKEN_ADDRESS, // tokenIn
|
||||
USDC_ADDR, // tokenOut
|
||||
@@ -80,6 +82,7 @@ contract EkuboExecutorTest is Test, Constants {
|
||||
uint256 ethBalanceBeforeExecutor = address(executor).balance;
|
||||
|
||||
bytes memory data = abi.encodePacked(
|
||||
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core)
|
||||
address(executor), // receiver
|
||||
USDC_ADDR, // tokenIn
|
||||
NATIVE_TOKEN_ADDRESS, // tokenOut
|
||||
@@ -137,6 +140,7 @@ contract EkuboExecutorTest is Test, Constants {
|
||||
// Same test case as in swap_encoder::tests::ekubo::test_encode_swap_multi
|
||||
function testMultiHopSwap() public {
|
||||
bytes memory data = abi.encodePacked(
|
||||
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType
|
||||
address(executor), // receiver
|
||||
NATIVE_TOKEN_ADDRESS, // tokenIn
|
||||
USDC_ADDR, // tokenOut of 1st swap
|
||||
@@ -151,8 +155,6 @@ contract EkuboExecutorTest is Test, Constants {
|
||||
|
||||
// Data is generated by test case in swap_encoder::tests::ekubo::test_encode_swap_multi
|
||||
function testMultiHopSwapIntegration() public {
|
||||
multiHopSwap(
|
||||
hex"ca4f73fe97d0b987a0d12b39bbd562c779bab6f60000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000001a36e2eb1c43200000032"
|
||||
);
|
||||
multiHopSwap(loadCallDataFromFile("test_ekubo_encode_swap_multi"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import "@src/executors/UniswapV2Executor.sol";
|
||||
import "@src/executors/TokenTransfer.sol";
|
||||
import {Test} from "../../lib/forge-std/src/Test.sol";
|
||||
import {Constants} from "../Constants.sol";
|
||||
import {Permit2TestHelper} from "../Permit2TestHelper.sol";
|
||||
|
||||
contract UniswapV2ExecutorExposed is UniswapV2Executor {
|
||||
constructor(address _factory, bytes32 _initCode)
|
||||
UniswapV2Executor(_factory, _initCode)
|
||||
{}
|
||||
constructor(
|
||||
address _factory,
|
||||
bytes32 _initCode,
|
||||
address _permit2,
|
||||
uint256 _feeBps
|
||||
) UniswapV2Executor(_factory, _initCode, _permit2, _feeBps) {}
|
||||
|
||||
function decodeParams(bytes calldata data)
|
||||
external
|
||||
@@ -17,7 +22,8 @@ contract UniswapV2ExecutorExposed is UniswapV2Executor {
|
||||
IERC20 inToken,
|
||||
address target,
|
||||
address receiver,
|
||||
bool zeroForOne
|
||||
bool zeroForOne,
|
||||
TransferType transferType
|
||||
)
|
||||
{
|
||||
return _decodeData(data);
|
||||
@@ -46,7 +52,7 @@ contract FakeUniswapV2Pool {
|
||||
}
|
||||
}
|
||||
|
||||
contract UniswapV2ExecutorTest is Test, Constants {
|
||||
contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
UniswapV2ExecutorExposed uniswapV2Exposed;
|
||||
@@ -54,32 +60,54 @@ contract UniswapV2ExecutorTest is Test, Constants {
|
||||
UniswapV2ExecutorExposed pancakeswapV2Exposed;
|
||||
IERC20 WETH = IERC20(WETH_ADDR);
|
||||
IERC20 DAI = IERC20(DAI_ADDR);
|
||||
IAllowanceTransfer permit2;
|
||||
|
||||
function setUp() public {
|
||||
uint256 forkBlock = 17323404;
|
||||
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
||||
uniswapV2Exposed = new UniswapV2ExecutorExposed(
|
||||
USV2_FACTORY_ETHEREUM, USV2_POOL_CODE_INIT_HASH
|
||||
USV2_FACTORY_ETHEREUM, USV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS, 30
|
||||
);
|
||||
sushiswapV2Exposed = new UniswapV2ExecutorExposed(
|
||||
SUSHISWAPV2_FACTORY_ETHEREUM, SUSHIV2_POOL_CODE_INIT_HASH
|
||||
SUSHISWAPV2_FACTORY_ETHEREUM,
|
||||
SUSHIV2_POOL_CODE_INIT_HASH,
|
||||
PERMIT2_ADDRESS,
|
||||
30
|
||||
);
|
||||
pancakeswapV2Exposed = new UniswapV2ExecutorExposed(
|
||||
PANCAKESWAPV2_FACTORY_ETHEREUM, PANCAKEV2_POOL_CODE_INIT_HASH
|
||||
PANCAKESWAPV2_FACTORY_ETHEREUM,
|
||||
PANCAKEV2_POOL_CODE_INIT_HASH,
|
||||
PERMIT2_ADDRESS,
|
||||
25
|
||||
);
|
||||
permit2 = IAllowanceTransfer(PERMIT2_ADDRESS);
|
||||
}
|
||||
|
||||
function testDecodeParams() public view {
|
||||
bytes memory params =
|
||||
abi.encodePacked(WETH_ADDR, address(2), address(3), false);
|
||||
bytes memory params = abi.encodePacked(
|
||||
WETH_ADDR,
|
||||
address(2),
|
||||
address(3),
|
||||
false,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
);
|
||||
|
||||
(IERC20 tokenIn, address target, address receiver, bool zeroForOne) =
|
||||
uniswapV2Exposed.decodeParams(params);
|
||||
(
|
||||
IERC20 tokenIn,
|
||||
address target,
|
||||
address receiver,
|
||||
bool zeroForOne,
|
||||
TokenTransfer.TransferType transferType
|
||||
) = uniswapV2Exposed.decodeParams(params);
|
||||
|
||||
assertEq(address(tokenIn), WETH_ADDR);
|
||||
assertEq(target, address(2));
|
||||
assertEq(receiver, address(3));
|
||||
assertEq(zeroForOne, false);
|
||||
assertEq(
|
||||
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL),
|
||||
uint8(transferType)
|
||||
);
|
||||
}
|
||||
|
||||
function testDecodeParamsInvalidDataLength() public {
|
||||
@@ -126,12 +154,17 @@ contract UniswapV2ExecutorTest is Test, Constants {
|
||||
assertGe(amountOut, 0);
|
||||
}
|
||||
|
||||
function testSwap() public {
|
||||
function testSwapWithTransfer() public {
|
||||
uint256 amountIn = 10 ** 18;
|
||||
uint256 amountOut = 1847751195973566072891;
|
||||
bool zeroForOne = false;
|
||||
bytes memory protocolData =
|
||||
abi.encodePacked(WETH_ADDR, WETH_DAI_POOL, BOB, zeroForOne);
|
||||
bytes memory protocolData = abi.encodePacked(
|
||||
WETH_ADDR,
|
||||
WETH_DAI_POOL,
|
||||
BOB,
|
||||
zeroForOne,
|
||||
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
|
||||
);
|
||||
|
||||
deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);
|
||||
uniswapV2Exposed.swap(amountIn, protocolData);
|
||||
@@ -140,24 +173,102 @@ contract UniswapV2ExecutorTest is Test, Constants {
|
||||
assertGe(finalBalance, amountOut);
|
||||
}
|
||||
|
||||
function testDecodeIntegration() public view {
|
||||
// Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode
|
||||
bytes memory protocolData =
|
||||
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f5640000000000000000000000000000000000000000100";
|
||||
function testSwapWithTransferFrom() public {
|
||||
uint256 amountIn = 10 ** 18;
|
||||
uint256 amountOut = 1847751195973566072891;
|
||||
bool zeroForOne = false;
|
||||
bytes memory protocolData = abi.encodePacked(
|
||||
WETH_ADDR,
|
||||
WETH_DAI_POOL,
|
||||
BOB,
|
||||
zeroForOne,
|
||||
uint8(TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL)
|
||||
);
|
||||
|
||||
(IERC20 tokenIn, address target, address receiver, bool zeroForOne) =
|
||||
uniswapV2Exposed.decodeParams(protocolData);
|
||||
deal(WETH_ADDR, address(this), amountIn);
|
||||
IERC20(WETH_ADDR).approve(address(uniswapV2Exposed), amountIn);
|
||||
|
||||
uniswapV2Exposed.swap(amountIn, protocolData);
|
||||
|
||||
uint256 finalBalance = DAI.balanceOf(BOB);
|
||||
assertGe(finalBalance, amountOut);
|
||||
}
|
||||
|
||||
function testSwapWithPermit2TransferFrom() public {
|
||||
uint256 amountIn = 10 ** 18;
|
||||
uint256 amountOut = 1847751195973566072891;
|
||||
bool zeroForOne = false;
|
||||
bytes memory protocolData = abi.encodePacked(
|
||||
WETH_ADDR,
|
||||
WETH_DAI_POOL,
|
||||
ALICE,
|
||||
zeroForOne,
|
||||
uint8(TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL)
|
||||
);
|
||||
|
||||
deal(WETH_ADDR, ALICE, amountIn);
|
||||
vm.startPrank(ALICE);
|
||||
(
|
||||
IAllowanceTransfer.PermitSingle memory permitSingle,
|
||||
bytes memory signature
|
||||
) = handlePermit2Approval(
|
||||
WETH_ADDR, address(uniswapV2Exposed), amountIn
|
||||
);
|
||||
|
||||
// Assume the permit2.approve method will be called from the TychoRouter
|
||||
// Replicate this scenario in this test.
|
||||
permit2.permit(ALICE, permitSingle, signature);
|
||||
|
||||
uniswapV2Exposed.swap(amountIn, protocolData);
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 finalBalance = DAI.balanceOf(ALICE);
|
||||
assertGe(finalBalance, amountOut);
|
||||
}
|
||||
|
||||
function testSwapNoTransfer() public {
|
||||
uint256 amountIn = 10 ** 18;
|
||||
uint256 amountOut = 1847751195973566072891;
|
||||
bool zeroForOne = false;
|
||||
bytes memory protocolData = abi.encodePacked(
|
||||
WETH_ADDR,
|
||||
WETH_DAI_POOL,
|
||||
BOB,
|
||||
zeroForOne,
|
||||
uint8(TokenTransfer.TransferType.NONE)
|
||||
);
|
||||
|
||||
deal(WETH_ADDR, address(this), amountIn);
|
||||
IERC20(WETH_ADDR).transfer(address(WETH_DAI_POOL), amountIn);
|
||||
uniswapV2Exposed.swap(amountIn, protocolData);
|
||||
|
||||
uint256 finalBalance = DAI.balanceOf(BOB);
|
||||
assertGe(finalBalance, amountOut);
|
||||
}
|
||||
|
||||
function testDecodeIntegration() public view {
|
||||
bytes memory protocolData =
|
||||
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f564000000000000000000000000000000000000000010000";
|
||||
|
||||
(
|
||||
IERC20 tokenIn,
|
||||
address target,
|
||||
address receiver,
|
||||
bool zeroForOne,
|
||||
TokenTransfer.TransferType transferType
|
||||
) = uniswapV2Exposed.decodeParams(protocolData);
|
||||
|
||||
assertEq(address(tokenIn), WETH_ADDR);
|
||||
assertEq(target, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640);
|
||||
assertEq(receiver, 0x0000000000000000000000000000000000000001);
|
||||
assertEq(zeroForOne, false);
|
||||
// TRANSFER = 0
|
||||
assertEq(0, uint8(transferType));
|
||||
}
|
||||
|
||||
function testSwapIntegration() public {
|
||||
// Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode
|
||||
bytes memory protocolData =
|
||||
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e00";
|
||||
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0000";
|
||||
uint256 amountIn = 10 ** 18;
|
||||
uint256 amountOut = 1847751195973566072891;
|
||||
deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);
|
||||
@@ -171,8 +282,13 @@ contract UniswapV2ExecutorTest is Test, Constants {
|
||||
uint256 amountIn = 10 ** 18;
|
||||
bool zeroForOne = false;
|
||||
address fakePool = address(new FakeUniswapV2Pool(WETH_ADDR, DAI_ADDR));
|
||||
bytes memory protocolData =
|
||||
abi.encodePacked(WETH_ADDR, fakePool, BOB, zeroForOne);
|
||||
bytes memory protocolData = abi.encodePacked(
|
||||
WETH_ADDR,
|
||||
fakePool,
|
||||
BOB,
|
||||
zeroForOne,
|
||||
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
|
||||
);
|
||||
|
||||
deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);
|
||||
vm.expectRevert(UniswapV2Executor__InvalidTarget.selector);
|
||||
@@ -186,8 +302,13 @@ contract UniswapV2ExecutorTest is Test, Constants {
|
||||
vm.rollFork(26857267);
|
||||
uint256 amountIn = 10 * 10 ** 6;
|
||||
bool zeroForOne = true;
|
||||
bytes memory protocolData =
|
||||
abi.encodePacked(BASE_USDC, USDC_MAG7_POOL, BOB, zeroForOne);
|
||||
bytes memory protocolData = abi.encodePacked(
|
||||
BASE_USDC,
|
||||
USDC_MAG7_POOL,
|
||||
BOB,
|
||||
zeroForOne,
|
||||
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
|
||||
);
|
||||
|
||||
deal(BASE_USDC, address(uniswapV2Exposed), amountIn);
|
||||
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import "@src/executors/UniswapV3Executor.sol";
|
||||
import "@permit2/src/interfaces/IAllowanceTransfer.sol";
|
||||
import {Test} from "../../lib/forge-std/src/Test.sol";
|
||||
import {Constants} from "../Constants.sol";
|
||||
import {Permit2TestHelper} from "../Permit2TestHelper.sol";
|
||||
|
||||
contract UniswapV3ExecutorExposed is UniswapV3Executor {
|
||||
constructor(address _factory, bytes32 _initCode)
|
||||
UniswapV3Executor(_factory, _initCode)
|
||||
constructor(address _factory, bytes32 _initCode, address _permit2)
|
||||
UniswapV3Executor(_factory, _initCode, _permit2)
|
||||
{}
|
||||
|
||||
function decodeData(bytes calldata data)
|
||||
@@ -19,7 +21,8 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor {
|
||||
uint24 fee,
|
||||
address receiver,
|
||||
address target,
|
||||
bool zeroForOne
|
||||
bool zeroForOne,
|
||||
TransferType transferType
|
||||
)
|
||||
{
|
||||
return _decodeData(data);
|
||||
@@ -35,30 +38,40 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor {
|
||||
}
|
||||
}
|
||||
|
||||
contract UniswapV3ExecutorTest is Test, Constants {
|
||||
contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
UniswapV3ExecutorExposed uniswapV3Exposed;
|
||||
UniswapV3ExecutorExposed pancakeV3Exposed;
|
||||
IERC20 WETH = IERC20(WETH_ADDR);
|
||||
IERC20 DAI = IERC20(DAI_ADDR);
|
||||
IAllowanceTransfer permit2;
|
||||
|
||||
function setUp() public {
|
||||
uint256 forkBlock = 17323404;
|
||||
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
||||
|
||||
uniswapV3Exposed = new UniswapV3ExecutorExposed(
|
||||
USV3_FACTORY_ETHEREUM, USV3_POOL_CODE_INIT_HASH
|
||||
USV3_FACTORY_ETHEREUM, USV3_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS
|
||||
);
|
||||
pancakeV3Exposed = new UniswapV3ExecutorExposed(
|
||||
PANCAKESWAPV3_DEPLOYER_ETHEREUM, PANCAKEV3_POOL_CODE_INIT_HASH
|
||||
PANCAKESWAPV3_DEPLOYER_ETHEREUM,
|
||||
PANCAKEV3_POOL_CODE_INIT_HASH,
|
||||
PERMIT2_ADDRESS
|
||||
);
|
||||
permit2 = IAllowanceTransfer(PERMIT2_ADDRESS);
|
||||
}
|
||||
|
||||
function testDecodeParams() public view {
|
||||
uint24 expectedPoolFee = 500;
|
||||
bytes memory data = abi.encodePacked(
|
||||
WETH_ADDR, DAI_ADDR, expectedPoolFee, address(2), address(3), false
|
||||
WETH_ADDR,
|
||||
DAI_ADDR,
|
||||
expectedPoolFee,
|
||||
address(2),
|
||||
address(3),
|
||||
false,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
);
|
||||
|
||||
(
|
||||
@@ -67,7 +80,8 @@ contract UniswapV3ExecutorTest is Test, Constants {
|
||||
uint24 fee,
|
||||
address receiver,
|
||||
address target,
|
||||
bool zeroForOne
|
||||
bool zeroForOne,
|
||||
TokenTransfer.TransferType transferType
|
||||
) = uniswapV3Exposed.decodeData(data);
|
||||
|
||||
assertEq(tokenIn, WETH_ADDR);
|
||||
@@ -76,6 +90,33 @@ contract UniswapV3ExecutorTest is Test, Constants {
|
||||
assertEq(receiver, address(2));
|
||||
assertEq(target, address(3));
|
||||
assertEq(zeroForOne, false);
|
||||
assertEq(
|
||||
uint8(transferType),
|
||||
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
|
||||
);
|
||||
}
|
||||
|
||||
function testSwapIntegration() public {
|
||||
uint256 amountIn = 10 ** 18;
|
||||
deal(WETH_ADDR, address(uniswapV3Exposed), amountIn);
|
||||
|
||||
uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI
|
||||
bool zeroForOne = false;
|
||||
|
||||
bytes memory data = encodeUniswapV3Swap(
|
||||
WETH_ADDR,
|
||||
DAI_ADDR,
|
||||
address(this),
|
||||
DAI_WETH_USV3,
|
||||
zeroForOne,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
);
|
||||
|
||||
uint256 amountOut = uniswapV3Exposed.swap(amountIn, data);
|
||||
|
||||
assertGe(amountOut, expAmountOut);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(address(uniswapV3Exposed)), 0);
|
||||
assertGe(IERC20(DAI_ADDR).balanceOf(address(this)), expAmountOut);
|
||||
}
|
||||
|
||||
function testDecodeParamsInvalidDataLength() public {
|
||||
@@ -105,12 +146,18 @@ contract UniswapV3ExecutorTest is Test, Constants {
|
||||
uint256 initialPoolReserve = IERC20(WETH_ADDR).balanceOf(DAI_WETH_USV3);
|
||||
|
||||
vm.startPrank(DAI_WETH_USV3);
|
||||
bytes memory protocolData =
|
||||
abi.encodePacked(WETH_ADDR, DAI_ADDR, poolFee);
|
||||
bytes memory protocolData = abi.encodePacked(
|
||||
WETH_ADDR,
|
||||
DAI_ADDR,
|
||||
poolFee,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL,
|
||||
address(uniswapV3Exposed)
|
||||
);
|
||||
uint256 dataOffset = 3; // some offset
|
||||
uint256 dataLength = protocolData.length;
|
||||
|
||||
bytes memory callbackData = abi.encodePacked(
|
||||
bytes4(0xfa461e33),
|
||||
int256(amountOwed), // amount0Delta
|
||||
int256(0), // amount1Delta
|
||||
dataOffset,
|
||||
@@ -124,24 +171,6 @@ contract UniswapV3ExecutorTest is Test, Constants {
|
||||
assertEq(finalPoolReserve - initialPoolReserve, amountOwed);
|
||||
}
|
||||
|
||||
function testSwapIntegration() public {
|
||||
uint256 amountIn = 10 ** 18;
|
||||
deal(WETH_ADDR, address(uniswapV3Exposed), amountIn);
|
||||
|
||||
uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI
|
||||
bool zeroForOne = false;
|
||||
|
||||
bytes memory data = encodeUniswapV3Swap(
|
||||
WETH_ADDR, DAI_ADDR, address(this), DAI_WETH_USV3, zeroForOne
|
||||
);
|
||||
|
||||
uint256 amountOut = uniswapV3Exposed.swap(amountIn, data);
|
||||
|
||||
assertGe(amountOut, expAmountOut);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(address(uniswapV3Exposed)), 0);
|
||||
assertGe(IERC20(DAI_ADDR).balanceOf(address(this)), expAmountOut);
|
||||
}
|
||||
|
||||
function testSwapFailureInvalidTarget() public {
|
||||
uint256 amountIn = 10 ** 18;
|
||||
deal(WETH_ADDR, address(uniswapV3Exposed), amountIn);
|
||||
@@ -154,7 +183,8 @@ contract UniswapV3ExecutorTest is Test, Constants {
|
||||
uint24(3000),
|
||||
address(this),
|
||||
fakePool,
|
||||
zeroForOne
|
||||
zeroForOne,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
|
||||
);
|
||||
|
||||
vm.expectRevert(UniswapV3Executor__InvalidTarget.selector);
|
||||
@@ -166,11 +196,18 @@ contract UniswapV3ExecutorTest is Test, Constants {
|
||||
address tokenOut,
|
||||
address receiver,
|
||||
address target,
|
||||
bool zero2one
|
||||
bool zero2one,
|
||||
TokenTransfer.TransferType transferType
|
||||
) internal view returns (bytes memory) {
|
||||
IUniswapV3Pool pool = IUniswapV3Pool(target);
|
||||
return abi.encodePacked(
|
||||
tokenIn, tokenOut, pool.fee(), receiver, target, zero2one
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
pool.fee(),
|
||||
receiver,
|
||||
target,
|
||||
zero2one,
|
||||
transferType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,18 @@
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import "../../src/executors/UniswapV4Executor.sol";
|
||||
import "../TestUtils.sol";
|
||||
import "./UniswapV4Utils.sol";
|
||||
import "@src/executors/TokenTransfer.sol";
|
||||
import "@src/executors/UniswapV4Executor.sol";
|
||||
import {Constants} from "../Constants.sol";
|
||||
import {Test} from "../../lib/forge-std/src/Test.sol";
|
||||
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
|
||||
import {Test} from "../../lib/forge-std/src/Test.sol";
|
||||
|
||||
contract UniswapV4ExecutorExposed is UniswapV4Executor {
|
||||
constructor(IPoolManager _poolManager) UniswapV4Executor(_poolManager) {}
|
||||
constructor(IPoolManager _poolManager, address _permit2)
|
||||
UniswapV4Executor(_poolManager, _permit2)
|
||||
{}
|
||||
|
||||
function decodeData(bytes calldata data)
|
||||
external
|
||||
@@ -18,7 +22,8 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor {
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
bool zeroForOne,
|
||||
address callbackExecutor,
|
||||
TokenTransfer.TransferType transferType,
|
||||
address receiver,
|
||||
UniswapV4Pool[] memory pools
|
||||
)
|
||||
{
|
||||
@@ -26,7 +31,7 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor {
|
||||
}
|
||||
}
|
||||
|
||||
contract UniswapV4ExecutorTest is Test, Constants {
|
||||
contract UniswapV4ExecutorTest is Constants, TestUtils {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
UniswapV4ExecutorExposed uniswapV4Exposed;
|
||||
@@ -37,8 +42,9 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
||||
function setUp() public {
|
||||
uint256 forkBlock = 21817316;
|
||||
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
||||
uniswapV4Exposed =
|
||||
new UniswapV4ExecutorExposed(IPoolManager(poolManager));
|
||||
uniswapV4Exposed = new UniswapV4ExecutorExposed(
|
||||
IPoolManager(poolManager), PERMIT2_ADDRESS
|
||||
);
|
||||
}
|
||||
|
||||
function testDecodeParams() public view {
|
||||
@@ -47,6 +53,8 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
||||
int24 tickSpacing1 = 60;
|
||||
uint24 pool2Fee = 1000;
|
||||
int24 tickSpacing2 = -10;
|
||||
TokenTransfer.TransferType transferType =
|
||||
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL;
|
||||
|
||||
UniswapV4Executor.UniswapV4Pool[] memory pools =
|
||||
new UniswapV4Executor.UniswapV4Pool[](2);
|
||||
@@ -62,21 +70,23 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
||||
});
|
||||
|
||||
bytes memory data = UniswapV4Utils.encodeExactInput(
|
||||
USDE_ADDR, USDT_ADDR, zeroForOne, address(uniswapV4Exposed), pools
|
||||
USDE_ADDR, USDT_ADDR, zeroForOne, transferType, ALICE, pools
|
||||
);
|
||||
|
||||
(
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
bool zeroForOneDecoded,
|
||||
address callbackExecutor,
|
||||
TokenTransfer.TransferType transferTypeDecoded,
|
||||
address receiver,
|
||||
UniswapV4Executor.UniswapV4Pool[] memory decodedPools
|
||||
) = uniswapV4Exposed.decodeData(data);
|
||||
|
||||
assertEq(tokenIn, USDE_ADDR);
|
||||
assertEq(tokenOut, USDT_ADDR);
|
||||
assertEq(zeroForOneDecoded, zeroForOne);
|
||||
assertEq(callbackExecutor, address(uniswapV4Exposed));
|
||||
assertEq(uint8(transferTypeDecoded), uint8(transferType));
|
||||
assertEq(receiver, ALICE);
|
||||
assertEq(decodedPools.length, 2);
|
||||
assertEq(decodedPools[0].intermediaryToken, USDT_ADDR);
|
||||
assertEq(decodedPools[0].fee, pool1Fee);
|
||||
@@ -102,7 +112,12 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
||||
});
|
||||
|
||||
bytes memory data = UniswapV4Utils.encodeExactInput(
|
||||
USDE_ADDR, USDT_ADDR, true, address(uniswapV4Exposed), pools
|
||||
USDE_ADDR,
|
||||
USDT_ADDR,
|
||||
true,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL,
|
||||
ALICE,
|
||||
pools
|
||||
);
|
||||
|
||||
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
|
||||
@@ -111,14 +126,13 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
||||
USDE.balanceOf(address(uniswapV4Exposed)),
|
||||
usdeBalanceBeforeSwapExecutor - amountIn
|
||||
);
|
||||
assertTrue(USDT.balanceOf(address(uniswapV4Exposed)) == amountOut);
|
||||
assertTrue(USDT.balanceOf(ALICE) == amountOut);
|
||||
}
|
||||
|
||||
function testSingleSwapIntegration() public {
|
||||
// USDE -> USDT
|
||||
// Generated by the Tycho swap encoder - test_encode_uniswap_v4_simple_swap
|
||||
bytes memory protocolData =
|
||||
hex"4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec701f62849f9a0b5bf2913b396098f7c7019b51a820adac17f958d2ee523a2206206994597c13d831ec7000064000001";
|
||||
loadCallDataFromFile("test_encode_uniswap_v4_simple_swap");
|
||||
uint256 amountIn = 100 ether;
|
||||
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);
|
||||
uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager);
|
||||
@@ -128,10 +142,9 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
||||
uint256 amountOut = uniswapV4Exposed.swap(amountIn, protocolData);
|
||||
assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn);
|
||||
assertEq(
|
||||
USDE.balanceOf(address(uniswapV4Exposed)),
|
||||
usdeBalanceBeforeSwapExecutor - amountIn
|
||||
USDE.balanceOf(ALICE), usdeBalanceBeforeSwapExecutor - amountIn
|
||||
);
|
||||
assertTrue(USDT.balanceOf(address(uniswapV4Exposed)) == amountOut);
|
||||
assertTrue(USDT.balanceOf(ALICE) == amountOut);
|
||||
}
|
||||
|
||||
function testMultipleSwap() public {
|
||||
@@ -156,7 +169,12 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
||||
});
|
||||
|
||||
bytes memory data = UniswapV4Utils.encodeExactInput(
|
||||
USDE_ADDR, WBTC_ADDR, true, address(uniswapV4Exposed), pools
|
||||
USDE_ADDR,
|
||||
WBTC_ADDR,
|
||||
true,
|
||||
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL,
|
||||
ALICE,
|
||||
pools
|
||||
);
|
||||
|
||||
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
|
||||
@@ -165,17 +183,13 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
||||
USDE.balanceOf(address(uniswapV4Exposed)),
|
||||
usdeBalanceBeforeSwapExecutor - amountIn
|
||||
);
|
||||
assertTrue(
|
||||
IERC20(WBTC_ADDR).balanceOf(address(uniswapV4Exposed)) == amountOut
|
||||
);
|
||||
assertTrue(IERC20(WBTC_ADDR).balanceOf(ALICE) == amountOut);
|
||||
}
|
||||
|
||||
function testMultipleSwapIntegration() public {
|
||||
// USDE -> USDT -> WBTC
|
||||
// Generated by the Tycho swap encoder - test_encode_uniswap_v4_sequential_swap
|
||||
|
||||
bytes memory protocolData =
|
||||
hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c59901f62849f9a0b5bf2913b396098f7c7019b51a820adac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c";
|
||||
loadCallDataFromFile("test_encode_uniswap_v4_sequential_swap");
|
||||
|
||||
uint256 amountIn = 100 ether;
|
||||
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);
|
||||
@@ -189,8 +203,6 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
||||
USDE.balanceOf(address(uniswapV4Exposed)),
|
||||
usdeBalanceBeforeSwapExecutor - amountIn
|
||||
);
|
||||
assertTrue(
|
||||
IERC20(WBTC_ADDR).balanceOf(address(uniswapV4Exposed)) == amountOut
|
||||
);
|
||||
assertTrue(IERC20(WBTC_ADDR).balanceOf(ALICE) == amountOut);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ library UniswapV4Utils {
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
bool zeroForOne,
|
||||
address callbackExecutor,
|
||||
UniswapV4Executor.TransferType transferType,
|
||||
address receiver,
|
||||
UniswapV4Executor.UniswapV4Pool[] memory pools
|
||||
) public pure returns (bytes memory) {
|
||||
bytes memory encodedPools;
|
||||
@@ -23,7 +24,7 @@ library UniswapV4Utils {
|
||||
}
|
||||
|
||||
return abi.encodePacked(
|
||||
tokenIn, tokenOut, zeroForOne, callbackExecutor, encodedPools
|
||||
tokenIn, tokenOut, zeroForOne, transferType, receiver, encodedPools
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user