DiffusalPriceHistory
Price snapshot storage for TWAP-based settlement
The DiffusalPriceHistory contract stores historical price snapshots in a circular buffer and calculates Time-Weighted Average Prices (TWAP) for option settlement. This enables fair settlement prices that are resistant to manipulation by using averaged prices over a 1-hour window.
Overview
Price history serves one critical purpose:
| Function | Description |
|---|---|
| TWAP Calculation | Provides manipulation-resistant settlement prices for expired options |
Snapshot Architecture
┌─────────────────────────────────────────────────────────────────┐
│ CIRCULAR BUFFER │
│ │
│ [0] [1] [2] [3] [4] ... [237] [238] [239] │
│ │ │ │ │ │
│ └───┴───┴─────── 240 slots ────────────┘ │
│ │
│ • New snapshots overwrite oldest when full │
│ • 30-second minimum between snapshots │
│ • 2-hour buffer duration (240 × 30s) │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ TWAP WINDOW │
│ │
│ Buffer: [──────────────── 2 hours ────────────────] │
│ │ │ │
│ TWAP Window: [──── 1 hour ────────────] │
│ (at settlement) │
│ │
│ Settlement uses average of all snapshots in the 1-hour window │
│ preceding the option expiry timestamp. │
│ │
└─────────────────────────────────────────────────────────────────┘Key Concepts
Buffer Configuration
The contract uses fixed parameters optimized for settlement fairness:
| Constant | Value | Purpose |
|---|---|---|
BUFFER_SIZE | 240 | Maximum snapshots stored per pair |
BUFFER_DURATION | 2 hours | Total time coverage |
TWAP_WINDOW | 1 hour | Settlement averaging window |
MIN_SNAPSHOT_INTERVAL | 30 seconds | Rate limiting between snapshots |
Permissionless Snapshots
Anyone can take price snapshots—this is intentional:
function takeSnapshot(bytes32 pairId) external {
_takeSnapshot(pairId);
}- Incentive: Keepers or interested parties ensure snapshots are recorded
- Rate limiting: 30-second minimum prevents spam
- No cost to protocol: Gas paid by snapshot takers
TWAP Calculation
At settlement, the registry queries TWAP for the 1-hour window ending at expiry:
Where are the prices of all snapshots within the window.
Edge cases:
- No snapshots: Falls back to current spot price
- Partial window: Uses available snapshots
- Stale data: Snapshots older than the window are ignored
Storage & State
The contract uses ERC-7201 namespaced storage for upgradeability:
/// @custom:storage-location erc7201:diffusal.storage.PriceHistory
struct PriceHistoryStorage {
address owner;
address oracle;
mapping(bytes32 => PriceSnapshot[240]) snapshots; // Circular buffer
mapping(bytes32 => uint256) head; // Next write position
mapping(bytes32 => uint256) count; // Snapshots stored (max 240)
mapping(bytes32 => uint256) lastSnapshotTime; // Rate limiting
}PriceSnapshot Struct
struct PriceSnapshot {
uint256 timestamp; // Block timestamp of snapshot
uint256 price; // Price in WAD (18 decimals)
}Snapshot Functions
These functions are permissionless—anyone can call them.
takeSnapshot
Records a single price snapshot for a trading pair.
function takeSnapshot(bytes32 pairId) external| Parameter | Type | Description |
|---|---|---|
pairId | bytes32 | Trading pair identifier |
Requirements:
- At least 30 seconds since last snapshot (reverts with
TooFrequent)
Effect:
- Fetches current spot price from oracle
- Writes to circular buffer at head position
- Advances head pointer
- Updates last snapshot timestamp
Emits: SnapshotTaken
takeSnapshotBatch
Records snapshots for multiple trading pairs.
function takeSnapshotBatch(bytes32[] calldata pairIds) externalBehavior:
- Skips pairs where 30 seconds haven't passed (no revert)
- Continues processing remaining pairs
Use case: Keepers efficiently snapshot all active pairs in one transaction.
View Functions
getTwap
Calculates the Time-Weighted Average Price for a window.
function getTwap(bytes32 pairId, uint256 endTime)
external view returns (uint256 twapPrice, uint256 snapshotCount)| Parameter | Type | Description |
|---|---|---|
pairId | bytes32 | Trading pair identifier |
endTime | uint256 | End of TWAP window (typically expiry) |
Returns:
twapPrice: Average price in WAD (18 decimals)snapshotCount: Number of snapshots used (0 = fallback to spot)
Window: [endTime - TWAP_WINDOW, endTime] (1 hour)
Fallback behavior:
- No snapshots in buffer → current spot price
- No snapshots in window → current spot price
getLatestPrice
Returns the most recent snapshot for a pair.
function getLatestPrice(bytes32 pairId) external view returns (uint256 price, uint256 timestamp)Returns:
price: Latest snapshot price in WADtimestamp: When snapshot was taken
Fallback: If no snapshots, returns current spot price and block.timestamp.
needsSnapshot
Checks if a new snapshot can be taken.
function needsSnapshot(bytes32 pairId) external view returns (bool)Returns: true if:
- No snapshots exist for this pair, OR
- At least 30 seconds since last snapshot
Use case: Keepers check before calling takeSnapshot() to avoid reverts.
getSnapshotCount
Returns the number of snapshots stored for a pair.
function getSnapshotCount(bytes32 pairId) external view returns (uint256)Returns: Count (max 240).
owner
Returns the contract owner.
function owner() external view returns (address)oracle
Returns the oracle address used for price fetching.
function oracle() external view returns (address)Owner Functions
setOracle
Updates the oracle reference.
function setOracle(address oracle_) external onlyOwnerEmits: OracleUpdated
transferOwnership
Transfers contract ownership.
function transferOwnership(address newOwner) external onlyOwnerEmits: OwnershipTransferred
Events
| Event | Parameters | Description |
|---|---|---|
SnapshotTaken | pairId, price, timestamp | Price snapshot recorded |
OracleUpdated | oldOracle, newOracle | Oracle reference changed |
OwnershipTransferred | previousOwner, newOwner | Ownership changed |
TWAP Example
Scenario
ETH-USD option expires at timestamp 1700000000. Price history:
| Timestamp | Price |
|---|---|
| 1699996400 | $2980 |
| 1699996430 | $2985 |
| 1699996460 | $2990 |
| ... | ... |
| 1699999970 | $3010 |
| 1700000000 | $3015 |
Calculation
(uint256 twap, uint256 count) = priceHistory.getTwap(pairId, 1700000000);
// Window: [1699996400, 1700000000] (1 hour)
// All snapshots within window are averaged
// twap ≈ $2997 (average of ~120 snapshots)
// count = 120Settlement
// In SeriesRegistry.settle()
(uint256 twapPrice, ) = priceHistory.getTwap(pairId, series.expiry);
// Series settles at $2997 TWAP, not instantaneous priceIntegration Points
Depends On
| Contract | Purpose |
|---|---|
| DiffusalOracle | Current spot price for snapshots |
Used By
| Contract | Purpose |
|---|---|
| DiffusalOptionsSeriesRegistry | TWAP for settlement price |
Security Considerations
Manipulation Resistance
TWAP averaging prevents single-block price manipulation:
- Window size: 1 hour of data requires sustained manipulation
- Snapshot density: Up to 120 snapshots per hour
- Cost: Manipulating average requires moving price consistently
Permissionless Design
Anyone can take snapshots, which creates both opportunity and risk:
- Benefit: No single point of failure for snapshot availability
- Risk: Adversary could try to spam (mitigated by 30s rate limit)
- Incentive alignment: Parties with settlement interest ensure data quality
Fallback Behavior
When insufficient historical data exists:
if (count == 0) {
return (_getCurrentPrice(pairId), 0);
}This spot price fallback:
- Enables trading on new pairs immediately
- Reduces settlement manipulation resistance
- Should be avoided for high-value settlements
Oracle Trust
Price snapshots inherit oracle trust assumptions:
- Snapshots reflect oracle price at snapshot time
- Stale or manipulated oracle prices get recorded
- Multiple snapshots dilute impact of single bad price
Rate Limiting
The 30-second minimum interval:
- Prevents gas-based DoS attacks
- Limits storage consumption
- Still allows ~120 snapshots in TWAP window
Code Reference
Source: packages/contracts/src/DiffusalPriceHistory.sol
Interface: packages/contracts/src/interfaces/IDiffusalPriceHistory.sol
Testnet: View on MonadVision
Key Constants
// From DiffusalPriceHistory.sol
uint256 public constant BUFFER_SIZE = 240;
uint256 public constant BUFFER_DURATION = 2 hours;
uint256 public constant TWAP_WINDOW = 1 hours;
uint256 public constant MIN_SNAPSHOT_INTERVAL = 30 seconds;Related
- Options Settlement (Protocol) — How TWAP enables fair settlement
- DiffusalOptionsSeriesRegistry — Series settlement using TWAP
- DiffusalOracle — Price source for snapshots