DeviationSentinel
DeviationSentinel
The DeviationSentinel is a Venus periphery contract that monitors price deviations between the ResilientOracle and a DEX-based SentinelOracle. When deviations exceed configured thresholds, it routes emergency actions through the EBrake contract to pause market actions and zero collateral factors, protecting the protocol from price manipulation or oracle failures.
Overview
Price oracle reliability is critical for lending protocols. A compromised or malfunctioning oracle can lead to undercollateralized borrows or improper liquidations. The DeviationSentinel mitigates this risk by continuously comparing prices from two independent sources and taking protective action when they diverge beyond acceptable limits.
The system consists of seven contracts:
DeviationSentinel
Detects price deviations and routes emergency actions through EBrake
EBrake
Executes emergency actions (pauses, CF decrease) on the Comptroller
SentinelOracle
Aggregator routing per-token to a DEX oracle or returning a direct price override
UniswapOracle
Fetches prices from Uniswap V3 pools
PancakeSwapOracle
Fetches prices from PancakeSwap V3 pools
CurveOracle
Fetches prices from Curve StableSwap-NG pools via get_dy
AerodromeSlipstreamOracle
Fetches prices from Aerodrome Slipstream (CL) pools — Uniswap V3 fork
Economic Rationale
The deviation response logic is designed to counter specific attack vectors that arise when oracle prices diverge from real market prices.
When DEX Price > Oracle Price (borrow attack):
Attacker deposits an asset (e.g., BNB) as collateral
Borrows the mispriced token at its undervalued oracle price
Sells the borrowed token on DEX at the higher DEX price
Repeats until protocol liquidity is drained
Action: Pause borrowing for the affected asset.
When DEX Price < Oracle Price (collateral attack):
Attacker deposits the mispriced token as collateral, valued at the higher oracle price
Borrows other assets up to the loan-to-value limit
Sells or hedges the collateral token at the lower DEX price
Walks away with profit if the protocol later realigns, leaving bad debt
Action: Set collateral factor to 0 and pause supply for the affected asset.
Pauses trigger only for large price deviations (e.g., 15%-50%), not for minor discrepancies (1%-10%), to avoid unnecessary interruptions from normal pool volatility. Small deviations are not economically viable for attackers.
Deviation Response Logic
When a deviation is detected, the response depends on the direction:
Sentinel price > Oracle price
Pause borrowing via EBrake
Prevents borrowing out undervalued assets
Sentinel price ≤ Oracle price
Zero collateral factor and pause supply via EBrake
Prevents supplying overvalued assets as collateral
DeviationSentinel can only tighten restrictions. Recovery (unpausing, restoring collateral factors) is handled exclusively via a governance VIP, which calls the corresponding EBrake snapshot reset functions after restoring Comptroller parameters.
Off-Chain Monitoring
An off-chain monitoring service continuously compares the ResilientOracle price with the on-chain price reported by the SentinelOracle for each monitored token (sourced from the configured DEX backend — Uniswap V3, PancakeSwap V3, Curve StableSwap-NG, or Aerodrome Slipstream). When the deviation exceeds a configured threshold, the monitor calls handleDeviation via a trusted keeper address, which routes the emergency action through EBrake to pause the affected market.
handleDeviation re-runs the deviation check against live on-chain prices before acting, so a compromised or faulty off-chain monitor cannot trigger a pause that the on-chain prices don't justify. Once the incident is resolved, a governance VIP restores all parameters on the Comptroller and calls the EBrake snapshot reset functions to prepare for future incidents.
Architecture

Any of the four DEX-oracle backends (UniswapOracle, PancakeSwapOracle, CurveOracle, AerodromeSlipstreamOracle) can sit behind SentinelOracle per token, with the right backend chosen at deploy time per chain.
Inheritance
All DeviationSentinel-system contracts (DeviationSentinel, SentinelOracle, UniswapOracle, PancakeSwapOracle, CurveOracle, AerodromeSlipstreamOracle) inherit from AccessControlledV8 — every privileged setter is gated through the Venus Access Control Manager.
They all use the upgradeable initializer pattern: each constructor sets immutables and calls _disableInitializers(); initialize(address accessControlManager_) wires the ACM on the proxy after deployment.
State Variables
DeviationSentinel
Immutable
EBRAKE
IEBrake
EBrake contract through which all emergency actions are executed
RESILIENT_ORACLE
ResilientOracleInterface
Primary oracle for reference prices
SENTINEL_ORACLE
OracleInterface
DEX-based oracle for comparison prices
Mutable
tokenConfigs
mapping(address => DeviationConfig)
Deviation threshold and enabled status per token
trustedKeepers
mapping(address => bool)
Addresses authorized to call handleDeviation
Constants
MAX_DEVIATION
100
Maximum configurable deviation (100%)
Storage Reservation
__gap
uint256[48]
Reserved storage slots for future upgrades (private)
SentinelOracle
tokenConfigs
mapping(address => TokenConfig)
Maps tokens to their DEX oracle address
directPrices
mapping(address => uint256)
Direct price overrides (0 means use DEX oracle)
UniswapOracle / PancakeSwapOracle
RESILIENT_ORACLE
ResilientOracleInterface
Immutable reference for pair token prices
tokenPools
mapping(address => address)
Maps tokens to their DEX pool address
CurveOracle
RESILIENT_ORACLE
ResilientOracleInterface
Immutable reference for the reference token's USD price
poolConfigs
mapping(address => PoolConfig)
Maps each priced token to its Curve pool routing config
AerodromeSlipstreamOracle
RESILIENT_ORACLE
ResilientOracleInterface
Immutable reference for pair token prices
tokenPools
mapping(address => address)
Maps tokens to their Aerodrome Slipstream pool address
Structs
DeviationConfig
DeviationAction
TokenConfig (SentinelOracle)
PoolConfig (CurveOracle)
Solidity API
DeviationSentinel
constructor
Sets the three immutables (EBRAKE, RESILIENT_ORACLE, SENTINEL_ORACLE) and disables initializers on the implementation. Reverts with ZeroAddress if any argument is the zero address.
initialize
Wires the Access Control Manager. Called once on the proxy after deployment.
handleDeviation
Checks for price deviation on a market and takes protective action. This is the core function called by trusted keepers.
Parameters
market
IVToken
The vToken market to check and act on
Behavior
Retrieves the underlying token and its deviation config
Calls
checkPriceDeviationto compare oracle pricesReturns early if no deviation is detected (prices within configured threshold)
If deviation detected:
If sentinel price > oracle price: calls
EBRAKE.pauseBorrow(market)If sentinel price ≤ oracle price: calls
EBRAKE.decreaseCF(market, 0)thenEBRAKE.pauseSupply(market)
Emits
DeviationHandledwith the prices and the action taken
Idempotency is handled by EBrake — duplicate calls for an already-paused market are no-ops on the EBrake side.
Access Requirements
Caller must be a trusted keeper
Errors
UnauthorizedKeeperif caller is not a trusted keeperMarketNotConfiguredif token has no deviation configTokenMonitoringDisabledif monitoring is disabled for the token
checkPriceDeviation
View function that checks whether a market's underlying token has a price deviation exceeding its configured threshold.
Parameters
market
IVToken
The vToken market to check
Return Values
hasDeviation
bool
True if deviation exceeds the configured threshold
oraclePrice
uint256
Price from the ResilientOracle
sentinelPrice
uint256
Price from the SentinelOracle
deviationPercent
uint256
Calculated deviation as a percentage
Deviation Calculation
If either price is 0, the function sets hasDeviation to true and deviationPercent to type(uint256).max.
setTokenConfig
Configures deviation monitoring parameters for a token.
Parameters
token
address
The underlying token address
config
DeviationConfig
Deviation threshold and status
Access Requirements
Governance only (via AccessControlManager)
Errors
ZeroAddressiftokenis the zero addressZeroDeviationifconfig.deviationis 0ExceedsMaxDeviationifconfig.deviationexceeds 100
Events
TokenConfigUpdatedemitted on success
setTokenMonitoringEnabled
Enables or disables deviation monitoring for a previously configured token.
Parameters
token
address
The underlying token address
enabled
bool
Whether monitoring should be active
Access Requirements
Governance only
Errors
ZeroAddressiftokenis the zero addressMarketNotConfiguredif token has no existing config
Events
TokenMonitoringStatusChangedemitted on success
setTrustedKeeper
Adds or removes an address from the trusted keepers list.
Parameters
keeper
address
The keeper address
isTrusted
bool
Whether the address should be trusted
Access Requirements
Governance only
Errors
ZeroAddressif keeper is the zero address
Events
TrustedKeeperUpdatedemitted on success
SentinelOracle
constructor
Disables initializers on the implementation. SentinelOracle has no immutables.
initialize
Wires the Access Control Manager. Called once on the proxy after deployment.
getPrice
Returns the price of an asset from the configured DEX oracle or a direct price override.
Parameters
asset
address
The token address
Return Values
uint256
Asset price in (36 - asset decimals) format
Price Resolution Order
If
directPrices[asset]is non-zero, return itOtherwise, fetch from the configured DEX oracle
Errors
TokenNotConfiguredif no oracle is configured and no direct price is set
setTokenOracleConfig
Associates a token with a DEX oracle for price fetching.
Parameters
token
address
The underlying token address
oracle
address
The DEX oracle address
Access Requirements
Governance only
Errors
ZeroAddressif either address is zero
Events
TokenOracleConfigUpdatedemitted on success
setDirectPrice
Sets a direct price override for a token. Set to 0 to revert to DEX oracle pricing.
Parameters
token
address
The underlying token address
price
uint256
The price override (0 to use DEX oracle)
Access Requirements
Governance only
Events
DirectPriceUpdatedemitted on success
UniswapOracle
constructor
Sets the immutable RESILIENT_ORACLE and disables initializers on the implementation.
initialize
Wires the Access Control Manager. Called once on the proxy after deployment.
getPrice
Returns the price of a token derived from its Uniswap V3 pool.
Parameters
asset
address
The token address
Return Values
uint256
Asset price in (36 - asset decimals) format
Price Calculation
Reads
sqrtPriceX96from the Uniswap V3 pool'sslot0Computes
priceX96 = (sqrtPriceX96)² / Q96Determines target token position (token0 or token1)
Fetches the reference token price from ResilientOracle
Converts the pool ratio to a USD price using the reference price
Errors
TokenNotConfiguredif no pool is configured for the tokenInvalidPoolif the configured pool does not contain the token
setPoolConfig
Associates a token with a Uniswap V3 pool.
Parameters
token
address
The underlying token address
pool
address
The Uniswap V3 pool address
Access Requirements
Governance only
Errors
ZeroAddressif either address is zero
Events
PoolConfigUpdatedemitted on success
PancakeSwapOracle
constructor
Sets the immutable RESILIENT_ORACLE and disables initializers on the implementation.
initialize
Wires the Access Control Manager. Called once on the proxy after deployment.
getPrice
Returns the price of a token derived from its PancakeSwap V3 pool. Identical behavior to UniswapOracle.getPrice.
setPoolConfig
Associates a token with a PancakeSwap V3 pool. Identical behavior to UniswapOracle.setPoolConfig.
CurveOracle
constructor
Sets the immutable RESILIENT_ORACLE and disables initializers. Reverts with ZeroAddress if resilientOracle_ is the zero address.
initialize
Wires the Access Control Manager. Called once on the proxy after deployment.
getPrice
Returns the price of a token derived from its configured Curve StableSwap-NG pool.
Parameters
asset
address
The token address
Return Values
uint256
Asset price in (36 - asset decimals) format
Price Calculation
Reads
dy = pool.get_dy(coinIndex, refCoinIndex, 10**assetDecimals)— instantaneous swap output for one unit of the asset, reflecting the current pool state (including any active manipulation).Fetches the reference token's USD price
refPriceUsdfrom ResilientOracle.Computes
price = dy * refPriceUsd / 10**assetDecimals.
refDecimals cancels out of the formula, so only assetDecimals is needed at call time. It is cached in the PoolConfig at setup time to avoid an extra STATICCALL per getPrice.
Errors
TokenNotConfiguredif no pool is configured for the assetZeroPriceifget_dyreturns 0
setPoolConfig
Associates a token with a Curve StableSwap-NG pool and its reference-token routing.
Parameters
token
address
The token to price
pool
address
The Curve StableSwap-NG pool
coinIndex
uint8
Index of token in the pool's coins array
refCoinIndex
uint8
Index of referenceToken in the same pool
referenceToken
address
The pool's other coin, whose USD price is fetched from ResilientOracle
assetDecimals
uint8
Decimals of token (cached to avoid a per-call STATICCALL)
Access Requirements
Governance only
Errors
ZeroAddressiftoken,pool, orreferenceTokenis the zero addressAssetMismatchifpool.coins(coinIndex) != tokenReferenceMismatchifpool.coins(refCoinIndex) != referenceToken
Events
PoolConfigUpdated(token, pool, coinIndex, refCoinIndex, referenceToken, assetDecimals)emitted on success
AerodromeSlipstreamOracle
constructor
Sets the immutable RESILIENT_ORACLE and disables initializers on the implementation.
initialize
Wires the Access Control Manager. Called once on the proxy after deployment.
getPrice
Returns the price of a token derived from its configured Aerodrome Slipstream pool.
Price Calculation
Aerodrome Slipstream is a concentrated-liquidity AMM (Uniswap V3 fork). Its slot0() returns six values (no feeProtocol field), so the pool ABI is incompatible with IUniswapV3Pool, but the price-derivation math is otherwise identical:
Reads
sqrtPriceX96from the pool'sslot0().Computes
priceX96 = (sqrtPriceX96)² / Q96.Determines target token position (token0 or token1).
Fetches the reference token price from ResilientOracle.
Converts the pool ratio to a USD price using the reference price.
Uses the current slot0 price (no TWAP). Suitable for sentinel deviation detection where some manipulation tolerance is acceptable.
Errors
TokenNotConfiguredif no pool is configured for the tokenInvalidPoolif the configured pool does not contain the token
setPoolConfig
Associates a token with an Aerodrome Slipstream pool. Identical behavior to UniswapOracle.setPoolConfig.
Errors
ZeroAddressif either address is zero
Events
PoolConfigUpdatedemitted on success
Events
DeviationSentinel Events
TokenConfigUpdated
token, config
Deviation config set for a token
TokenMonitoringStatusChanged
token, enabled
Monitoring toggled for a token
TrustedKeeperUpdated
keeper, isTrusted
Keeper added or removed
DeviationHandled
market, oraclePrice, sentinelPrice, action
Deviation detected and acted on; action is a DeviationAction enum value
Pause and collateral factor events are now emitted by EBrake (ActionPaused, CollateralFactorDecreased) rather than by DeviationSentinel directly.
SentinelOracle Events
TokenOracleConfigUpdated
token, oracle
Token associated with DEX oracle
DirectPriceUpdated
token, price
Direct price override set
UniswapOracle / PancakeSwapOracle / AerodromeSlipstreamOracle Events
PoolConfigUpdated
token, pool
Token associated with a DEX pool (V3 / Slipstream)
CurveOracle Events
PoolConfigUpdated
token, pool, coinIndex, refCoinIndex, referenceToken, assetDecimals
Token associated with a Curve pool route
Security Considerations
Access Control
Governance Functions: All configuration functions (
setTokenConfig,setTrustedKeeper,setTokenMonitoringEnabled) are restricted viaAccessControlManagerKeeper Functions:
handleDeviationis restricted to trusted keepers via theonlyKeepermodifierDirect Price Overrides: The
setDirectPricefunction onSentinelOracleis governance-controlled to prevent price manipulation
Separation of Detection and Execution
DeviationSentinel contains only detection logic. All Comptroller interactions (pausing, CF changes) are executed by EBrake. This means:
A compromised keeper can only trigger EBrake actions, not call the Comptroller directly
EBrake enforces its own "tighten only" invariant independently — the sentinel cannot unpause or restore CF even if its logic were modified
Pre-incident state snapshots (original CF, borrow caps, supply caps) are tracked by EBrake, not DeviationSentinel
Recovery
Recovery from a sentinel-triggered freeze requires a governance VIP that:
Restores Comptroller parameters (unpause actions, restore CF)
Calls the appropriate EBrake snapshot reset functions (
resetCFSnapshot,resetBorrowCapSnapshot,resetSupplyCapSnapshot) to clear the stored snapshots
Custom Errors
Per-contract reference of revert reasons:
DeviationSentinel
ZeroAddress, ZeroDeviation, ExceedsMaxDeviation, UnauthorizedKeeper, MarketNotConfigured, TokenMonitoringDisabled
SentinelOracle
ZeroAddress, TokenNotConfigured
UniswapOracle
ZeroAddress, InvalidPool, TokenNotConfigured
PancakeSwapOracle
ZeroAddress, InvalidPool, TokenNotConfigured
CurveOracle
ZeroAddress, TokenNotConfigured, AssetMismatch, ReferenceMismatch, ZeroPrice
AerodromeSlipstreamOracle
ZeroAddress, InvalidPool, TokenNotConfigured
Deployment
See Deployed Contracts for current addresses.
Audits
DeviationSentinel undergoes security audits before mainnet deployment. Audit reports are available in the venus-periphery repository.
Last updated

