Oracles
Oracles are the contracts the core reads to price an FX pair or a collateral token — pull, never push. This page covers the IOracle interface and each implementation, from the Pyth FX mark to the fixed price that values USDC at par.
Code
Oracles are contracts the CRX core reads to price an FX pair or a collateral token. Every oracle implements the IOracle interface defined in IOracle.sol.
One oracle instance serves one thing: one pair, or one token. It is bound to that target by setOracle(pair, oracle) for an FX pair, or setCollateralOracle(token, oracle) for a collateral token. The pattern mirrors Morpho: the oracle is bound to the market, and the core reads a single scaled price on demand. The read is pull, never push: the core reads the current rate when it acts, and the actor who wants a fresh rate pays for it in the same transaction.
Basics
Every oracle reports at the fixed mark scale expo == MARK_EXPO (-8), where 1e8 = $1.00; the core rejects any other exponent. The two-method IOracle surface is total: price() is the live mark, emaPrice() is the settlement fixing. An oracle with no separate average returns the same value from both.
price
function price() external view returns (uint256 price, int32 expo, uint64 publishTime);Returns the current rate as price * 10^expo, with the timestamp it was observed.
The core enforces the fixed exponent and a non-zero publishTime on every read. A mis-scaled or unset feed fails loud. price() carries no staleness gate of its own; the core applies the freshness discipline: MAX_MARK_AGE on the live path, and a provenance check (publishTime >= deliveryTime) at settlement, which reads an older fixing mark by design.
Return values:
| Name | Type | Description |
|---|---|---|
price | uint256 | The rate, scaled such that price * 10^expo is the real value. |
expo | int32 | The scale exponent. |
publishTime | uint64 | The unix second the rate was observed. |
emaPrice
function emaPrice() external view returns (uint256 price, int32 expo, uint64 publishTime);The settlement fixing: a pure, smoothed rate (the Pyth EMA over a ~1h window, 5921 Pythnet slots) with no spot fallback: a fixing must not turn on a single noisy spot tick. Same scale and shape as price(). The collateral oracles (sUSDS, push, fixed) carry no separate average. They return their price() value here unchanged.
PythOracle
The production per-pair FX-rate oracle, the one that supplies the mark CRX trades and settles against. One instance serves one currency pair.
It is intent-pull, not push. There is no keeper and no privileged writer. The actor who wants a fresh mark (the party settling variation margin, asserting a close-out, or force-closing) bundles, in the same transaction as its action:
pyth.updatePriceFeeds{value: pyth.getUpdateFee(data)}(data); // ~1 wei on Base Sepolia
// ...then calls the CRX action that reads price()The price is updated as part of the actor's transaction, and the actor pays the update fee.
The mark prefers Pyth's spot aggregate, the freshest, most responsive rate. Spot carries a confidence band; when that band is wide, Pyth's publishers disagree (the FX feed is illiquid, jumpy, or the market is closing). In that case the oracle falls back to the EMA, an exponentially-weighted moving average over a ~1h window that rides out the noise. The EMA ships inside the same update blob as spot, leaving the fallback no extra cost.
One gate, applied to whichever rate is chosen: reject when conf / price exceeds maxConfBps. The fallback chain is spot → EMA → revert. Only when both bands are blown (a feed with no usable rate) does price() revert, which on the live path surfaces as a held action (the same as a stale mark) while the core waits for the feed to recover. The oracle rejects an untrustworthy mark rather than supplying one.
setOwner
function setOwner(address newOwner) external;Sets newOwner as owner. Owner-only. The owner tunes the gate; pair it with a multisig in production.
setMaxConfBps
function setMaxConfBps(uint64 newMaxConfBps) external;Sets the confidence-band breaker, in bps of the price. Owner-only, never zero (a zero gate would reject every rate and brick the feed).
Parameters:
| Name | Type | Description |
|---|---|---|
newMaxConfBps | uint64 | The new confidence band, in bps of the price. |
Immutables
pyth: the Pyth contract this oracle reads (Base Sepolia:0x2880aB15…).feedId: the Pyth price-feed id for this pair (e.g.FX.USD/PHP). Bound at construction.pairId: the CRX pair id this oracle serves (keccak256of the symbol). Informational.inverted: a flag the struct still decodes, but alwaysfalsein the live book. Each oracle serves its feed in the orientation Pyth publishes: the USD-base legs asUSD/XXX, and the four FX majors in their nativeXXX/USDorientation (EUR/USD,GBP/USD,AUD/USD,NZD/USD). The Pyth signature only verifies for the feed's own id, which means a price is never inverted off-chain; CRX lists the major where Pyth lists it instead.
Assumptions
- The Pyth feed publishes at a fixed exponent (FX feeds publish at
expo == -5); the oracle rescales to the mark scale. maxConfBpsis non-zero and set to a band that admits a healthy feed and rejects an illiquid one.- The relative confidence band is invariant under inversion, which means a trusted
XXX/USDfeed inverts to a trustedUSD/XXXmark.
SusdsOracle
The collateral price oracle for sUSDS. It reads the share's live, accruing price straight off the sUSDS token and reports it at the mark scale with a fresh publishTime.
setCollateralOracle(sUSDS, this) binds it, letting the CSA value sUSDS at its live price and the yield accrue through the oracle: no keeper, no admin bump. The price is a pure function of block.timestamp, leaving it always fresh and never tripping a staleness gate.
Immutables
susds: the sUSDS token this oracle readspricePerShareE8()from.
PushOracle
A settable USD price oracle for a collateral token whose real price lives on another chain. In production this fronts sUSDS, whose true share value and yield live on the Sky vault on Ethereum mainnet. Base Sepolia cannot read mainnet. An off-chain keeper carries the real price here.
The keeper reads convertToAssets(1e18) on mainnet and calls setPrice with that value at the mark scale. The constructor seeds a price immediately, keeping valuation from reverting in the gap before the first push, and every write stamps block.timestamp to let a consumer judge staleness.
WarningFat-finger rail, not a security boundary. The[$0.90, $5.00]band on every write catches a typo, not a compromised key. Containment is the admin'ssetPusherrotation.
setPrice
function setPrice(uint256 newPriceE8) external;Writes the latest real price at the mark scale. Pusher-only, banded to [$0.90, $5.00]. Emits PriceSet.
setPusher
function setPusher(address pusher_) external;Rotates the keeper authorized to write the price. Admin-only. Emits PusherSet.
Immutables
admin: can rotate the pusher; set once at deploy (the operator / deployer).
FixedPriceOracle
A constant-price oracle. Used for the settlement token's collateral price: USDC is valued at par ($1), leaving its oracle fixed. It returns a fixed (price, expo) and a publishTime of the current block, leaving it always fresh and never tripping a staleness gate.
For USDC at par: price = 1e8 → 1.0 at the mark scale. Not for FX pairs: those use a PythOracle that reads the live Pyth mark.
Immutables
fixedPrice: the constant price (e.g.1e8for USDC at par).fixedExpo: the constant exponent (-8for the mark scale).