Skip to content

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.

Version scope: This API reference follows venus-periphery v1.2.0. DeviationSentinel, EBrake, SentinelOracle, and the DEX oracle contracts are upgradeable. Resolve the current proxy implementation, configured backend, pools, direct-price overrides, keepers, token thresholds, and ACM grants before treating this page as live configuration.

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 contract types:

ContractDescription
DeviationSentinelDetects price deviations and routes emergency actions through EBrake
EBrakeExecutes emergency actions (pauses, CF decrease) on the Comptroller
SentinelOracleAggregator routing per-token to a DEX oracle or returning a direct price override
UniswapOracleFetches prices from Uniswap V3 pools
PancakeSwapOracleFetches prices from PancakeSwap V3 pools
CurveOracleFetches prices from Curve StableSwap-NG pools via get_dy
AerodromeSlipstreamOracleFetches prices from Aerodrome Slipstream (CL) pools — Uniswap V3 fork

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):

  1. Attacker deposits an asset (e.g., BNB) as collateral
  2. Borrows the mispriced token at its undervalued oracle price
  3. Sells the borrowed token on DEX at the higher DEX price
  4. Repeats until protocol liquidity is drained

Action: Pause borrowing for the affected asset.

When DEX Price < Oracle Price (collateral attack):

  1. Attacker deposits the mispriced token as collateral, valued at the higher oracle price
  2. Borrows other assets up to the loan-to-value limit
  3. Sells or hedges the collateral token at the lower DEX price
  4. 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.

Thresholds are configured per token to balance incident sensitivity against normal pool volatility. Whether a particular deviation is economically exploitable depends on the configured threshold, available liquidity, pool depth, execution costs, and the rest of the transaction path; the contract does not prove that a smaller deviation is harmless.

When a deviation is detected, the response depends on the direction:

ConditionActionRationale
Sentinel price > Oracle pricePause borrowing via EBrakePrevents borrowing out undervalued assets
Sentinel price ≤ Oracle priceZero collateral factor and pause supply via EBrakePrevents 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.

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 its configured DEX backend). 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 configured check against current on-chain inputs before acting. This prevents a keeper from substituting an arbitrary off-chain price, but it does not make the result independent of the selected DEX pool, spot-price manipulation, token configuration, or a governance-set direct-price override. 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.

DeviationSentinel Architecture

One of four DEX-oracle backends (UniswapOracle, PancakeSwapOracle, CurveOracle, or AerodromeSlipstreamOracle) can sit behind SentinelOracle for a token. The live choice is mutable configuration and can differ by token and network.

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.

VariableTypeDescription
EBRAKEIEBrakeEBrake contract through which all emergency actions are executed
RESILIENT_ORACLEResilientOracleInterfacePrimary oracle for reference prices
SENTINEL_ORACLEOracleInterfaceDEX-based oracle for comparison prices
VariableTypeDescription
tokenConfigsmapping(address => DeviationConfig)Deviation threshold and enabled status per token
trustedKeepersmapping(address => bool)Addresses authorized to call handleDeviation
ConstantValueDescription
MAX_DEVIATION100Maximum configurable deviation (100%)
VariableTypeDescription
__gapuint256[48]Reserved storage slots for future upgrades (private)
VariableTypeDescription
tokenConfigsmapping(address => TokenConfig)Maps tokens to their DEX oracle address
directPricesmapping(address => uint256)Direct price overrides (0 means use DEX oracle)
VariableTypeDescription
RESILIENT_ORACLEResilientOracleInterfaceImmutable reference for pair token prices
tokenPoolsmapping(address => address)Maps tokens to their DEX pool address
VariableTypeDescription
RESILIENT_ORACLEResilientOracleInterfaceImmutable reference for the reference token’s USD price
poolConfigsmapping(address => PoolConfig)Maps each priced token to its Curve pool routing config
VariableTypeDescription
RESILIENT_ORACLEResilientOracleInterfaceImmutable reference for pair token prices
tokenPoolsmapping(address => address)Maps tokens to their Aerodrome Slipstream pool address
struct DeviationConfig {
uint8 deviation; // Maximum allowed deviation percentage (1-100)
bool enabled; // Whether monitoring is active for this token
}
enum DeviationAction {
BorrowPaused, // Triggered when sentinel price > oracle price
SupplyPausedAndCFZeroed // Triggered when sentinel price <= oracle price
}
struct TokenConfig {
address oracle; // DEX oracle contract address
}
struct PoolConfig {
address pool; // Curve StableSwap-NG pool
uint8 coinIndex; // index of the priced asset in the pool
uint8 refCoinIndex; // index of the reference token in the same pool
address referenceToken; // token whose USD price is fetched from ResilientOracle
uint8 assetDecimals; // decimals of the priced asset (cached to avoid a per-call STATICCALL)
}
constructor(IEBrake eBrake_, ResilientOracleInterface resilientOracle_, OracleInterface sentinelOracle_)

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.

function initialize(address accessControlManager_) external initializer

Wires the Access Control Manager. Called once on the proxy after deployment.


Checks for price deviation on a market and takes protective action. This is the core function called by trusted keepers.

function handleDeviation(IVToken market) external
NameTypeDescription
marketIVTokenThe vToken market to check and act on
  1. Retrieves the underlying token and its deviation config
  2. Calls checkPriceDeviation to compare oracle prices
  3. Returns early if no deviation is detected (prices within configured threshold)
  4. If deviation detected:
    • If sentinel price > oracle price: calls EBRAKE.pauseBorrow(market)
    • If sentinel price ≤ oracle price: calls EBRAKE.decreaseCF(market, 0) then EBRAKE.pauseSupply(market)
  5. Emits DeviationHandled with 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.

  • Caller must be a trusted keeper
  • UnauthorizedKeeper if caller is not a trusted keeper
  • MarketNotConfigured if token has no deviation config
  • TokenMonitoringDisabled if monitoring is disabled for the token

View function that checks whether a market’s underlying token has a price deviation exceeding its configured threshold.

function checkPriceDeviation(
IVToken market
) public view returns (
bool hasDeviation,
uint256 oraclePrice,
uint256 sentinelPrice,
uint256 deviationPercent
)
NameTypeDescription
marketIVTokenThe vToken market to check
NameTypeDescription
hasDeviationboolTrue if deviation exceeds the configured threshold
oraclePriceuint256Price from the ResilientOracle
sentinelPriceuint256Price from the SentinelOracle
deviationPercentuint256Calculated deviation as a percentage
deviationPercent = (|oraclePrice - sentinelPrice| / oraclePrice) * 100

If either price is 0, the function sets hasDeviation to true and deviationPercent to type(uint256).max.


Configures deviation monitoring parameters for a token.

function setTokenConfig(address token, DeviationConfig calldata config) external
NameTypeDescription
tokenaddressThe underlying token address
configDeviationConfigDeviation threshold and status
  • Governance only (via AccessControlManager)
  • ZeroAddress if token is the zero address
  • ZeroDeviation if config.deviation is 0
  • ExceedsMaxDeviation if config.deviation exceeds 100
  • TokenConfigUpdated emitted on success

Enables or disables deviation monitoring for a previously configured token.

function setTokenMonitoringEnabled(address token, bool enabled) external
NameTypeDescription
tokenaddressThe underlying token address
enabledboolWhether monitoring should be active
  • Governance only
  • ZeroAddress if token is the zero address
  • MarketNotConfigured if token has no existing config
  • TokenMonitoringStatusChanged emitted on success

Adds or removes an address from the trusted keepers list.

function setTrustedKeeper(address keeper, bool isTrusted) external
NameTypeDescription
keeperaddressThe keeper address
isTrustedboolWhether the address should be trusted
  • Governance only
  • ZeroAddress if keeper is the zero address
  • TrustedKeeperUpdated emitted on success

constructor()

Disables initializers on the implementation. SentinelOracle has no immutables.

function initialize(address accessControlManager_) external initializer

Wires the Access Control Manager. Called once on the proxy after deployment.


Returns the price of an asset from the configured DEX oracle or a direct price override.

function getPrice(address asset) external view returns (uint256)
NameTypeDescription
assetaddressThe token address
TypeDescription
uint256Asset price in (36 - asset decimals) format
  1. If directPrices[asset] is non-zero, return it
  2. Otherwise, fetch from the configured DEX oracle
  • TokenNotConfigured if no oracle is configured and no direct price is set

Associates a token with a DEX oracle for price fetching.

function setTokenOracleConfig(address token, address oracle) external
NameTypeDescription
tokenaddressThe underlying token address
oracleaddressThe DEX oracle address
  • Governance only
  • ZeroAddress if either address is zero
  • TokenOracleConfigUpdated emitted on success

Sets a direct price override for a token. Set to 0 to revert to DEX oracle pricing.

function setDirectPrice(address token, uint256 price) external
NameTypeDescription
tokenaddressThe underlying token address
priceuint256The price override (0 to use DEX oracle)
  • Governance only
  • DirectPriceUpdated emitted on success

constructor(ResilientOracleInterface resilientOracle_)

Sets the immutable RESILIENT_ORACLE and disables initializers on the implementation.

function initialize(address accessControlManager_) external initializer

Wires the Access Control Manager. Called once on the proxy after deployment.


Returns the price of a token derived from its Uniswap V3 pool.

function getPrice(address asset) external view returns (uint256)
NameTypeDescription
assetaddressThe token address
TypeDescription
uint256Asset price in (36 - asset decimals) format
  1. Reads sqrtPriceX96 from the Uniswap V3 pool’s slot0
  2. Computes priceX96 = (sqrtPriceX96)² / Q96
  3. Determines target token position (token0 or token1)
  4. Fetches the reference token price from ResilientOracle
  5. Converts the pool ratio to a USD price using the reference price
  • TokenNotConfigured if no pool is configured for the token
  • InvalidPool if the configured pool does not contain the token

Associates a token with a Uniswap V3 pool.

function setPoolConfig(address token, address pool) external
NameTypeDescription
tokenaddressThe underlying token address
pooladdressThe Uniswap V3 pool address
  • Governance only
  • ZeroAddress if either address is zero
  • PoolConfigUpdated emitted on success

constructor(ResilientOracleInterface resilientOracle_)

Sets the immutable RESILIENT_ORACLE and disables initializers on the implementation.

function initialize(address accessControlManager_) external initializer

Wires the Access Control Manager. Called once on the proxy after deployment.


Returns the price of a token derived from its PancakeSwap V3 pool. Identical behavior to UniswapOracle.getPrice.

function getPrice(address asset) external view returns (uint256)

Associates a token with a PancakeSwap V3 pool. Identical behavior to UniswapOracle.setPoolConfig.

function setPoolConfig(address token, address pool) external

constructor(ResilientOracleInterface resilientOracle_)

Sets the immutable RESILIENT_ORACLE and disables initializers. Reverts with ZeroAddress if resilientOracle_ is the zero address.

function initialize(address accessControlManager_) external initializer

Wires the Access Control Manager. Called once on the proxy after deployment.


Returns the price of a token derived from its configured Curve StableSwap-NG pool.

function getPrice(address asset) external view returns (uint256)
NameTypeDescription
assetaddressThe token address
TypeDescription
uint256Asset price in (36 - asset decimals) format
  1. 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).
  2. Fetches the reference token’s USD price refPriceUsd from ResilientOracle.
  3. 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.

  • TokenNotConfigured if no pool is configured for the asset
  • ZeroPrice if get_dy returns 0

Associates a token with a Curve StableSwap-NG pool and its reference-token routing.

function setPoolConfig(
address token,
address pool,
uint8 coinIndex,
uint8 refCoinIndex,
address referenceToken,
uint8 assetDecimals
) external
NameTypeDescription
tokenaddressThe token to price
pooladdressThe Curve StableSwap-NG pool
coinIndexuint8Index of token in the pool’s coins array
refCoinIndexuint8Index of referenceToken in the same pool
referenceTokenaddressThe pool’s other coin, whose USD price is fetched from ResilientOracle
assetDecimalsuint8Decimals of token (cached to avoid a per-call STATICCALL)
  • Governance only
  • ZeroAddress if token, pool, or referenceToken is the zero address
  • AssetMismatch if pool.coins(coinIndex) != token
  • ReferenceMismatch if pool.coins(refCoinIndex) != referenceToken
  • PoolConfigUpdated(token, pool, coinIndex, refCoinIndex, referenceToken, assetDecimals) emitted on success

constructor(ResilientOracleInterface resilientOracle_)

Sets the immutable RESILIENT_ORACLE and disables initializers on the implementation.

function initialize(address accessControlManager_) external initializer

Wires the Access Control Manager. Called once on the proxy after deployment.


Returns the price of a token derived from its configured Aerodrome Slipstream pool.

function getPrice(address asset) external view returns (uint256)

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:

  1. Reads sqrtPriceX96 from the pool’s slot0().
  2. Computes priceX96 = (sqrtPriceX96)² / Q96.
  3. Determines target token position (token0 or token1).
  4. Fetches the reference token price from ResilientOracle.
  5. 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.

  • TokenNotConfigured if no pool is configured for the token
  • InvalidPool if the configured pool does not contain the token

Associates a token with an Aerodrome Slipstream pool. Identical behavior to UniswapOracle.setPoolConfig.

function setPoolConfig(address token, address pool) external
  • ZeroAddress if either address is zero
  • PoolConfigUpdated emitted on success

EventParametersDescription
TokenConfigUpdatedtoken, configDeviation config set for a token
TokenMonitoringStatusChangedtoken, enabledMonitoring toggled for a token
TrustedKeeperUpdatedkeeper, isTrustedKeeper added or removed
DeviationHandledmarket, oraclePrice, sentinelPrice, actionDeviation 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.

EventParametersDescription
TokenOracleConfigUpdatedtoken, oracleToken associated with DEX oracle
DirectPriceUpdatedtoken, priceDirect price override set

UniswapOracle / PancakeSwapOracle / AerodromeSlipstreamOracle Events

Section titled “UniswapOracle / PancakeSwapOracle / AerodromeSlipstreamOracle Events”
EventParametersDescription
PoolConfigUpdatedtoken, poolToken associated with a DEX pool (V3 / Slipstream)
EventParametersDescription
PoolConfigUpdatedtoken, pool, coinIndex, refCoinIndex, referenceToken, assetDecimalsToken associated with a Curve pool route
  • Governance Functions: All configuration functions (setTokenConfig, setTrustedKeeper, setTokenMonitoringEnabled) are restricted via AccessControlManager
  • Keeper Functions: handleDeviation is restricted to trusted keepers via the onlyKeeper modifier
  • Direct Price Overrides: setDirectPrice on SentinelOracle is governance-controlled. A nonzero override replaces the configured DEX-backend value, so its live value is part of the system’s trust and configuration surface.

DeviationSentinel contains only detection logic. All Comptroller interactions (pausing, CF changes) are executed by EBrake. This means:

  • A keeper cannot supply an arbitrary off-chain price; it can trigger only the EBrake action produced by the configured on-chain comparison. Those on-chain inputs and configurations must still be trusted and monitored.
  • 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 from a sentinel-triggered freeze requires a governance VIP that:

  1. Restores Comptroller parameters (unpause actions, restore CF)
  2. Calls the appropriate EBrake snapshot reset functions (resetCFSnapshot, resetBorrowCapSnapshot, resetSupplyCapSnapshot) to clear the stored snapshots

Per-contract reference of revert reasons:

ContractErrors
DeviationSentinelZeroAddress, ZeroDeviation, ExceedsMaxDeviation, UnauthorizedKeeper, MarketNotConfigured, TokenMonitoringDisabled
SentinelOracleZeroAddress, TokenNotConfigured
UniswapOracleZeroAddress, InvalidPool, TokenNotConfigured
PancakeSwapOracleZeroAddress, InvalidPool, TokenNotConfigured
CurveOracleZeroAddress, TokenNotConfigured, AssetMismatch, ReferenceMismatch, ZeroPrice
AerodromeSlipstreamOracleZeroAddress, InvalidPool, TokenNotConfigured

See Deployed Contracts for current addresses.

Published DeviationSentinel and EBrake audit reports are indexed in the venus-periphery v1.2.0 release. Check each report’s scope and reviewed commit against every current proxy implementation and configured oracle backend.