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:
| Category | Purpose | Precision |
|---|---|---|
| Price Data | Spot prices for underlying assets | 8 decimals (int64) |
| Volatility | Implied volatility per trading pair | 8 decimals (50% = 50_000_000) |
| Premium | Premium rate per trading pair | 8 decimals |
| Risk-Free Rate | Global interest rate for Black-Scholes | 8 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:
- Pyth Network (Primary) — High-frequency, low-latency price feeds
- Chainlink (Secondary) — Decentralized oracle network
- 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 > NativePair 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):
| Value | Example | Representation |
|---|---|---|
| $3,000.00 | ETH price | 300_000_000_000 with expo=-8 |
| 50% | Volatility | 50_000_000 |
| 5% | Risk-free rate | 5_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)| Parameter | Type | Description |
|---|---|---|
pairId | bytes32 | Trading 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)| Parameter | Type | Description |
|---|---|---|
pairId | bytes32 | Trading pair identifier |
age | uint256 | Maximum 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)| Parameter | Type | Description |
|---|---|---|
base | string | Base asset (e.g., "ETH") or full pair name |
quote | string | Quote 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 resultgetRegisteredPairs
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 onlyAdminEmits: VolatilityUpdated(pairId, volatility)
setVolatilityBatch
Sets volatility for multiple pairs in one transaction.
function setVolatilityBatch(bytes32[] calldata pairIds, int64[] calldata volatilityValues) external onlyAdminsetPremium
Sets premium rate for a trading pair.
function setPremium(bytes32 pairId, int64 premium) external onlyAdminEmits: PremiumUpdated(pairId, premium)
setRiskFreeRate
Sets the global risk-free rate.
function setRiskFreeRate(int64 _riskFreeRate) external onlyAdminEmits: RiskFreeRateUpdated(oldRate, newRate)
Feed Configuration
setPythFeed
Configures the Pyth feed ID for a trading pair.
function setPythFeed(string calldata pairName, bytes32 feedId) external onlyAdminEmits: 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 onlyAdminEmits: 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 onlyAdminExample:
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 onlyAdminEmits: PairRemoved(pairName)
Native Price Setting
setPrice
Sets a native (fallback) price for a trading pair.
function setPrice(bytes32 pairId, int64 price) external onlyAdminNote: Uses default exponent (-8) and zero confidence.
setPriceWithConf
Sets a native price with confidence interval.
function setPriceWithConf(bytes32 pairId, int64 price, uint64 conf) public onlyAdminsetPriceFull
Sets a native price with full parameters.
function setPriceFull(bytes32 pairId, int64 price, uint64 conf, int32 expo) external onlyAdminOwner Functions
These functions require the onlyOwner modifier.
transferOwnership
Transfers contract ownership.
function transferOwnership(address newOwner) external onlyOwnerReverts: ZeroAddress if newOwner is zero address.
addAdmin
Grants admin privileges to an address.
function addAdmin(address admin) external onlyOwnerReverts: AdminAlreadyExists if already an admin.
Emits: AdminAdded(admin)
removeAdmin
Revokes admin privileges.
function removeAdmin(address admin) external onlyOwnerReverts: AdminDoesNotExist if not an admin.
Emits: AdminRemoved(admin)
setPythOracle
Updates the Pyth oracle contract address.
function setPythOracle(address _pythOracle) external onlyOwnerEmits: 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| Parameter | Type | Description |
|---|---|---|
to | address payable | Recipient address |
amount | uint256 | Amount of ETH to withdraw |
Reverts:
ZeroAddressiftois zero addressInsufficientBalanceif contract balance is less thanamountTransferFailedif the ETH transfer fails
Events
| Event | Parameters | Description |
|---|---|---|
OwnershipTransferred | previousOwner, newOwner | Ownership changed |
AdminAdded | admin | Admin privileges granted |
AdminRemoved | admin | Admin privileges revoked |
VolatilityUpdated | pairId, volatility | Volatility changed for pair |
PremiumUpdated | pairId, premium | Premium changed for pair |
RiskFreeRateUpdated | oldRate, newRate | Global rate changed |
PriceUpdated | pairId, price, conf, expo, publishTime | Native price updated |
PythOracleUpdated | oldOracle, newOracle | Pyth address changed |
PythFeedSet | pairName, feedId | Pyth feed configured |
ChainlinkFeedSet | pairName, feedAddress | Chainlink feed configured |
PairRemoved | pairName | Pair removed from registry |
Integration Points
Depends On
| Contract | Purpose |
|---|---|
| Pyth Network | Primary price feed source |
| Chainlink | Secondary price feed source |
Used By
| Contract | Purpose |
|---|---|
| DiffusalOptionsQuoter | Gets spot price, volatility, premium, rate for pricing |
| DiffusalLiquidationEngine | Gets volatility for penalty rate calculation |
| DiffusalPriceHistory | Gets spot price for TWAP snapshots |
Security Considerations
Access Control
| Function Category | Access Level |
|---|---|
| View functions | Public |
| Parameter setting | Admin only |
| Feed configuration | Admin only |
| Ownership transfer | Owner only |
| Admin management | Owner 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
| Component | Trust Level |
|---|---|
| Pyth Network | External dependency — prices could be manipulated if Pyth is compromised |
| Chainlink | External dependency — secondary fallback |
| Admin addresses | Trusted to set reasonable volatility/premium values |
| Native prices | Should 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: 3000e18Warning: 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;Related
- DiffusalOptionsQuoter — Uses oracle data for Black-Scholes pricing
- Protocol Design — High-level architecture
- Solidity Math — Precision conversion details