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 these 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) |
| Tick Decimals | Dynamic tick precision based on spot price | uint8 (2-12) |
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: Native > Chainlink > Pyth.
Key Concepts
Price Aggregation
The oracle queries up to three price sources for each pair:
- Native Prices (Primary) — Admin-set prices for protocol control
- Chainlink (Secondary) — Decentralized oracle network
- Pyth Network (Tertiary) — High-frequency, low-latency price feeds
┌──────────────────────────────────────┐
│ 1. Query all configured sources │
└───────────────────┬──────────────────┘
▼
┌──────────────────────────────────────┐
│ 2. Compare timestamps │
└───────────────────┬──────────────────┘
▼
┌──────────────────────────────────────┐
│ 3. Return most recent price │
└───────────────────┬──────────────────┘
▼
┌──────────────────────────────────────┐
│ 4. On timestamp tie: │
│ Native > Chainlink > Pyth │
└──────────────────────────────────────┘Pair Identification
Trading pairs are identified by the keccak256 hash of their name string:
bytes32 pairId = keccak256(bytes("ETH-USDC"));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)
getTickDecimals
Returns the tick decimal precision for a trading pair based on the current spot price. Higher-priced assets use fewer decimals for larger tick sizes.
function getTickDecimals(bytes32 pairId) external view returns (uint8 tickDecimals)| Parameter | Type | Description |
|---|---|---|
pairId | bytes32 | Trading pair identifier |
Returns: Tick decimal precision (range: 2-12)
Dynamic Scaling:
| Spot Price | Tick Decimals | Tick Size |
|---|---|---|
| >= $100 | 2 | 0.01 |
| >= $10 | 3 | 0.001 |
| >= $1 | 4 | 0.0001 |
| >= $0.1 | 5 | 0.00001 |
| >= $0.01 | 6 | 0.000001 |
| < $0.01 | 7-12 | Finer precision |
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", "USDC"); // Returns hash of "ETH-USDC"
bytes32 pairId = oracle.getPairId("ETH-USDC", ""); // 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)getRegisteredPairCount
Returns the total number of registered trading pairs.
function getRegisteredPairCount() external view returns (uint256 count)getPythFeedId
Returns the Pyth feed ID configured for a trading pair.
function getPythFeedId(string calldata pairName) external view returns (bytes32 feedId)Returns: The Pyth feed ID (bytes32(0) if not configured)
getChainlinkFeedAddress
Returns the Chainlink feed address configured for a trading pair.
function getChainlinkFeedAddress(string calldata pairName) external view returns (address feedAddress)Returns: The Chainlink feed address (address(0) if not configured)
isAdmin
Checks if an address has admin privileges.
function isAdmin(address account) external view returns (bool)Returns: true if the account has admin privileges, false otherwise.
Storage Getters
The following functions provide direct access to internal storage state:
volatilities
Returns the stored volatility for a pair.
function volatilities(bytes32 pairId) external view returns (int64)premiums
Returns the stored premium rate for a pair.
function premiums(bytes32 pairId) external view returns (int64)riskFreeRate
Returns the stored global risk-free rate.
function riskFreeRate() external view returns (int64)nativePrices
Returns a native price override for a pair (if set).
function nativePrices(bytes32 pairId) external view returns (int64 price)pythFeeds
Returns the Pyth feed ID for a pair (by name hash).
function pythFeeds(bytes32 nameHash) external view returns (bytes32 feedId)chainlinkFeeds
Returns the Chainlink feed address for a pair (by name hash).
function chainlinkFeeds(bytes32 nameHash) external view returns (address)registeredPairs
Returns a registered pair name by index.
function registeredPairs(uint256 index) external view returns (string memory)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)
setPremiumBatch
Sets premium rates for multiple pairs in one transaction.
function setPremiumBatch(bytes32[] calldata pairIds, int64[] calldata premiumValues) external onlyAdminEmits: PremiumUpdated for each pair
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-USDC",
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 onlyAdminsetPriceBatch
Sets native prices for multiple pairs in one transaction.
function setPriceBatch(bytes32[] calldata pairIds, int64[] calldata priceValues) external onlyAdminNote: Uses default exponent (-8) and zero confidence for all prices.
Emits: PriceUpdated for each pair
setPairBatch
Configures Pyth and Chainlink feeds for multiple pairs in one transaction.
function setPairBatch(
string[] calldata pairNames,
bytes32[] calldata pythFeedIds,
address[] calldata chainlinkAddresses
) external onlyAdmin| Parameter | Type | Description |
|---|---|---|
pairNames | string[] | Array of pair names |
pythFeedIds | bytes32[] | Pyth feed IDs (bytes32(0) to skip) |
chainlinkAddresses | address[] | Chainlink addresses (address(0) to skip) |
Note: Use zero values to skip configuring a specific feed type.
Owner Functions
These functions require the onlyOwner modifier.
Two-Step Ownership Transfer
The contract uses a two-step ownership transfer pattern for safety.
transferOwnership
Initiates ownership transfer to a new address. The new owner must call acceptOwnership() to complete the transfer.
function transferOwnership(address newOwner) external onlyOwnerEmits: OwnershipTransferStarted(currentOwner, newOwner)
Reverts: ZeroAddress if newOwner is zero address.
pendingOwner
Returns the address of the pending owner during a two-step transfer.
function pendingOwner() external view returns (address pendingOwnerAddress)Returns: The pending owner address (or address(0) if no transfer pending)
acceptOwnership
Completes the ownership transfer. Must be called by the pending owner.
function acceptOwnership() externalEmits: OwnershipTransferred(oldOwner, newOwner)
Reverts: NotPendingOwner if caller is not the pending owner.
Admin Management
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 nonReentrant| Parameter | Type | Description |
|---|---|---|
to | address payable | Recipient address |
amount | uint256 | Amount of ETH to withdraw |
Security: Protected by nonReentrant modifier to prevent reentrancy attacks.
Reverts:
ZeroAddressiftois zero addressInsufficientBalanceif contract balance is less thanamountTransferFailedif the ETH transfer fails
IPyth-Compatible Functions
These functions provide compatibility with the Pyth Network interface. In the current implementation, they are no-ops or return default values for testing purposes.
updatePriceFeeds
Updates price feeds (IPyth-compatible). Currently a no-op for testing.
function updatePriceFeeds(bytes[] calldata updateData) external payableNote: In production Pyth, this requires payment. For testing, it's free and does nothing. Prices are set manually via setPrice().
getUpdateFee
Returns the fee required to update price feeds.
function getUpdateFee(bytes[] calldata updateData) external pure returns (uint256 fee)Returns: Always 0 for testing. In production Pyth, this returns the actual fee.
getValidTimePeriod
Returns the valid time period for prices.
function getValidTimePeriod() external pure returns (uint256 validTimePeriod)Returns: 60 seconds (default for testing)
Events
| Event | Parameters | Description |
|---|---|---|
OwnershipTransferStarted | previousOwner, newOwner | Two-step transfer initiated |
OwnershipTransferred | previousOwner, newOwner | Ownership transfer completed |
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 | pairId (indexed), feedId (indexed), pairName | Pyth feed configured |
ChainlinkFeedSet | pairId (indexed), feedAddress (indexed), pairName | Chainlink feed configured |
PairRemoved | pairId (indexed), pairName | Pair removed from registry |
Integration Points
Depends On
| Contract | Purpose |
|---|---|
| Pyth Network | Tertiary price feed source |
| Chainlink | Secondary price feed source |
Used By
| Contract | Purpose |
|---|---|
| DiffusalOptionsQuoter | Gets spot price, volatility, premium, rate for pricing |
| DiffusalOptionsOrderBook | Gets tick decimals for dynamic pricing precision |
| DiffusalLiquidationEngine | Gets volatility for penalty rate calculation |
| DiffusalSettlementReadinessLiquidator | 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
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