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%:
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 type | Suggested threshold | Rationale |
|---|---|---|
| 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 altcoins | 500-1000 bps (5-10%) | More volatile, wider tolerance |
| Long-tail assets | Consider disabling | Manipulation 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.