Skip to main content
Oracle prices are the most critical external input to the Open Nile Protocol. Every position opening, PnL calculation, liquidation, and settlement depends on accurate forward prices. A manipulated or erroneous price could enable unfair trades, trigger improper liquidations, or drain the liquidity pool. The protocol enforces multiple layers of onchain safeguards to validate every price update before it is accepted.
Oracle safeguards protect against publisher errors and manipulation, not against fundamental market moves. If the underlying EUR/USD rate genuinely moves by a large amount, the publisher may need the oracle admin to temporarily adjust safeguard parameters to accommodate the real price movement.

Safeguard Checks

Every call to publishForwardRound() 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

block.timestamp - lastPublishTime >= minForwardUpdateSpacing
Ensures a minimum time interval between consecutive forward price updates. This prevents rapid-fire updates that could overwhelm the system or be used in a sandwich attack.
ParameterDefaultRole
minForwardUpdateSpacing10 secondsORACLE_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

|newPrice - lastPrice| * 10,000 <= lastPrice * maxOracleMovePerUpdateBps
Caps the maximum price change permitted in a single update. This catches erroneous prices from publisher bugs, API failures, or deliberate manipulation attempts.
ParameterDefaultRole
maxOracleMovePerUpdateBps200 (2%)ORACLE_ADMIN_ROLE
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

|newPrice - lastAcceptedPrice| * 10,000 <= lastAcceptedPrice * maxDeviationVsPriorBps
Limits the total drift from the last accepted price. Unlike the move limit (which checks per-update jumps), this check measures cumulative drift, preventing a series of small updates from gradually walking the price to an unreasonable level.
ParameterDefaultRole
maxDeviationVsPriorBps50 (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

|newPrice - lastClosePrice| * 10,000 <= lastClosePrice * maxDeviationVsLastCloseBps
Compares the new price against the last settlement close price. This provides an independent anchor that prevents the published price from drifting too far from the most recently settled value.
ParameterDefaultRole
maxDeviationVsLastCloseBps150 (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

block.timestamp - publishTimestamp > maxForwardAge
Default: 60 secondsIf the forward price has not been updated within maxForwardAge, isValid returns false. Operations requiring a valid forward price (position opens, PnL calculations, early terminations) will revert with OracleInvalid.

Spot Staleness

block.timestamp - cachedPublishTime > maxOracleAge
Default: 30 secondsIf the cached spot price has not been refreshed within maxOracleAge, spot-dependent operations will revert. Spot is pull-based from Pyth, so it is typically refreshed on demand.
Stale prices directly impact protocol availability. If the publisher service goes down and stops refreshing forward prices, the protocol will automatically detect staleness and operations will halt. The checkOracleAndPause() function can automatically escalate the protocol to PAUSED mode when staleness is detected.

Oracle Validity

The isOracleValid(pairId) function provides a comprehensive validity check used by the ModeController for automatic mode escalation:
1

Pair Enabled Check

Verifies the currency pair is registered and enabled in the oracle module. Disabled pairs are considered invalid.
2

Forward Published Check

Verifies that at least one forward price has been published (lastForwardPublishTime > 0). Before the first publish, the oracle is invalid.
3

Freshness Check

Verifies the forward price is not stale: block.timestamp - lastForwardPublishTime <= maxForwardAge. This is the most common failure mode — if the publisher stops updating, staleness is detected within 60 seconds.

Safeguard Parameters Summary

ParameterDefaultUnitDescription
minForwardUpdateSpacing10secondsMin time between forward updates
maxOracleMovePerUpdateBps200bps (2%)Max price change per single update
maxDeviationVsPriorBps50bps (0.5%)Max drift from last accepted price
maxDeviationVsLastCloseBps150bps (1.5%)Max drift from last settlement close
maxForwardAge60secondsForward price staleness threshold
maxOracleAge30secondsSpot price staleness threshold
All parameters are configured by the 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 via clearMaturedForwards(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
Baseline resets are important for operational continuity. After a batch of forwards mature and are cleared, the publisher starts a fresh publish cycle without being constrained by stale references from the previous cycle.

Defense in Depth

The oracle safeguards work in concert with other protocol protections to provide defense in depth:
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.
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.
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.
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:
PropertyValue
AccessPUBLISHER_ROLE (access-controlled)
Price sourcePyth Hermes API for spot, interest-rate parity formula for forwards
Update frequencyEvery maxForwardAge / 2 (default: 30 seconds)
Retry strategy3 attempts with exponential backoff
Batch supportpublishForwardRounds() 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.