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

@@ -222,7 +222,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
);
}
/**
/**
* @notice Executes a swap operation based on a predefined swap graph with no split routes.
* This function enables multi-step swaps, optional ETH wrapping/unwrapping, and validates the output amount
* against a user-specified minimum. This function performs a transferFrom to retrieve tokens from the caller.
@@ -345,7 +345,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
* @param minAmountOut The minimum acceptable amount of the output token. Reverts if this condition is not met. This should always be set to avoid losing funds due to slippage.
* @param wrapEth If true, wraps the input token (native ETH) into WETH.
* @param unwrapEth If true, unwraps the resulting WETH into native ETH and sends it to the receiver.
* @param nTokens The total number of tokens involved in the swap graph (used to initialize arrays for internal calculations).
* @param receiver The address to receive the output tokens.
* @param swapData Encoded swap details.
*
@@ -358,7 +357,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
uint256 minAmountOut,
bool wrapEth,
bool unwrapEth,
uint256 nTokens,
address receiver,
bytes calldata swapData
) public payable whenNotPaused nonReentrant returns (uint256 amountOut) {
@@ -370,7 +368,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
minAmountOut,
wrapEth,
unwrapEth,
nTokens,
receiver,
swapData
);
@@ -394,7 +391,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
* @param minAmountOut The minimum acceptable amount of the output token. Reverts if this condition is not met. This should always be set to avoid losing funds due to slippage.
* @param wrapEth If true, wraps the input token (native ETH) into WETH.
* @param unwrapEth If true, unwraps the resulting WETH into native ETH and sends it to the receiver.
* @param nTokens The total number of tokens involved in the swap graph (used to initialize arrays for internal calculations).
* @param receiver The address to receive the output tokens.
* @param permitSingle A Permit2 structure containing token approval details for the input token. Ignored if `wrapEth` is true.
* @param signature A valid signature authorizing the Permit2 approval. Ignored if `wrapEth` is true.
@@ -409,7 +405,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
uint256 minAmountOut,
bool wrapEth,
bool unwrapEth,
uint256 nTokens,
address receiver,
IAllowanceTransfer.PermitSingle calldata permitSingle,
bytes calldata signature,
@@ -433,7 +428,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
minAmountOut,
wrapEth,
unwrapEth,
nTokens,
receiver,
swapData
);
@@ -523,7 +517,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
uint256 minAmountOut,
bool wrapEth,
bool unwrapEth,
uint256 nTokens,
address receiver,
bytes calldata swap_
) internal returns (uint256 amountOut) {
@@ -544,8 +537,10 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
? address(this).balance
: IERC20(tokenIn).balanceOf(address(this));
amountOut =
_callExecutor(swap_.executor(), amountIn, swap_.protocolData());
(address executor, bytes calldata protocolData) =
swap_.decodeSingleSwap();
amountOut = _callExecutor(executor, amountIn, protocolData);
uint256 currentBalance = tokenIn == address(0)
? address(this).balance
: IERC20(tokenIn).balanceOf(address(this));
@@ -684,6 +679,8 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
uint8 tokenInIndex = 0;
uint8 tokenOutIndex = 0;
uint24 split;
address executor;
bytes calldata protocolData;
bytes calldata swapData;
uint256[] memory remainingAmounts = new uint256[](nTokens);
@@ -694,17 +691,16 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
while (swaps_.length > 0) {
(swapData, swaps_) = swaps_.next();
tokenInIndex = swapData.tokenInIndex();
tokenOutIndex = swapData.tokenOutIndex();
split = swapData.splitPercentage();
(tokenInIndex, tokenOutIndex, split, executor, protocolData) =
swapData.decodeSplitSwap();
currentAmountIn = split > 0
? (amounts[tokenInIndex] * split) / 0xffffff
: remainingAmounts[tokenInIndex];
currentAmountOut = _callExecutor(
swapData.executor(), currentAmountIn, swapData.protocolData()
);
currentAmountOut =
_callExecutor(executor, currentAmountIn, protocolData);
// Checks if the output token is the same as the input token
if (tokenOutIndex == 0) {
cyclicSwapAmountOut += currentAmountOut;
@@ -725,16 +721,20 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
*
* @return calculatedAmount The total amount of the buy token obtained after all swaps have been executed.
*/
function _sequentialSwap(
uint256 amountIn,
bytes calldata swaps_
) internal returns (uint256 calculatedAmount) {
function _sequentialSwap(uint256 amountIn, bytes calldata swaps_)
internal
returns (uint256 calculatedAmount)
{
bytes calldata swap;
calculatedAmount = amountIn;
while (swaps_.length > 0) {
(swap, swaps_) = swaps_.next();
(address executor, bytes calldata protocolData) =
swap.decodeSingleSwap();
calculatedAmount =
_callExecutor(swap.executor(), calculatedAmount, swap.protocolData());
_callExecutor(executor, calculatedAmount, protocolData);
}
}