Oracle

The Oracle system provides two types of data to the Cap protocol: asset prices (USD values for backing assets, collateral tokens, and Cap-native tokens) and interest rates (borrow rates for vault utilization and per-Operator restaker rates). Both systems share the same adapter architecture — a central contract stores a payload per asset, and delegates the actual calculation to a stateless library via staticcall. This makes the oracle fully composable: any data source can be integrated by deploying a new adapter library.

Mechanics

Adapter Architecture

Each asset has an OracleData struct stored on-chain containing two fields:

  • adapter — the address of the library or contract that computes the value

  • payload — ABI-encoded calldata passed to the adapter

When a price or rate is requested, the Oracle does a low-level staticcall to adapter(payload). Errors in the adapter are silently swallowed — a failed call returns zero rather than reverting. This means any data source failure falls through to the backup rather than halting the protocol.

Price Oracle: Primary and Backup

Each asset has two OracleData slots — primary and backup. getPrice attempts the primary first:

  1. Call primary.adapter with primary.payload.

  2. If the returned price is zero or the timestamp is older than staleness[asset], fall through.

  3. Call backup.adapter with backup.payload.

  4. If backup also fails or is stale, revert with PriceError.

All prices are normalized to 8 decimals (USD). Adapters are responsible for decimal conversion.

Rate Oracle: Market and Utilization

Rates drive two separate interest streams:

  • Market rate — an external floor rate (e.g. Aave borrow rate). Used as a benchmark.

  • Utilization rate — derived from the cUSD vault's time-weighted utilization (via VaultAdapter). This is the actual rate Operators pay on vault interest.

Both are fetched via the same adapter pattern, but rate adapters use call rather than staticcall because VaultAdapter writes state (it updates the utilization index snapshot).

Additionally, two admin-set rates are stored directly:

  • benchmarkRate[asset] — a manual floor rate per asset (ray)

  • restakerRate[agent] — per-Operator restaker interest rate (ray)


Architecture

PriceOracle

Abstract contract. Inherited by the central Oracle contract.

getPrice(address _asset)

Returns the asset's USD price (8 decimals) and the timestamp of the last update. Tries the primary adapter first; falls back to the backup if the primary returns zero or a stale price. Reverts with PriceError if both fail.


Admin Functions

Function
Description

setPriceOracleData(address _asset, OracleData)

Set primary price adapter + payload for an asset

setPriceBackupOracleData(address _asset, OracleData)

Set backup price adapter + payload for an asset

setStaleness(address _asset, uint256 _staleness)

Set max age (seconds) for a valid price


View Functions

Function
Returns
Description

priceOracleData(address)

OracleData

Primary adapter + payload for the asset

priceBackupOracleData(address)

OracleData

Backup adapter + payload for the asset

staleness(address)

uint256

Max acceptable age in seconds for the price


RateOracle

Abstract contract. Inherited by the central Oracle contract.

marketRate(address _asset)

Returns the current market rate for _asset in ray format (1e27 = 100% annualised). Calls the configured market adapter (e.g. AaveAdapter). Returns zero if the adapter fails.


utilizationRate(address _asset)

Returns the utilization-based interest rate for _asset in ray. Calls the configured utilization adapter (VaultAdapter). This is the rate Operators pay on borrowed vault principal.


Admin Functions

Function
Description

setMarketOracleData(address _asset, OracleData)

Configure the market rate adapter + payload

setUtilizationOracleData(address _asset, OracleData)

Configure the utilization rate adapter + payload

setBenchmarkRate(address _asset, uint256 _rate)

Set a manual benchmark rate floor (ray)

setRestakerRate(address _agent, uint256 _rate)

Set the restaker interest rate for an Operator (ray)


View Functions

Function
Returns
Description

benchmarkRate(address)

uint256 (ray)

Manually set rate floor for the asset

restakerRate(address)

uint256 (ray)

Per-Operator restaker interest rate

marketOracleData(address)

OracleData

Market adapter + payload

utilizationOracleData(address)

OracleData

Utilization adapter + payload


Data Structures

OracleData

Field
Description

adapter

Contract or library address called via staticcall (prices) or call (rates)

payload

ABI-encoded calldata passed directly to the adapter


Price Adapters

ChainlinkAdapter

Reads a Chainlink price feed aggregator. Normalizes the returned value to 8 decimals regardless of the feed's native decimals.

Payload: abi.encodeCall(ChainlinkAdapter.price, (_source)) where _source is the Chainlink aggregator address.

Returns: (uint256 price, uint256 lastUpdated) — price in 8 decimals, lastUpdated from latestRoundData.


ChainlinkAdapterChained

Multiplies multiple Chainlink feeds together to derive a cross-rate. For example, to price an asset quoted in ETH: feed A/ETH × feed ETH/USD. The final price is normalized to 8 decimals; lastUpdated is the minimum across all feeds (most conservative).

Payload: abi.encodeCall(ChainlinkAdapterChained.price, (_sources)) where _sources is an array of aggregator addresses.


wstETHAdapter

Prices wstETH by reading the stETH/USD Chainlink feed and multiplying by the stETH-to-wstETH conversion ratio from the Lido contract (getPooledEthByShares(1 ether)).

Payload: abi.encodeCall(wstETHAdapter.price, (_source, _stETH)) where _source is the stETH Chainlink aggregator and _stETH is the stETH token address.


CapTokenAdapter

Prices a cUSD vault token (e.g. cUSD itself) as the weighted average of its backing assets:

Calls back into the Oracle via msg.sender to get each underlying asset's price. lastUpdated is the minimum across all underlying prices. Returns 1e8 if supply is zero.

Payload: abi.encodeCall(CapTokenAdapter.price, (_asset)) where _asset is the Cap vault token address.


StakedCapAdapter

Prices stcUSD (staked cUSD) by reading the cUSD price and multiplying by the convertToAssets ratio of the ERC4626 staking vault:

Payload: abi.encodeCall(StakedCapAdapter.price, (_asset)) where _asset is the stcUSD address.


FixedPriceOracle

A standalone contract (not a library) that always returns 1e8 (1 USD). Used for stablecoins or test deployments.

Payload: abi.encodeCall(FixedPriceOracle.price, ()) — no parameters.


UniswapV3Adapter

On-chain TWAP oracle using Uniswap V3 pool observations. Supports chained routes (e.g. TOKEN → WETH → USDC) to derive prices for any token with sufficient liquidity.

Payload: abi.encode(tokens[], pools[], twapPeriods[]) where:

  • tokens — ordered token route (length = pools.length + 1)

  • pools — Uniswap V3 pool addresses for each hop

  • twapPeriods — TWAP window in seconds per hop

The first token in the route must already have a registered price in the Oracle (used as the base price). The adapter validates the route via validateData before the payload is stored.


Rate Adapters

AaveAdapter

Reads Aave's getReserveData to fetch the current variable borrow rate for an asset. Used as a market rate benchmark.

Payload: abi.encodeCall(AaveAdapter.rate, (_aaveDataProvider, _asset)).


VaultAdapter

Computes a utilization-based interest rate using Cap's own vault utilization history. This is the primary rate source for vault interest.

The adapter tracks a per-vault per-asset utilization snapshot (index, lastUpdate) and computes the time-weighted average utilization since the last call. The rate is determined by a two-slope model with a dynamic multiplier:

  • Below kink: rate = slope0 × utilization / kink × multiplier. Multiplier decays when utilization is low.

  • Above kink: rate = (slope0 + slope1 × excess) × multiplier. Multiplier grows when utilization is high.

The multiplier is bounded by [minMultiplier, maxMultiplier] and adjusts at speed rate (ray per second).

Admin Functions

Function
Description

setSlopes(address _asset, SlopeData)

Configure the two-slope curve for an asset

setLimits(uint256 _max, uint256 _min, uint256 _rate)

Set multiplier bounds and adjustment speed

SlopeData


Usage Examples

1. Reading an asset price



3. Setting a restaker rate for an Operator

Last updated