Skip to content

Deviation Guard

The deviation guard compares the current round’s price against a stored baseline. If the move exceeds maxDeviationBps, the round is rejected.

This is the check that would have stopped the Compound DAI incident. DAI spiked to 1.34 USD on Coinbase Pro, a 34% deviation from its 1.00 USD peg. A guard set to 5% or even 10% would have rejected that round and prevented 89M USD in unwarranted liquidations.

The math

function _calcDeviation(int256 _base, int256 _current) internal pure returns (uint256) {
uint256 absDiff = _current > _base
? uint256(_current - _base)
: uint256(_base - _current);
return (absDiff * BPS_BASE) / uint256(_base);
}

Deviation is expressed in basis points (bps), where 1 bps = 0.01%:

deviation=currentbasebase×10000\text{deviation} = \frac{|\text{current} - \text{base}|}{\text{base}} \times 10000

The absolute value handles both upward and downward moves symmetrically. A 5% increase and a 5% decrease both produce a deviation of 500 bps.

The check

if (maxDeviationBps > 0) {
uint256 deviation = _calcDeviation(lastAcceptedPrice, price);
if (deviation > maxDeviationBps) revert DeviationExceeded(deviation);
}

Setting maxDeviationBps to zero disables the check entirely. This is useful during initial deployment when no meaningful baseline has been established, or for feeds where price volatility is high enough that a deviation guard would produce too many false positives.

Updating the baseline

function updateBaseline() external onlyOwner {
(, int256 price, , , ) = priceFeed.latestRoundData();
if (price <= 0) revert InvalidPrice();
lastAcceptedPrice = price;
}

The baseline is set automatically in the constructor from the feed’s current price. After that, updateBaseline must be called explicitly by the owner to advance it.

This is intentional. Automatic baseline advancement would allow an attacker who controls the feed (or who can manipulate it for multiple consecutive rounds) to walk the baseline up gradually and eventually make a large move look like a series of small ones. Manual advancement requires the owner to observe and validate each price before accepting it as the new reference.

Choosing a threshold

The right maxDeviationBps value depends on the asset:

Asset typeSuggested thresholdRationale
Stablecoins (DAI, USDC)100-200 bps (1-2%)Should not move significantly
Large-cap (ETH, BTC)300-500 bps (3-5%)High liquidity, moves are real
Mid-cap altcoins500-1000 bps (5-10%)More volatile, wider tolerance
Long-tail assetsConsider disablingManipulation risk outweighs benefit

These are starting points. What’s right for a stablecoin integration on a low-latency L2 is completely different from a long-tail asset consumer on mainnet. Start conservative, monitor rejections in production, and widen only when you have data showing legitimate moves are being blocked.