-
Notifications
You must be signed in to change notification settings - Fork 164
Description
[Security] Slippage Protection Significantly Weakened in swapAndAdd for Certain Price Ranges
Summary
The swapAndAddCallParameters method in @uniswap/router-sdk has a critical design flaw that can significantly weaken slippage protection from the intended 3% to as much as 30-40% in certain scenarios, particularly when the position's price range causes mintAmountsWithSlippage to return substantially reduced amounts. This creates a large MEV attack surface.
Environment
- Package:
@uniswap/router-sdk - Version: [Current version]
- Affected Method:
SwapRouter.swapAndAddCallParameters
Problem Description
When using swapAndAddCallParameters , the swap slippage protection ( minimumAmountOut ) can be overridden by a much weaker protection value from mintAmountsWithSlippage , especially when:
- The position's price range is wide or near boundaries
- Multiple trades trigger aggregated slippage check (>2 trades)
- The current pool price is close to the position's range boundaries
Root Cause
The issue occurs in the interaction between three components:
1. Aggregated Slippage Check (swapRouter.ts:432)
const performAggregatedSlippageCheck =
sampleTrade.tradeType === TradeType.EXACT_INPUT && numberOfTrades > 2
// When triggered, individual swaps have amountOutMinimum = 02. Minimal Position Construction (swapRouter.ts:618-625)
const minimalPosition = Position.fromAmounts({
pool: position.pool,
tickLower: position.tickLower,
tickUpper: position.tickUpper,
amount0: zeroForOne ? position.amount0 : minimumAmountOut, // Uses swap's minimumAmountOut
amount1: zeroForOne ? minimumAmountOut : position.amount1, // Uses swap's minimumAmountOut
useFullPrecision: false,
})3. Min Value Selection (approveAndCall.ts:82-87)
let { amount0: amount0Min, amount1: amount1Min } =
position.mintAmountsWithSlippage(slippageTolerance)
// Takes the SMALLER value between two protections
if (JSBI.lessThan(minimalPosition.amount0.quotient, amount0Min)) {
amount0Min = minimalPosition.amount0.quotient
}
if (JSBI.lessThan(minimalPosition.amount1.quotient, amount1Min)) {
amount1Min = minimalPosition.amount1.quotient
}The problem: mintAmountsWithSlippage can return values much smaller than minimalPosition amounts due to Uniswap V3's concentrated liquidity mechanics. When this happens, the weaker protection overwrites the stronger one.
Reproduction Steps
Scenario Setup
// User wants to swap USDC → cbBTC and add liquidity
const slippageTolerance = new Percent(300, 10000) // 3%
// Position with relatively wide price range
const position = new Position({
pool: cbBTC_USDC_POOL,
tickLower: 62082, // Lower price bound
tickUpper: 75878, // Upper price bound (22% width)
liquidity: DESIRED_LIQUIDITY
})
// Swap trade (3 routes, triggers aggregated slippage check)
const trades = [trade1, trade2, trade3] // numberOfTrades > 2
const { calldata, value } = SwapRouter.swapAndAddCallParameters(
trades,
{ slippageTolerance, ... },
position,
addLiquidityOptions,
ApprovalTypes.MAX,
ApprovalTypes.MAX
)Expected Protection
Swap slippage: 3%
minimumAmountOut for cbBTC: ~97% of expected output
Final amount1Min should be: ~97% of expected cbBTC
Actual Behavior
Calculation flow:
1. minimumAmountOut (from swap): 1.455 cbBTC (3% slippage)
2. minimalPosition.amount1: 1.455 cbBTC
3. position.mintAmountsWithSlippage(3%): 1.019 cbBTC (30% reduction due to price range)
4. Final amount1Min = min(1.019, 1.455) = 1.019 cbBTC (30% slippage!)
Result: Protection degraded from 3% to 30%
Real-World Example
From an actual on-chain transaction:
User input: 188,088 USDC
Swap amount: 88,740 USDC
Expected remaining for LP: ~99,348 USDC
After mintAmountsWithSlippage:
- amount0Min: 70,609 USDC
- Reduction: (99,348 - 70,609) / 99,348 ≈ 29%
This means:
- User expected: 3% slippage protection
- Actual protection: ~30% slippage (10x weaker!)
Security Impact
Attack Scenario
1. User submits swapAndAdd transaction with 3% slippage
2. MEV bot front-runs: Buys cbBTC to pump price
3. User's swap executes at inflated price:
- Expected: 1.5 cbBTC
- Gets: 1.05 cbBTC (30% loss)
4. Validation:
- Swap check: amountOutMinimum = 0 ✅ (passes)
- LP check: 1.05 >= 1.019 ✅ (passes)
5. MEV bot back-runs: Sells cbBTC for profit
6. Transaction succeeds, user loses 30% instead of max 3%Risk Assessment
| Condition | Risk Level | Potential Loss |
|---|---|---|
| Normal price range (< 10% reduction) | 🟢 Low | < 5% |
| Medium price range (10-20% reduction) | 🟡 Medium | 5-15% |
| Wide/boundary range (> 20% reduction) | 🔴 High | 15-40% |
Additional Context
Why mintAmountsWithSlippage Can Reduce Amounts Significantly
The method calculates amounts needed for adding liquidity at price boundaries:
// position.ts:157-205
public mintAmountsWithSlippage(slippageTolerance: Percent) {
const { sqrtRatioX96Upper, sqrtRatioX96Lower } =
this.ratiosAfterSlippage(slippageTolerance)
// Creates positions at price boundaries
// When position is near range boundaries, the amount change is non-linear
// A 3% price change can result in 30%+ amount reduction
}This is by design for Uniswap V3's concentrated liquidity, but creates a security issue when used to override swap slippage protection.
Observed in Practice
Some implementations appear to add an additional sweepToken validation after swaps:
calldatas = [
swap1, swap2, swap3,
sweepToken(USDC, minimumRemaining), // ← Extra validation step observed
pullTokens,
approve,
mintLP,
sweep
]This suggests the issue may be known, but the standard SDK implementation does not include this protection.
References
- Code:
sdks/router-sdk/src/swapRouter.ts:563-653 - Related:
sdks/router-sdk/src/approveAndCall.ts:71-113 - V3 SDK Position:
sdks/v3-sdk/src/entities/position.ts:157-205
Thank you for your attention to this security concern. I'm happy to provide more details if needed.