Price impact is perhaps the most consequential mathematical primitive in DeFi security. Every finding an auditor writes about oracle manipulation has a profitability threshold, and that threshold is denominated in price impact. Every sandwich attack bot makes real-time decisions using the same equations that governance proposals use to set pool parameters. This article derives the full mathematical machinery from first principles and shows exactly how security practitioners translate those derivations into attack profitability assessments.


1. The Constant Product Invariant

The core rule governing the most prevalent class of AMM is the constant product formula x * y = k, where x and y are the reserves of two tokens. This formula maintains that the product of the reserves of two assets remains constant after trades, excluding fees.

Let the pool begin with reserves $(x_0, y_0)$ such that $k = x_0 y_0$. A trader supplies $\Delta x$ units of token X and receives $\Delta y$ units of token Y. Post-trade reserves must satisfy:

$$ (x_0 + \Delta x)(y_0 - \Delta y) = k $$

Solving for $\Delta y$:

$$ y_0 - \Delta y = \frac{k}{x_0 + \Delta x} = \frac{x_0 y_0}{x_0 + \Delta x} $$

$$ \Delta y = y_0 - \frac{x_0 y_0}{x_0 + \Delta x} = \frac{y_0 \Delta x}{x_0 + \Delta x} $$

This final formula $y_0 \Delta x / (x_0 + \Delta x)$ gives the output amount considering the input amount and the current reserves.

1.1 The Spot Price and the Execution Price

From the constant product formula it follows that the spot price of token X is simply price_X = reserve_Y / reserve_X. This is the marginal price—the price of an infinitesimally small trade. For any trade of finite size, the execution price diverges from the spot price.

Define the execution price of the swap as:

$$ P_{\text{exec}} = \frac{\Delta y}{\Delta x} = \frac{y_0}{x_0 + \Delta x} $$

The spot price before the trade is:

$$ P_{\text{spot}} = \frac{y_0}{x_0} $$

The price impact $\pi$ is the percentage deviation of execution price from spot price:

$$ \pi = \frac{P_{\text{spot}} - P_{\text{exec}}}{P_{\text{spot}}} = 1 - \frac{x_0}{x_0 + \Delta x} = \frac{\Delta x}{x_0 + \Delta x} $$

This is the central result. Price impact is a function of $\Delta x / (x_0 + \Delta x)$—the fraction the trade input represents of the resulting pool balance. A trade equal to the entire existing reserve ($\Delta x = x_0$) produces a price impact of exactly 50%. For small trades relative to the pool’s size, the price impact is minimal, but for large trades, the trader is pushed further along the curve, resulting in a progressively worse execution price.

The new spot price after the trade is:

$$ P_{\text{spot}}’ = \frac{y_0 - \Delta y}{x_0 + \Delta x} = \frac{x_0 y_0 / (x_0 + \Delta x)}{x_0 + \Delta x} = \frac{k}{(x_0 + \Delta x)^2} $$

This new spot price is what any downstream on-chain oracle will read after the transaction settles.


2. How Liquidity Depth Affects Slippage

The denominator $x_0 + \Delta x$ in the price impact formula makes the relationship between liquidity and slippage precise. For a fixed trade size $\Delta x$:

$$ \pi = \frac{\Delta x}{x_0 + \Delta x} \approx \frac{\Delta x}{x_0} \quad \text{(when } \Delta x \ll x_0\text{)} $$

Doubling $x_0$ (and by symmetry $y_0$, to maintain price) halves the price impact. This is the formal statement of why deep pools have low slippage: the reserve size dominates the denominator.

Consider two pools both pricing token A at $1 relative to token B, but with different depths:

  • Pool S (shallow): $x_0 = 10{,}000$, $y_0 = 10{,}000$, $k = 10^8$
  • Pool D (deep): $x_0 = 1{,}000{,}000$, $y_0 = 1{,}000{,}000$, $k = 10^{12}$

A $\Delta x = 1{,}000$ trade incurs:

$$ \pi_S = \frac{1{,}000}{10{,}000 + 1{,}000} = 9.09% $$

$$ \pi_D = \frac{1{,}000}{1{,}000{,}000 + 1{,}000} \approx 0.10% $$

The same nominal trade produces 90× more price impact in the shallow pool. From a security lens this difference is not cosmetic—it is the difference between an attack being profitable and being economically impossible. The profitability of sandwich attacks depends on slippage, which in turn decreases as the AMM’s reserve size increases, at least in the case of the CPMM.

2.1 Liquidity as the Security Parameter

The cost of each trade is based on how much it shifts the curve. The curvature of the hyperbola is determined entirely by $k$. When $k$ is large (deep liquidity), the curve is nearly flat locally, and trades cause little price movement. When $k$ is small (thin liquidity), the curve is steeply curved, and the same nominal trade displaces the pool price dramatically.

This gives rise to what auditors call the manipulation cost of a pool: the dollar amount required to move the pool price by a target percentage $\pi^*$. Solving for $\Delta x$:

$$ \Delta x = \frac{\pi^* x_0}{1 - \pi^*} $$

For a 50% price displacement ($\pi^* = 0.5$):

$$ \Delta x = x_0 $$

An attacker must supply the entire existing X reserve to halve the price of Y in terms of X. For deep pools, this dollar figure is prohibitive. For thin pools, it may be well within flash loan range.


3. Deriving the Fee-Adjusted Output

Real protocols charge a fee $f$ (e.g., $f = 0.003$ for a 0.3% Uniswap V2 fee) applied to the input. The effective input is:

$$ \Delta x_{\text{eff}} = \Delta x (1 - f) $$

The output becomes:

$$ \Delta y = \frac{y_0 \cdot \Delta x(1-f)}{x_0 + \Delta x(1-f)} $$

In the contract, this is computed as amountInWithFee = (_amountIn * 997) / 1000 and then amountOut = (reserveOut * amountInWithFee) / (reserveIn + amountInWithFee).

Fees raise the effective price for the trader but, critically, do not protect against manipulation because an attacker can simply account for fees in their profit calculation. AMM trading fees can act as an inherent safeguard by decreasing the marginal profitability of price manipulation. When transaction fees are incorporated, profitability declines and eventually becomes negative beyond a critical threshold fee. However, for typical fee levels (0.01–1%), this threshold lies far above what is needed to break oracle-reliant protocols that use thin pools.

Here is a Solidity implementation of the pure price impact computation, intentionally separated from fee logic to make each component auditable in isolation:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/// @title PriceImpactMath
/// @notice Pure price impact and slippage calculations for CPMM pools.
///         All values use 18-decimal fixed-point (WAD) arithmetic.
library PriceImpactMath {
    uint256 constant WAD = 1e18;

    /// @notice Compute output amount for a constant-product swap (fee-inclusive).
    /// @param amountIn  Token input amount (raw units).
    /// @param reserveIn Reserve of input token.
    /// @param reserveOut Reserve of output token.
    /// @param feeBps Fee in basis points (e.g., 30 = 0.30%).
    function getAmountOut(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut,
        uint256 feeBps
    ) internal pure returns (uint256 amountOut) {
        require(amountIn > 0, "ZERO_IN");
        require(reserveIn > 0 && reserveOut > 0, "ZERO_RESERVE");

        uint256 amountInWithFee = amountIn * (10_000 - feeBps);
        uint256 numerator = amountInWithFee * reserveOut;
        uint256 denominator = reserveIn * 10_000 + amountInWithFee;
        amountOut = numerator / denominator;
    }

    /// @notice Compute price impact in WAD (1e18 = 100%).
    /// @dev    priceImpact = amountIn / (reserveIn + amountIn)
    ///         This is the fee-exclusive measure used in security analysis.
    function priceImpact(
        uint256 amountIn,
        uint256 reserveIn
    ) internal pure returns (uint256 impact) {
        // impact = amountIn * WAD / (reserveIn + amountIn)
        impact = (amountIn * WAD) / (reserveIn + amountIn);
    }

    /// @notice Compute the spot price of tokenOut in terms of tokenIn.
    /// @return price WAD-denominated spot price.
    function spotPrice(
        uint256 reserveIn,
        uint256 reserveOut
    ) internal pure returns (uint256 price) {
        price = (reserveOut * WAD) / reserveIn;
    }

    /// @notice Compute the new spot price after a swap, without executing it.
    /// @dev    newSpot = k / (reserveIn + amountIn)^2
    ///         expressed as reserveOut' / reserveIn'
    function spotPriceAfterSwap(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut,
        uint256 feeBps
    ) internal pure returns (uint256 newSpot) {
        uint256 amountOut = getAmountOut(amountIn, reserveIn, reserveOut, feeBps);
        uint256 newReserveIn  = reserveIn  + amountIn;
        uint256 newReserveOut = reserveOut - amountOut;
        newSpot = (newReserveOut * WAD) / newReserveIn;
    }

    /// @notice Minimum input required to move the spot price by targetImpactWad.
    /// @dev    Solving: targetImpact = dx / (x0 + dx)  =>  dx = x0 * t / (1 - t)
    function inputForImpact(
        uint256 reserveIn,
        uint256 targetImpactWad
    ) internal pure returns (uint256 amountIn) {
        require(targetImpactWad < WAD, "IMPACT_GTE_100PCT");
        // dx = x0 * t / (1 - t)  in WAD
        amountIn = (reserveIn * targetImpactWad) / (WAD - targetImpactWad);
    }
}

4. Multi-Hop Slippage Compounding

Routers frequently execute trades through intermediate pools: a path like TokenA → TokenB → TokenC involves two separate constant-product swaps. Most DEXs consist of many liquidity pools that represent different trading pairs. Each hop compounds price impact multiplicatively, not additively.

Let $\pi_1$ be the price impact of hop 1 and $\pi_2$ be the price impact of hop 2. The composite execution price relative to the theoretical straight-through price is:

$$ P_{\text{exec}}^{\text{total}} = P_{\text{spot},1} \cdot (1 - \pi_1) \cdot P_{\text{spot},2} \cdot (1 - \pi_2) $$

The effective price shortfall from the composed path is:

$$ \pi_{\text{total}} = 1 - (1 - \pi_1)(1 - \pi_2) $$

This generalizes to $n$ hops:

$$ \pi_{\text{total}} = 1 - \prod_{i=1}^{n}(1 - \pi_i) $$

Even modest individual hop impacts compound aggressively. Two 5% impacts yield a composite impact of $1 - (0.95)(0.95) = 9.75%$. Three 5% impacts yield $14.26%$.

4.1 Multi-Hop Oracle Manipulation Amplification

From a security perspective, this compounding has a dangerous corollary. An attacker who wants to move the effective price read by a multi-pool oracle can split their manipulation across two thin intermediate pools rather than attacking one deep pool. If the target protocol derives its price as a product of two pool ratios, the attacker achieves a multiplicative amplification of their price displacement per dollar spent.

Suppose an attacker wishes to inflate a derived price by factor $M$. If attacking a single pool with reserve $x_0$, they need $\Delta x = x_0 \cdot \pi^* / (1 - \pi^*)$. But if the price oracle reads two pools in series and the attacker can split across both (each needing only $\sqrt{M}-1$ relative displacement), the capital requirement decreases substantially due to the lower marginal cost in each smaller pool.

/// @notice Simulate two-hop composite price impact.
/// @return compositeImpact WAD-denominated effective impact across both hops.
function twoHopImpact(
    uint256 amountIn,
    uint256 reserveInHop1,
    uint256 reserveOutHop1, // == reserveInHop2 token
    uint256 reserveInHop2,
    uint256 reserveOutHop2,
    uint256 feeBps
) internal pure returns (uint256 compositeImpact) {
    // Hop 1: amountIn → intermediateOut
    uint256 intermediateOut = PriceImpactMath.getAmountOut(
        amountIn, reserveInHop1, reserveOutHop1, feeBps
    );
    // Theoretical straight-through: price1 * price2
    uint256 spotThrough = (
        (reserveOutHop1 * WAD / reserveInHop1) *
        (reserveOutHop2 * WAD / reserveInHop2)
    ) / WAD;
    // Hop 2: intermediateOut → finalOut
    uint256 finalOut = PriceImpactMath.getAmountOut(
        intermediateOut, reserveInHop2, reserveOutHop2, feeBps
    );
    // Effective execution price vs ideal
    uint256 execPrice = (finalOut * WAD) / amountIn;
    // compositeImpact = 1 - execPrice/spotThrough
    compositeImpact = WAD - (execPrice * WAD / spotThrough);
}

5. Slippage Tolerance vs. Price Impact Limits

These two concepts are frequently conflated in both user interfaces and protocol code, but they are distinct and carry different security implications.

Price impact is a property of the trade itself, determined entirely by the input size and pool reserves at execution time. It is calculable before the transaction is sent. The formula derived above ($\pi = \Delta x / (x_0 + \Delta x)$) gives the exact impact in isolation.

Slippage tolerance is the maximum price degradation a trader accepts between the time they submit a transaction and the time it executes on-chain. Price slippage refers to the change in the price of an asset during a transaction. When undertaking a transaction, traders typically set a slippage tolerance, which is the maximum price difference they are willing to accept for their transaction.

In Uniswap V2, slippage tolerance manifests as the amountOutMin parameter:

// User sets amountOutMin = expectedOut * (1 - slippageTolerance)
// The contract enforces:
require(amounts[amounts.length - 1] >= amountOutMin, "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT");

The security surface differs sharply:

PropertyPrice ImpactSlippage Tolerance
Determined byPool reserves + trade sizeUser-supplied parameter
Fixed before tx?Yes, if reserves unchangedYes
Attackable via frontrun?Indirectly (frontrun shifts reserves)Directly (wide tolerance = free profit)
Relevant to oracle manipulation?DirectlyIndirectly

When the slippage tolerance is set really high, it allows the transaction to still complete despite large price swings, and this can open the door to front-running and sandwich attacks.

A critical pattern auditors check: protocols that call AMM swaps internally and set amountOutMin = 0 effectively have no slippage protection. This appears throughout DeFi codebases in vault rebalancing, auto-compounders, and liquidation flows. The correct pattern is always to calculate an expected output off-chain or in a view function and pass a meaningful minimum.

// INSECURE: zero minimum output — full sandwich surface
router.swapExactTokensForTokens(
    amountIn, 0, path, address(this), deadline
);

// SECURE: minimum output derived from TWAP with tolerance
uint256 twapPrice = oracle.getTwapPrice(tokenIn, tokenOut, 1800); // 30-min TWAP
uint256 expectedOut = (amountIn * twapPrice) / 1e18;
uint256 minOut = expectedOut * (10_000 - MAX_SLIPPAGE_BPS) / 10_000;
router.swapExactTokensForTokens(
    amountIn, minOut, path, address(this), deadline
);

6. Price Impact in StableSwap Invariants

The constant product formula is poorly suited to pegged-asset pairs. The constant product model works well for volatile asset pairs such as ETH/WBTC, but it introduces problems for stablecoins. Swapping token X for Y reduces the pool balance of Y and immediately increases its price. Even small reserve imbalances can lead to noticeable price deviations.

Curve Finance introduced the StableSwap invariant. Curve V1 combines the constant sum invariant and constant product invariant to find the middle ground between low slippage and keeping the pool balanced, and thus the StableSwap invariant was born.

The full Curve invariant for $n$ tokens with amplification coefficient $A$ is:

$$ A n^n \sum_i x_i + D = A n^n D + \frac{D^{n+1}}{n^n \prod_i x_i} $$

where $D$ is the total pool value when all tokens are at equal balance (the “virtual liquidity”). Curve does not keep $x \cdot y$ constant. Instead it maintains $D$, which represents the total normalized liquidity of the pool. If balances are equal, slippage is low; if balances diverge, slippage increases automatically.

The role of $A$ is to control the curvature between the two regimes:

  • When $A \to \infty$: the invariant approaches the constant sum $\sum x_i = D$, yielding near-zero slippage
  • When $A \to 0$: it collapses to the constant product $\prod x_i = (D/n)^n$

Higher $A$ behaves closer to constant sum. $A$ acts as a form of virtual leverage, magnifying the linear portion of the formula and allowing for extremely low slippage within a defined range. As the pool becomes more imbalanced, the influence of the amplification coefficient diminishes, and the curve’s behavior degrades toward that of a constant-product AMM.

6.1 Slippage Explosion in Imbalanced Stable Pools

The critical security property is the asymmetry of the StableSwap curve under imbalance. Slippage can be much higher than a constant-product invariant if the pair price deviates significantly from par.

There is a price ratio at which the AMM offers low slippage, and as the liquidity becomes more lopsided, the slippage drastically increases.

This means an attacker who has already pushed a stableswap pool significantly off-balance (e.g., 90% in one asset) faces a pool that is now behaving much more like a constant-product AMM for subsequent trades—which is exactly the high-impact regime. Auditors must check whether protocols that integrate with Curve-style pools account for this dynamism; a pool parameter like $A = 500$ does not give invariant slippage protection—it only does so near parity.

/// @notice Approximate price impact in a two-token StableSwap pool.
/// @dev    Uses Newton's method for a single iteration (suitable for impact estimation only).
///         Full convergence requires ~5-10 iterations per the Curve whitepaper.
/// @param  dx      Amount of token X being swapped in (18 decimals).
/// @param  x       Current reserve of token X (18 decimals).
/// @param  y       Current reserve of token Y (18 decimals).
/// @param  A       Amplification coefficient.
/// @return dy      Amount of token Y received.
/// @return impact  Price impact in WAD.
function stableSwapOut(
    uint256 dx,
    uint256 x,
    uint256 y,
    uint256 A
) internal pure returns (uint256 dy, uint256 impact) {
    // D = x + y  (balanced pool simplification for estimation)
    uint256 D = x + y;

    // New x after swap
    uint256 xNew = x + dx;

    // Solve for yNew using the StableSwap invariant (single Newton step):
    // 4A*xNew*yNew + D*(yNew + xNew) - 4A*D*(xNew+yNew) - D^3/(4*xNew*yNew) = 0
    // Simplified single-step Newton approximation:
    uint256 b = xNew + D / (4 * A) - D;
    uint256 discriminant = b * b + D * D * D / (4 * A * xNew);
    // yNew ≈ (-b + sqrt(discriminant)) / 2
    uint256 yNew = (sqrt(discriminant) - b) / 2;

    dy = y - yNew;

    // Spot price before: 1:1 (stablecoin assumption)
    // Execution price: dx / dy
    // Impact = 1 - (dy/dx) / (1) = 1 - dy/dx
    if (dy >= dx) {
        impact = 0; // slight bonus due to imbalance correction
    } else {
        impact = ((dx - dy) * WAD) / dx;
    }
}

/// @dev Integer square root (Babylonian method).
function sqrt(uint256 x) internal pure returns (uint256 z) {
    if (x == 0) return 0;
    z = x;
    uint256 y = x / 2 + 1;
    while (y < z) { z = y; y = (x / y + y) / 2; }
}

7. Price Impact Manipulation in Thin Liquidity Environments

The naive use of an AMM liquidity pool as a price oracle works by dividing the number of tokens on each side of the pool to determine the spot exchange rate. For example, an AMM pool with 1 ETH and 3000 USDC produces a spot price of $3000 per ETH. This type of oracle is vulnerable to manipulation, especially with shallow liquidity, by bad actors making large trades.

Flash loans facilitate these attacks as they make it accessible to entities without a large pool of capital.

The attack anatomy is always the same three-step structure:

  1. Inflate or deflate the pool price via a large swap
  2. Exploit the downstream protocol that reads the distorted price
  3. Revert the manipulation (or allow arbitrageurs to do it) and extract profit

Attackers profit by temporarily pushing oracle prices away from fair value, often with flash loans or targeted swaps, then minting new tokens, borrowing against fake collateral, or harvesting liquidation discounts before prices snap back.

In 2022 alone, DeFi protocols lost about $403.2 million across 41 separate oracle manipulation attacks. A later study found that flawed oracles accounted for more than 49% of price-manipulation losses in 2023.

7.1 The Spot Oracle Vulnerability Pattern

The spot price of an AMM pool (reserveY / reserveX) is trivial to manipulate. Any protocol that reads reserve1 / reserve0 from a pool in the same transaction block as an exploit is vulnerable.

// VULNERABLE ORACLE PATTERN
// Reading spot price directly from pool reserves
function getTokenPrice() external view returns (uint256) {
    (uint112 reserve0, uint112 reserve1,) = IUniswapV2Pair(pool).getReserves();
    return uint256(reserve1) * 1e18 / uint256(reserve0);
    // ^ This can be manipulated by anyone in the same block
}

After a manipulative swap, if a pool that started at 3 ETH and 1000 USDC now shows a spot price of $333 per ETH, a lending protocol reading that price would let an attacker borrow up to 4.8 ETH against $2000 USDC collateral instead of the correct 0.528 ETH.


8. Auditor Methodology: Quantifying Attack Profitability

Understanding the mathematics is only useful in audit practice when it is converted into a profitability calculation. The framework is straightforward once the price impact formula is internalized.

8.1 Cost-Benefit Model for Oracle Manipulation

Define:

  • $C_{\text{manip}}$: Dollar cost to manipulate the oracle price by the required degree
  • $P_{\text{exploit}}$: Dollar profit from the downstream exploit enabled by the manipulation
  • $C_{\text{gas}}$: Gas cost of the full attack transaction sequence
  • $C_{\text{slippage}}$: Slippage cost paid when unwinding the manipulation position

An attack is profitable when:

$$ P_{\text{exploit}} > C_{\text{manip}} + C_{\text{slippage}} + C_{\text{gas}} $$

The manipulation cost $C_{\text{manip}}$ for a CPMM pool is:

$$ C_{\text{manip}} = \Delta x \cdot P_{X} = \frac{\pi^* x_0}{1-\pi^*} \cdot P_X $$

where $P_X$ is the dollar price of the input token. Note that $C_{\text{manip}}$ is the capital deployed, not the capital lost—the attacker recovers most of it when unwinding. The true cost is the slippage incurred on both legs:

$$ C_{\text{slippage}} = \text{(tokens paid above spot price on entry)} + \text{(tokens received below spot on exit)} $$

For a symmetric manipulation and unwind in the same pool (ignoring arbitrage):

$$ C_{\text{slippage}} \approx 2 \cdot \pi^* \cdot C_{\text{manip}} \cdot (1 - \pi^*) $$

Knowing both the profit and attack cost equations, it is straightforward to simulate various attacks to lending protocols.

8.2 Solidity Implementation of the Auditor’s Profitability Check

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "./PriceImpactMath.sol";

/// @title AttackProfitabilityEstimator
/// @notice Off-chain simulation tool for auditors to assess oracle manipulation
///         profitability on CPMM-backed oracles.
///         NOT for production use. Deploy in Foundry fork tests only.
contract AttackProfitabilityEstimator {
    using PriceImpactMath for uint256;

    uint256 constant WAD = 1e18;

    struct PoolState {
        uint256 reserveIn;
        uint256 reserveOut;
        uint256 feeBps;
    }

    struct AttackParams {
        uint256 targetImpactWad;    // e.g., 0.5e18 for 50% price move
        uint256 exploitProfitWad;   // expected profit in USD WAD
        uint256 inputTokenPriceUSD; // price of token being swapped (WAD)
        uint256 gasCostUSD;         // estimated gas cost (WAD)
    }

    /// @notice Estimate whether an oracle manipulation attack is profitable.
    /// @return manipCostUSD  Cost of capital deployed for manipulation (WAD).
    /// @return slippageCostUSD  Effective loss from round-trip slippage (WAD).
    /// @return netProfit  Signed net profit: positive = profitable attack (WAD).
    function estimateProfitability(
        PoolState calldata pool,
        AttackParams calldata attack
    ) external pure returns (
        uint256 manipCostUSD,
        uint256 slippageCostUSD,
        int256 netPro