Safeguard Checks
Every call topublishForwardRound() passes through four sequential validation checks. If any check fails, the entire update is rejected and the previous price remains in effect.
1. Spacing Check
| Parameter | Default | Role |
|---|---|---|
minForwardUpdateSpacing | 10 seconds | ORACLE_ADMIN_ROLE |
The spacing check uses
block.timestamp, which means the actual minimum interval depends on block times. On Sepolia (12-second blocks), the 10-second default effectively allows updates on every block.2. Move Limit Check
| Parameter | Default | Role |
|---|---|---|
maxOracleMovePerUpdateBps | 200 (2%) | ORACLE_ADMIN_ROLE |
Why 2% per update?
Why 2% per update?
EUR/USD is one of the most liquid currency pairs in the world. Under normal market conditions, it rarely moves more than 0.5% in a single day. A 2% per-update cap provides ample headroom for legitimate intraday volatility while catching grossly erroneous prices. During extreme events (central bank surprises, geopolitical shocks), the oracle admin may need to temporarily raise this limit.
3. Deviation vs Prior Check
| Parameter | Default | Role |
|---|---|---|
maxDeviationVsPriorBps | 50 (0.5%) | ORACLE_ADMIN_ROLE |
The deviation vs prior check uses the last accepted price as its reference, not the last published price. If a price update is rejected by safeguards, the reference does not change. This means the deviation window is anchored to the most recent successfully published price.
4. Deviation vs Last Close Check
| Parameter | Default | Role |
|---|---|---|
maxDeviationVsLastCloseBps | 150 (1.5%) | ORACLE_ADMIN_ROLE |
This check is only enforced when
lastClosePrice > 0. Before any position has been settled (no fixing price has been recorded), this check is skipped. Once the first settlement occurs, it provides an additional safeguard layer anchored to real settlement history.Staleness Checks
Beyond the safeguards on price updates, the protocol also enforces staleness checks on price reads. A price that has not been refreshed within its allowed age is considered stale and invalid.Forward Staleness
maxForwardAge, isValid returns false. Operations requiring a valid forward price (position opens, PnL calculations, early terminations) will revert with OracleInvalid.Spot Staleness
maxOracleAge, spot-dependent operations will revert. Spot is pull-based from Pyth, so it is typically refreshed on demand.Oracle Validity
TheisOracleValid(pairId) function provides a comprehensive validity check used by the ModeController for automatic mode escalation:
Pair Enabled Check
Verifies the currency pair is registered and enabled in the oracle module. Disabled pairs are considered invalid.
Forward Published Check
Verifies that at least one forward price has been published (
lastForwardPublishTime > 0). Before the first publish, the oracle is invalid.Safeguard Parameters Summary
| Parameter | Default | Unit | Description |
|---|---|---|---|
minForwardUpdateSpacing | 10 | seconds | Min time between forward updates |
maxOracleMovePerUpdateBps | 200 | bps (2%) | Max price change per single update |
maxDeviationVsPriorBps | 50 | bps (0.5%) | Max drift from last accepted price |
maxDeviationVsLastCloseBps | 150 | bps (1.5%) | Max drift from last settlement close |
maxForwardAge | 60 | seconds | Forward price staleness threshold |
maxOracleAge | 30 | seconds | Spot price staleness threshold |
ORACLE_ADMIN_ROLE (except maxOracleAge, which is set by the contract owner) and take effect immediately upon update.
Baseline Reset
When matured forward rounds are cleared viaclearMaturedForwards(pairId), the safeguard baselines are reset. This means:
- The spacing timer resets, allowing an immediate publish after clearing
- The move limit reference price resets to the latest accepted price
- The deviation tracking restarts from a clean state
Defense in Depth
The oracle safeguards work in concert with other protocol protections to provide defense in depth:Safeguards + Mode Escalation
Safeguards + Mode Escalation
If all safeguard checks pass but the published price is still suspicious, the ModeController can detect oracle degradation (price deviating beyond
degradedThresholdBps from spot) and escalate to DEGRADED mode. If the oracle becomes stale, checkOracleAndPause() can automatically escalate to PAUSED mode. See Mode Escalation for details.Safeguards + Margin Requirements
Safeguards + Margin Requirements
Even if a slightly inaccurate price passes the safeguard checks, the margin system provides a buffer. Positions require 2% initial margin and are liquidated at 1% maintenance margin, so a small price error would need to exceed the margin buffer to cause harm.
Safeguards + Exposure Caps
Safeguards + Exposure Caps
Exposure caps limit the total notional at risk. Even if a manipulated price were accepted, the maximum loss to the pool is bounded by the stress-adjusted net exposure cap. See Pool Exposure Caps for the formula.
Safeguards + Snapshotted Entry Prices
Safeguards + Snapshotted Entry Prices
Each position’s entry price is immutably set at open time. A manipulated current price cannot retroactively change the entry price of existing positions. It can only affect new position openings and current PnL valuations.
Publisher Architecture
The publisher service that submits forward prices operates as an authorized off-chain agent:| Property | Value |
|---|---|
| Access | PUBLISHER_ROLE (access-controlled) |
| Price source | Pyth Hermes API for spot, interest-rate parity formula for forwards |
| Update frequency | Every maxForwardAge / 2 (default: 30 seconds) |
| Retry strategy | 3 attempts with exponential backoff |
| Batch support | publishForwardRounds() for all enabled tenors in a single transaction |
In the M2 deployment, the publisher is operated by the protocol team. It is the only entity with
PUBLISHER_ROLE. The safeguard checks provide protection even against bugs in the publisher software itself — an erroneous price calculation will be rejected by the onchain checks before it can affect any positions.