Multi-hop swaps in CurveAdapter.executeSwapExactInput()
and FeeDistributionBranch._performMultiDexSwap()
may result in higher than expected slippage because slippage is verified separately for each individual swap along the multi-hop path, rather than based on the total amount of tokens received at the end of all hops.
When FeeDistribution.convertAccumulatedFeesToWeth()
executes, the conversion of assets to WETH can occur through these methods:
UniswapV2 adapter: single or multi-hop swap
UniswapV3: single or multi-hop swap
Curve: single or multi-hop swap
Multiple DEXes: multi-hop swap across different exchanges
However, in two specific cases - Curve multi-hop swaps and multi-hop swaps across multiple DEXes - users may experience higher slippage than expected due to a flaw in the code logic. This occurs in both CurveAdapter.executeSwapExactInput()
and FeeDistributionBranch._performMultiDexSwap()
. Those functions when executing multi-hop swaps (for example, DAI -> USDC -> WETH) use the following loop:
Iterate all tokens in the provided path
Fetch next token form the path
Approve amount to the selected router
Calculates expectedAmountOut
based on oracle prices
Calculates amountOutMinimum
based on expectedAmountOut
and slippageToleranceBps
.
Swap the tokens
As we can see, this behaves as expected for a single swap: the loss of tokens due to slippage is limited to (expectedAmountOut * slippageToleranceBps) / Constants.MIN_SLIPPAGE_BPS
. However, in the case of multiple hops, minAmountOut
is calculated separately at each step in the path. This means that while the slippage for each individual swap is constrained by the specified tolerance, the cumulative slippage across multiple hops will be greater.
As a result, although each swap in a multi-hop transaction adheres to the defined slippage limit, the final amountOut
after all sequential swaps will experience a higher maximum slippage than the value set by slippageToleranceBps
. The more hops involved in the swap, the greater the discrepancy between the actual slippage and the specified tolerance.
Consider the following example, to simplify consider that the exchange rate for all N tokens in a mult-hop scenario is 1:1 (this way we can focus only on slippage issues):
1 Hop: amountOutMinimum
will be (amountIn * (Constants.BPS_DENOMINATOR - slippageToleranceBps)) / Constants.BPS_DENOMINATOR
N Hops: amountOutMinimum
will be approximately (ignoring precision issues) amountIn * ((Constants.BPS_DENOMINATOR - slippageToleranceBps) / Constants.BPS_DENOMINATOR)**N
Some examples for N up to 4, slippageToleranceBps
set to 100 (1%) as used in Zaros test suite and AmountIn
= 100000.
N | minAmountOut | Slippage |
---|---|---|
1 | 99000 | 1.00% |
2 | 98010 | 1.99% |
3 | 97029 | 2.97% |
4 | 960581 | 3.94% |
Over multiple hops, the actual slippage experienced by the swap can be significantly higher than the limit set by the slippageToleranceBps
configuration. In contrast, other adapters, such as UniswapV2Adapter
and UniswapV3Adapter
, correctly enforce the slippageToleranceBps
restriction, even in multi-hop swaps.
Multi-hop swaps executed via CurveAdapter.executeSwapExactInput()
or FeeDistributionBranch._performMultiDexSwap()
may experience slippage exceeding the defined slippageToleranceBps
. As a result, in highly volatile market conditions, these swaps could incur greater than expected losses. This impacts fee conversion within FeeDistributionBranch.convertAccumulatedFeesToWeth()
, which relies on these methods to convert received fees into WETH. Consequently, this may lead to a reduction in the total fees available for distribution to vaults.
Manual Review.
Consider adding a slippage check that compares the initial input token amount against the final output token amount received after completing all swaps in the transaction path.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.