Aave

Theme

Aave V3 Oracles

Accurate and reliable price feeds are the bedrock of any lending protocol like Aave V3. They are essential for determining borrowing power, checking collateral sufficiency, and triggering liquidations. Aave employs a robust system primarily centered around the AaveOracle contract, with an additional safety layer, the PriceOracleSentinel, often used in Layer 2 deployments.

1. AaveOracle: The Primary Price Provider

The AaveOracle contract serves as the main source of truth for asset prices within an Aave market. It's designed to be flexible, primarily relying on Chainlink price feeds but incorporating a fallback mechanism for resilience.

  • Core Interface: IAaveOracle (which inherits from IPriceOracleGetter).
  • Key Goal: To provide the price of any listed asset relative to a common BASE_CURRENCY (usually USD, represented by address(0), or sometimes ETH).

Architecture & Key Concepts:

  1. Primary Sources (_assetsSources): The oracle maintains an internal mapping (conceptually mapping(address => AggregatorInterface) internal _assetsSources;) linking each underlying asset address to its primary price feed contract. In most Aave V3 deployments, these are Chainlink AggregatorV3Interface contracts.
  2. Fallback Oracle (_fallbackOracle): An optional secondary oracle address (IPriceOracleGetter) can be configured. If the primary source for an asset is missing, unresponsive, or returns an invalid price (e.g., zero or negative), the AaveOracle will consult this fallback oracle. This adds redundancy. The fallback could be another, perhaps less frequently updated, Chainlink feed, a different oracle provider, or even a simpler manual oracle for specific assets.
  3. Base Currency: Prices are returned relative to a defined BASE_CURRENCY. The BASE_CURRENCY_UNIT() function specifies the unit of this base currency (e.g., 1e8 for USD, 1e18 for ETH). The price of the base currency itself is defined as BASE_CURRENCY_UNIT().

Key Functions:

  • getAssetPrice(address asset):

    • Purpose: This is the most critical function, called frequently by the Pool's logic libraries (like GenericLogic during health factor calculations).
    • Logic (Conceptual):
      1. Check if asset is the BASE_CURRENCY. If so, return BASE_CURRENCY_UNIT().
      2. Look up the primary source address for asset in the _assetsSources mapping.
      3. If a primary source exists:
        • Call latestAnswer() on the source (Chainlink AggregatorInterface).
        • Validate the returned price (e.g., ensure it's positive).
        • If valid, potentially adjust for decimals and return the price relative to the BASE_CURRENCY.
      4. If no primary source exists or the price was invalid, check if a _fallbackOracle is set.
      5. If a fallback exists:
        • Call getAssetPrice(asset) on the _fallbackOracle.
        • Return the result (assuming the fallback provides a valid price).
      6. If no primary source, fallback, or if both return invalid prices, the function typically reverts (as seen in AaveOracleTest.t.sol's testAssetZeroPriceWithoutFallback test case expecting a revert).
    • Interface (IPriceOracleGetter.sol):
      function getAssetPrice(address asset) external view returns (uint256);
      
  • setAssetSources(address[] calldata assets, address[] calldata sources):

    • Purpose: Allows administrators to register or update the primary price feed addresses for multiple assets.
    • Permissions: Requires the caller to have the ASSET_LISTING_ADMIN_ROLE or POOL_ADMIN_ROLE granted via the ACLManager. The AaveOracle finds the ACLManager via the PoolAddressesProvider.
    • Interface (IAaveOracle.sol implies this, though not explicitly in IPriceOracleGetter): Often paired with configuration functions.
    • Validation: Ensures assets and sources arrays have the same length (test_revert_setAssetSources_inconsistentParams in AaveOracle.t.sol).
  • setFallbackOracle(address fallbackOracle):

    • Purpose: Allows administrators to set or update the fallback oracle address.
    • Permissions: Typically requires POOL_ADMIN_ROLE.
    • Interface (IAaveOracle.sol implies this): Configuration function.

2. PriceOracleSentinel: L2 Sequencer Safety Net

Layer 2 rollups introduce a potential risk: the centralized sequencer responsible for ordering transactions might become unavailable. If this happens, price feeds might not update, potentially allowing liquidations based on stale, incorrect prices. The PriceOracleSentinel acts as a safety mechanism to mitigate this risk.

  • Purpose: To pause critical, price-dependent operations (borrows and liquidations) if the L2 sequencer appears down or hasn't reported activity recently.
  • Core Interface: IPriceOracleSentinel.

Architecture & Key Concepts:

  1. Sequencer Oracle (_sequencerOracle): The sentinel relies on an external oracle contract (ISequencerOracle) that reports the status and last update time of the L2 sequencer. Chainlink often provides "Sequencer Uptime Feeds" for this purpose (like the SequencerOracle mock used in tests).
  2. Grace Period (_gracePeriod): A configurable duration (e.g., 1 hour, 1 day). Even if the sequencer is reporting as up, actions might still be paused if the time since the sequencer's last reported update (latestRoundData().updatedAt) exceeds the _gracePeriod. This acts as a buffer against potential delays or intermittent issues.

Key Functions:

  • isBorrowAllowed() / isLiquidationAllowed():

    • Purpose: Called by the Pool's logic libraries (ValidationLogic) before executing a borrow or liquidation.
    • Logic: Both functions rely on the same internal check (conceptually _isSequencerUpAndGracePeriodPassed()):
      1. Call latestRoundData() on the configured _sequencerOracle.
      2. Check the sequencer status from the returned data (typically, the answer indicates if it's down). If down, return false.
      3. If up, check the timestamp of the last update (updatedAt from latestRoundData).
      4. Compare the current block timestamp (block.timestamp) with updatedAt + _gracePeriod.
      5. Return true only if the sequencer is reported as up AND block.timestamp > updatedAt + _gracePeriod. Otherwise, return false.
    • Tests: The tests in PriceOracleSentinel.t.sol demonstrate this:
      • test_isLiquidationAllowed_network_down: Returns false if sequencer reports down.
      • test_isLiquidationAllowed_network_up_not_grace_period: Returns false if sequencer is up but grace period hasn't passed.
      • test_isLiquidationAllowed_true_network_up_grace_period_pass: Returns true only when up and grace period passed.
    • Interface (IPriceOracleSentinel.sol):
      function isBorrowAllowed() external view returns (bool);
      function isLiquidationAllowed() external view returns (bool);
      
  • setSequencerOracle(address newSequencerOracle):

    • Purpose: Updates the address of the Sequencer Uptime Feed.
    • Permissions: Requires POOL_ADMIN_ROLE (checked via ACLManager found via PoolAddressesProvider). Tested in test_reverts_setSequencerOracle_not_poolAdmin.
    • Event: Emits SequencerOracleUpdated.
    • Interface (IPriceOracleSentinel.sol):
      function setSequencerOracle(address newSequencerOracle) external;
      
  • setGracePeriod(uint256 newGracePeriod):

    • Purpose: Updates the grace period duration.
    • Permissions: Requires RISK_ADMIN_ROLE or POOL_ADMIN_ROLE. Tested in test_reverts_setGracePeriod_not_poolAdmin.
    • Event: Emits GracePeriodUpdated.
    • Interface (IPriceOracleSentinel.sol):
      function setGracePeriod(uint256 newGracePeriod) external;
      

Integration

The Pool contract (or rather, its logic libraries like ValidationLogic) uses the PoolAddressesProvider to get the addresses of both IAaveOracle (getPriceOracle()) and IPriceOracleSentinel (getPriceOracleSentinel()).

  • Prices are fetched using IAaveOracle.getAssetPrice().
  • Before critical operations like borrow or liquidationCall on L2s, the logic checks:
    • if (IPriceOracleSentinel(sentinelAddress) != address(0)) { require(IPriceOracleSentinel(sentinelAddress).isBorrowAllowed() / isLiquidationAllowed(), ...); }

Conclusion

Aave V3's oracle system is designed for reliability and safety. The AaveOracle provides core price information, primarily using Chainlink feeds with a fallback option for robustness. On Layer 2 deployments, the PriceOracleSentinel adds a crucial safety layer, pausing potentially risky operations like borrows and liquidations if the L2 sequencer shows signs of downtime or stale reporting, thus protecting the protocol from exploiting potentially inaccurate, outdated prices during sequencer outages. Both components are configured and managed via roles defined in the ACLManager, accessed through the central PoolAddressesProvider.