Summary
The executeSwapExactInput function in the CurveAdapter contract calculates slippage tolerance (amountOutMinimum) for each intermediate swap step in a multi-hop swap path. This approach leads to cumulative slippage, where the overall slippage exceeds the intended tolerance. For example, if the slippage tolerance is 1% and the swap path is USDT -> WETH -> USDC, the final output could be 98.01 USDC for an input of 100 USDT, resulting in an effective slippage of 1.99% instead of the intended 1%.
Vulnerability Details
The executeSwapExactInput function calculates the minimum acceptable output (amountOutMinimum) for each intermediate swap step using the calculateAmountOutMin function.
 for (uint256 i; i < tokens.length - 1; i++) {
            
            IERC20(tokens[i]).approve(curveStrategyRouterCache, amountIn);
            
            uint256 expectedAmountOut = getExpectedOutput(tokens[i], tokens[i + 1], amountIn);
            
            uint256 amountOutMinimum = calculateAmountOutMin(expectedAmountOut);
            
            address receiver = (i == tokens.length - 2) ? swapPayload.recipient : address(this);
            
            amountIn = ICurveSwapRouter(curveStrategyRouterCache).exchange_with_best_rate({
                _from: tokens[i],
                _to: tokens[i + 1],
                _amount: amountIn,
                _expected: amountOutMinimum,
                _receiver: receiver
            });
        }
For example, in a path like USDT -> WETH -> USDC:
First swap: 100 USDT -> 0.03 WETH (Let's say the 0.03 WETH is now equal to 99 USD, so this is 1% slippage).
 
Second swap: 0.03 WETH -> 98.01 USDC (1% slippage).
 
The final output is 98.01 USDC, which represents a total slippage of 1.99%.
Since slippage is applied at each step, the overall slippage accumulates, leading to a higher effective slippage than intended.
Impact
Users may receive significantly less output than expected due to cumulative slippage. The slippage tolerance mechanism fails to provide the intended protection against price fluctuations.
The impact is Medium, the likelihood is Medium, so the severity is Medium.
Tools Used
Manual Review
Recommendations
To fix this issue, the slippage tolerance should be calculated based on the entire swap path's expected output. Consider following code example:
function executeSwapExactInput(SwapExactInputPayload calldata swapPayload) external returns (uint256 amountOut) {
    
    IERC20(swapPayload.tokenIn).transferFrom(msg.sender, address(this), swapPayload.amountIn);
    
    (address[] memory tokens,) = swapPayload.path.decodePath();
    
    uint256 amountIn = swapPayload.amountIn;
    
    address curveStrategyRouterCache = curveStrategyRouter;
    
    uint256 expectedAmountOut = getExpectedOutputForPath(tokens, amountIn);
    
    uint256 amountOutMinimum = calculateAmountOutMin(expectedAmountOut);
    
    for (uint256 i; i < tokens.length - 1; i++) {
        
        IERC20(tokens[i]).approve(curveStrategyRouterCache, amountIn);
        
        address receiver = (i == tokens.length - 2) ? swapPayload.recipient : address(this);
        
        amountIn = ICurveSwapRouter(curveStrategyRouterCache).exchange_with_best_rate({
            _from: tokens[i],
            _to: tokens[i + 1],
            _amount: amountIn,
            _expected: 0, 
            _receiver: receiver
        });
    }
    
    if (amountIn < amountOutMinimum) {
        revert Errors.SlippageExceeded(amountIn, amountOutMinimum);
    }
    
    amountOut = amountIn;
}
function getExpectedOutputForPath(address[] memory tokens, uint256 amountIn) internal view returns (uint256 expectedAmountOut) {
    expectedAmountOut = amountIn;
    for (uint256 i; i < tokens.length - 1; i++) {
        
        expectedAmountOut = getExpectedOutput(tokens[i], tokens[i + 1], expectedAmountOut);
    }
}