feat: Decode single and sequential swaps in LibSwap

- The old way was useful when we just had split swaps. Unfortunately, we now have split, sequential, and single swaps, which don't always require token indices or split percentages, so we need to decode differently for each case.
This commit is contained in:
TAMARA LIPOWSKI
2025-03-18 01:37:14 -04:00
committed by Diana Carvalho
parent 3ae9d3ad67
commit 1dad4afb6b
5 changed files with 148 additions and 111 deletions

View File

@@ -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);
}
}

View File

@@ -230,7 +230,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
}
function testSingleSwapSimplePermit2() public {
function testSingleSwapPermit2() public {
// Trade 1 WETH for DAI with 1 swap on Uniswap V2 using Permit2
// 1 WETH -> DAI
// (USV2)
@@ -247,24 +247,20 @@ contract TychoRouterTest is TychoRouterTestSetup {
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
);
bytes memory swap = encodeSplitSwap(
uint8(0), uint8(1), uint24(0), address(usv2Executor), protocolData
);
bytes[] memory swaps = new bytes[](1);
swaps[0] = swap;
bytes memory swap =
encodeSingleSwap(address(usv2Executor), protocolData);
tychoRouter.splitSwapPermit2(
tychoRouter.singleSwapPermit2(
amountIn,
WETH_ADDR,
DAI_ADDR,
2659881924818443699786,
false,
false,
2,
ALICE,
permitSingle,
signature,
pleEncode(swaps)
swap
);
uint256 daiBalance = IERC20(DAI_ADDR).balanceOf(ALICE);
@@ -274,7 +270,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
vm.stopPrank();
}
function testSequentialSwapMultipleHops() public {
function testSequentialSwap() public {
// Trade 1 WETH for USDC through DAI with 2 swaps on Uniswap V2
// 1 WETH -> DAI -> USDC
// (univ2) (univ2)
@@ -283,10 +279,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
bytes[] memory swaps = new bytes[](2);
// WETH -> DAI
swaps[0] = encodeSplitSwap(
uint8(0),
uint8(1),
uint24(0),
swaps[0] = encodeSequentialSwap(
address(usv2Executor),
encodeUniswapV2Swap(
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
@@ -294,10 +287,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
);
// DAI -> USDC
swaps[1] = encodeSplitSwap(
uint8(1),
uint8(2),
uint24(0),
swaps[1] = encodeSequentialSwap(
address(usv2Executor),
encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, tychoRouterAddr, true)
);
@@ -384,9 +374,8 @@ contract TychoRouterTest is TychoRouterTestSetup {
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
);
bytes memory swap = encodeSplitSwap(
uint8(0), uint8(1), uint24(0), address(usv2Executor), protocolData
);
bytes memory swap =
encodeSingleSwap(address(usv2Executor), protocolData);
uint256 minAmountOut = 2600 * 1e18;
uint256 amountOut = tychoRouter.singleSwapPermit2(
@@ -396,7 +385,6 @@ contract TychoRouterTest is TychoRouterTestSetup {
minAmountOut,
false,
false,
2,
ALICE,
permitSingle,
signature,
@@ -466,9 +454,8 @@ contract TychoRouterTest is TychoRouterTestSetup {
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
);
bytes memory swap = encodeSplitSwap(
uint8(0), uint8(1), uint24(0), address(usv2Executor), protocolData
);
bytes memory swap =
encodeSingleSwap(address(usv2Executor), protocolData);
uint256 minAmountOut = 2600 * 1e18;
uint256 amountOut = tychoRouter.singleSwap(
@@ -478,7 +465,6 @@ contract TychoRouterTest is TychoRouterTestSetup {
minAmountOut,
false,
false,
2,
ALICE,
swap
);
@@ -979,27 +965,18 @@ contract TychoRouterTest is TychoRouterTestSetup {
bytes[] memory swaps = new bytes[](2);
// USDC -> WETH
swaps[0] = encodeSplitSwap(
uint8(0),
uint8(1),
uint24(0),
address(usv3Executor),
usdcWethV3Pool1ZeroOneData
swaps[0] = encodeSequentialSwap(
address(usv3Executor), usdcWethV3Pool1ZeroOneData
);
// WETH -> USDC
swaps[1] = encodeSplitSwap(
uint8(1),
uint8(0),
uint24(0),
address(usv3Executor),
usdcWethV3Pool2OneZeroData
swaps[1] = encodeSequentialSwap(
address(usv3Executor), usdcWethV3Pool2OneZeroData
);
tychoRouter.exposedSequentialSwap(amountIn, pleEncode(swaps));
assertEq(IERC20(USDC_ADDR).balanceOf(tychoRouterAddr), 99889294);
}
function testSplitInputCyclicSwap() public {
// This test has start and end tokens that are the same
// The flow is:

View File

@@ -30,10 +30,10 @@ contract TychoRouterExposed is TychoRouter {
return _splitSwap(amountIn, nTokens, swaps);
}
function exposedSequentialSwap(
uint256 amountIn,
bytes calldata swaps
) external returns (uint256) {
function exposedSequentialSwap(uint256 amountIn, bytes calldata swaps)
external
returns (uint256)
{
return _sequentialSwap(amountIn, swaps);
}
}
@@ -188,19 +188,23 @@ contract TychoRouterTestSetup is Constants {
}
}
function encodeSplitSwap(
uint8 tokenInIndex,
uint8 tokenOutIndex,
uint24 split,
address executor,
bytes memory protocolData
) internal pure returns (bytes memory) {
return abi.encodePacked(
tokenInIndex, tokenOutIndex, split, executor, protocolData
);
function encodeSingleSwap(address executor, bytes memory protocolData)
internal
pure
returns (bytes memory)
{
return abi.encodePacked(executor, protocolData);
}
function encodeSequentialSwap(
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,