> For the complete documentation index, see [llms.txt](https://docs.elara.fi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.elara.fi/protocol-reference/guides/oracle-guide.md).

# Oracle Guide

How Elara uses a price feed to guard against stablecoin collateral depegs during mint and redeem.

## Why Elara Has an Oracle

elUSD is backed 1:1 by stablecoin collateral held in the treasury — **not** valued through an oracle-priced basket. The oracle does one specific job: it checks the live USD price of the incoming/outgoing stablecoin during mint and redeem so the protocol can't be arbed if that collateral (USDC, USDT, etc.) temporarily loses its peg.

Treat `PriceOracle` as a **depeg guard**, not a collateral valuation layer. Without it, a $0.95 USDC mint or a $1.05 USDC redeem would drain value from the protocol — this contract exists to block that.

## Overview

`PriceOracle` is an oracle-agnostic price-feed wrapper. Every per-token entry is tagged with an `OracleType`, and `getPrice(token)` dispatches to the appropriate read path. Prices are always returned normalized to **18 decimal precision** regardless of the underlying feed.

{% hint style="info" %}
The current deployment supports feeds that implement the `AggregatorV3Interface` (e.g. Chainlink-compatible providers). The `OracleType` enum and `getPrice` dispatch are designed so additional provider types can be added via upgrade without changing the external interface.
{% endhint %}

The Vault calls `PriceOracle.getPrice(token)` during every mint and redeem. The returned price is then clamped by asymmetric caps (see [Peg Protection Mechanics](#peg-protection-mechanics)) before being used in the amount math.

## Feed Configuration

Each token requires a one-time configuration before it can be priced. The admin entry point registers an `AggregatorV3Interface`-compatible feed:

```solidity
priceOracle.configureChainlink(
    tokenAddress,   // collateral token to configure
    feedAddress,    // AggregatorV3Interface-compatible price feed
    heartbeat       // max acceptable price age in seconds (0 = use defaultHeartbeat)
);
```

This call:

1. Reads `feedDecimals` directly from the feed contract
2. Sets `oracleTypes[token] = OracleType.CHAINLINK`
3. Stores the per-token feed config (`feed`, `heartbeat`, `feedDecimals`)
4. Emits `TokenConfigured(token, OracleType.CHAINLINK, feed)`

{% hint style="warning" %}
Configuration requires `CONTRACT_ADMIN` role. Misconfiguring the feed address or heartbeat can cause all mint/redeem operations for that token to revert.
{% endhint %}

## Heartbeat

The heartbeat is the **maximum acceptable age** of a price data point in seconds. If the feed has not updated within the heartbeat window, `getPrice` reverts with `StalePrice`.

| Parameter           | Default                 | Range                               |
| ------------------- | ----------------------- | ----------------------------------- |
| `defaultHeartbeat`  | 3600s (1 hour)          | Set via `setDefaultHeartbeat`       |
| Per-token heartbeat | inherits default if `0` | Set per token at configuration time |

**Choosing a heartbeat:**

* Match or slightly exceed the feed's native publish cadence
* Common values: 3600s for frequent feeds, 86400s for daily-updated feeds
* Shorter heartbeats increase revert risk during feed maintenance windows

To update the default for future configurations:

```solidity
priceOracle.setDefaultHeartbeat(7200); // 2 hours
```

To adjust a token's heartbeat, re-run the configuration call with the same feed address and a new heartbeat value.

## Price Normalization

All prices are returned in **18 decimals** regardless of the underlying feed's decimal count:

```
if feedDecimals < 18: price = answer × 10^(18 − feedDecimals)
if feedDecimals > 18: price = answer / 10^(feedDecimals − 18)
if feedDecimals == 18: price = answer
```

**Example:** A USDC/USD feed with 8 decimals returning `100_000_000` ($1.00) is normalized to `1_000_000_000_000_000_000` (1e18).

## Peg Protection Mechanics

The Vault applies two asymmetric caps **on top of** the oracle price when computing mint and redeem amounts. These are what actually block depeg arbitrage:

```
mintPrice   = min(oraclePrice, 1e18)   // cap at $1
redeemPrice = max(oraclePrice, 1e18)   // floor at $1
```

| Scenario             | Oracle price | Mint price | Redeem price | Effect                                                                |
| -------------------- | ------------ | ---------- | ------------ | --------------------------------------------------------------------- |
| Peg (normal)         | $1.00        | $1.00      | $1.00        | 1:1 exchange                                                          |
| Collateral above peg | $1.02        | $1.00      | $1.02        | Can't mint extra elUSD from depegged-up collateral                    |
| Collateral below peg | $0.97        | $0.97      | $1.00        | Mint credits less elUSD; redeem still demands $1 of backing per elUSD |

Net effect: whichever direction the collateral depegs, the arbitrage opportunity lands on the side that favors the protocol, not the attacker.

## Feed Validation

The price-read path wraps the external feed call in a `try/catch`:

* `answer <= 0` → reverts `InvalidPrice()`
* `block.timestamp − updatedAt > heartbeat` → reverts `StalePrice(token, age, heartbeat)`
* Any external revert (feed down, malformed data) → caught and re-thrown as `InvalidPrice()`

## Deactivating a Token

To stop pricing a token (e.g., removing collateral support):

```solidity
priceOracle.deactivateToken(tokenAddress);
// Sets oracleTypes[token] = OracleType.NONE
// getPrice(token) will now revert with FeedNotSet(token)
```

{% hint style="info" %}
Deactivating a token in `PriceOracle` does not automatically disable it in `TokenRegistry`. You should also call `tokenRegistry.toggleMint(token, false)` and `tokenRegistry.toggleRedeem(token, false)` to fully suspend operations for that token.
{% endhint %}

## Adding New Oracle Types

`PriceOracle` is designed for extensibility. The `OracleType` enum and `getPrice` dispatch pattern allow new provider types (e.g., Pyth, Redstone) to be added in future upgrades without changing the external interface or any Vault code.
