Diffusal
Contracts

DiffusalOracle

Unified oracle providing volatility, premium, and aggregated price data

The DiffusalOracle contract serves as the central source of truth for all market data in the Diffusal protocol. It aggregates prices from multiple sources (Pyth Network, Chainlink, and native prices), stores implied volatility and premium rates per trading pair, and provides the global risk-free rate for Black-Scholes calculations.


Overview

The oracle provides three categories of data:

CategoryPurposePrecision
Price DataSpot prices for underlying assets8 decimals (int64)
VolatilityImplied volatility per trading pair8 decimals (50% = 50_000_000)
PremiumPremium rate per trading pair8 decimals
Risk-Free RateGlobal interest rate for Black-Scholes8 decimals (5% = 5_000_000)

The oracle uses a most-recent-wins price selection strategy. When multiple price sources have data, the most recently updated price is used. When timestamps are equal, priority is: Pyth > Chainlink > Native.


Key Concepts

Price Aggregation

The oracle queries up to three price sources for each pair:

  1. Pyth Network (Primary) — High-frequency, low-latency price feeds
  2. Chainlink (Secondary) — Decentralized oracle network
  3. Native Prices (Fallback) — Admin-set prices for testing or emergency
Price Selection:
1. Query all configured sources
2. Compare timestamps
3. Return most recent price
4. On timestamp tie: Pyth > Chainlink > Native

Pair Identification

Trading pairs are identified by the keccak256 hash of their name string:

bytes32 pairId = keccak256(bytes("ETH-USD"));

The oracle maintains a registry of pair names and their associated feed configurations.

Precision Model

All values use 8 decimal precision (matching Pyth/Chainlink standards):

ValueExampleRepresentation
$3,000.00ETH price300_000_000_000 with expo=-8
50%Volatility50_000_000
5%Risk-free rate5_000_000

Storage & State

The contract uses ERC-7201 namespaced storage to prevent storage collisions:

/// @custom:storage-location erc7201:diffusal.storage.DiffusalOracle
struct DiffusalOracleStorage {
    address owner;
    mapping(address => bool) admins;
    mapping(bytes32 pairId => int64) volatilities;
    mapping(bytes32 pairId => int64) premiums;
    int64 riskFreeRate;
    address pythOracle;
    mapping(bytes32 pairId => Price) nativePrices;
    mapping(bytes32 nameHash => bytes32) pythFeeds;
    mapping(bytes32 nameHash => address) chainlinkFeeds;
    string[] registeredPairs;
    mapping(bytes32 nameHash => bool) _isRegistered;
}

Price Struct

struct Price {
    int64 price;       // Price value
    uint64 conf;       // Confidence interval
    int32 expo;        // Exponent (typically -8)
    uint256 publishTime; // Timestamp of price update
}

External Functions

Price Reading

getPriceUnsafe

Returns the most recent price without staleness checks.

function getPriceUnsafe(bytes32 pairId) external view returns (Price memory priceData)
ParameterTypeDescription
pairIdbytes32Trading pair identifier

Returns: Price struct with price, confidence, exponent, and publish time.

Access: Public (view)


getPriceNoOlderThan

Returns price only if it was updated within the specified age.

function getPriceNoOlderThan(bytes32 pairId, uint256 age) external view returns (Price memory priceData)
ParameterTypeDescription
pairIdbytes32Trading pair identifier
ageuint256Maximum age in seconds

Reverts: StalePrice if price is older than age seconds.

Access: Public (view)


getPrice

Returns price components separately.

function getPrice(bytes32 pairId) external view returns (int64 price, int32 expo, uint256 publishTime)

Access: Public (view)


Volatility & Parameters

getVolatility

Returns the implied volatility for a trading pair.

function getVolatility(bytes32 pairId) external view returns (int64 volatility)

Returns: Volatility with 8 decimals (50% = 50_000_000)


getPremium

Returns the premium rate for a trading pair.

function getPremium(bytes32 pairId) external view returns (int64 premium)

getRiskFreeRate

Returns the global risk-free interest rate.

function getRiskFreeRate() external view returns (int64 rate)

Returns: Rate with 8 decimals (5% = 5_000_000)


Pair Management

getPairId

Generates a pair ID from asset symbols.

function getPairId(string calldata base, string calldata quote) external view returns (bytes32 pairId)
ParameterTypeDescription
basestringBase asset (e.g., "ETH") or full pair name
quotestringQuote asset (e.g., "USD") or empty string

Example:

bytes32 pairId = oracle.getPairId("ETH", "USD");  // Returns hash of "ETH-USD"
bytes32 pairId = oracle.getPairId("ETH-USD", ""); // Same result

getRegisteredPairs

Returns all registered trading pair names.

function getRegisteredPairs() external view returns (string[] memory pairs)

isPairRegistered

Checks if a pair is registered in the oracle.

function isPairRegistered(string calldata pairName) external view returns (bool isRegistered)

Admin Functions

These functions require the onlyAdmin modifier.

Parameter Setting

setVolatility

Sets implied volatility for a trading pair.

function setVolatility(bytes32 pairId, int64 volatility) external onlyAdmin

Emits: VolatilityUpdated(pairId, volatility)


setVolatilityBatch

Sets volatility for multiple pairs in one transaction.

function setVolatilityBatch(bytes32[] calldata pairIds, int64[] calldata volatilityValues) external onlyAdmin

setPremium

Sets premium rate for a trading pair.

function setPremium(bytes32 pairId, int64 premium) external onlyAdmin

Emits: PremiumUpdated(pairId, premium)


setRiskFreeRate

Sets the global risk-free rate.

function setRiskFreeRate(int64 _riskFreeRate) external onlyAdmin

Emits: RiskFreeRateUpdated(oldRate, newRate)


Feed Configuration

setPythFeed

Configures the Pyth feed ID for a trading pair.

function setPythFeed(string calldata pairName, bytes32 feedId) external onlyAdmin

Emits: PythFeedSet(pairName, feedId)

Note: Automatically registers the pair if not already registered.


setChainlinkFeed

Configures the Chainlink feed address for a trading pair.

function setChainlinkFeed(string calldata pairName, address feedAddress) external onlyAdmin

Emits: ChainlinkFeedSet(pairName, feedAddress)


setPair

Configures both Pyth and Chainlink feeds for a pair in one call.

function setPair(
    string calldata pairName,
    bytes32 pythFeedId,
    address chainlinkFeedAddress
) external onlyAdmin

Example:

oracle.setPair(
    "ETH-USD",
    0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace, // Pyth ETH/USD
    0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419  // Chainlink ETH/USD
);

removePair

Removes a trading pair from the registry.

function removePair(string calldata pairName) external onlyAdmin

Emits: PairRemoved(pairName)


Native Price Setting

setPrice

Sets a native (fallback) price for a trading pair.

function setPrice(bytes32 pairId, int64 price) external onlyAdmin

Note: Uses default exponent (-8) and zero confidence.


setPriceWithConf

Sets a native price with confidence interval.

function setPriceWithConf(bytes32 pairId, int64 price, uint64 conf) public onlyAdmin

setPriceFull

Sets a native price with full parameters.

function setPriceFull(bytes32 pairId, int64 price, uint64 conf, int32 expo) external onlyAdmin

Owner Functions

These functions require the onlyOwner modifier.

transferOwnership

Transfers contract ownership.

function transferOwnership(address newOwner) external onlyOwner

Reverts: ZeroAddress if newOwner is zero address.


addAdmin

Grants admin privileges to an address.

function addAdmin(address admin) external onlyOwner

Reverts: AdminAlreadyExists if already an admin.

Emits: AdminAdded(admin)


removeAdmin

Revokes admin privileges.

function removeAdmin(address admin) external onlyOwner

Reverts: AdminDoesNotExist if not an admin.

Emits: AdminRemoved(admin)


setPythOracle

Updates the Pyth oracle contract address.

function setPythOracle(address _pythOracle) external onlyOwner

Emits: PythOracleUpdated(oldOracle, newOracle)


withdrawEther

Withdraws ETH from the contract. Useful for recovering accidentally sent ETH or reclaiming Pyth update fees.

function withdrawEther(address payable to, uint256 amount) external onlyOwner
ParameterTypeDescription
toaddress payableRecipient address
amountuint256Amount of ETH to withdraw

Reverts:

  • ZeroAddress if to is zero address
  • InsufficientBalance if contract balance is less than amount
  • TransferFailed if the ETH transfer fails

Events

EventParametersDescription
OwnershipTransferredpreviousOwner, newOwnerOwnership changed
AdminAddedadminAdmin privileges granted
AdminRemovedadminAdmin privileges revoked
VolatilityUpdatedpairId, volatilityVolatility changed for pair
PremiumUpdatedpairId, premiumPremium changed for pair
RiskFreeRateUpdatedoldRate, newRateGlobal rate changed
PriceUpdatedpairId, price, conf, expo, publishTimeNative price updated
PythOracleUpdatedoldOracle, newOraclePyth address changed
PythFeedSetpairName, feedIdPyth feed configured
ChainlinkFeedSetpairName, feedAddressChainlink feed configured
PairRemovedpairNamePair removed from registry

Integration Points

Depends On

ContractPurpose
Pyth NetworkPrimary price feed source
ChainlinkSecondary price feed source

Used By

ContractPurpose
DiffusalOptionsQuoterGets spot price, volatility, premium, rate for pricing
DiffusalLiquidationEngineGets volatility for penalty rate calculation
DiffusalPriceHistoryGets spot price for TWAP snapshots

Security Considerations

Access Control

Function CategoryAccess Level
View functionsPublic
Parameter settingAdmin only
Feed configurationAdmin only
Ownership transferOwner only
Admin managementOwner only

Price Staleness

The contract provides getPriceNoOlderThan() to enforce freshness requirements. Consumers should specify appropriate staleness thresholds based on their use case:

  • Liquidations: Stricter freshness (e.g., 60 seconds)
  • Price display: More lenient (e.g., 300 seconds)

Trust Assumptions

ComponentTrust Level
Pyth NetworkExternal dependency — prices could be manipulated if Pyth is compromised
ChainlinkExternal dependency — secondary fallback
Admin addressesTrusted to set reasonable volatility/premium values
Native pricesShould only be used for testing or emergency

Precision Boundaries

When converting oracle prices to WAD format for use in Black-Scholes:

// Oracle returns: price=300_000_000_000, expo=-8
// This represents $3000.00000000

// Convert to WAD (1e18):
uint256 wadPrice = uint256(price.price) * 10**(18 + uint256(int256(price.expo)));
// Result: 3000e18

Warning: Be careful with exponent handling. A wrong conversion can cause severe pricing errors.


Code Reference

Source: packages/contracts/src/DiffusalOracle.sol

Interface: packages/contracts/src/interfaces/IDiffusalOracle.sol

Testnet: View on MonadVision

Key Constants

// From Constants.sol
uint256 constant ORACLE_DECIMALS = 8;
int32 constant DEFAULT_EXPO = -8;

On this page