Diffusal
Contracts

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:

FunctionDescription
TWAP CalculationProvides 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:

ConstantValuePurpose
BUFFER_SIZE240Maximum snapshots stored per pair
BUFFER_DURATION2 hoursTotal time coverage
TWAP_WINDOW1 hourSettlement averaging window
MIN_SNAPSHOT_INTERVAL30 secondsRate 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:

TWAP=i=1nPin\text{TWAP} = \frac{\sum_{i=1}^{n} P_i}{n}

Where PiP_i 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
ParameterTypeDescription
pairIdbytes32Trading pair identifier

Requirements:

  • At least 30 seconds since last snapshot (reverts with TooFrequent)

Effect:

  1. Fetches current spot price from oracle
  2. Writes to circular buffer at head position
  3. Advances head pointer
  4. Updates last snapshot timestamp

Emits: SnapshotTaken


takeSnapshotBatch

Records snapshots for multiple trading pairs.

function takeSnapshotBatch(bytes32[] calldata pairIds) external

Behavior:

  • 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)
ParameterTypeDescription
pairIdbytes32Trading pair identifier
endTimeuint256End 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 WAD
  • timestamp: 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 onlyOwner

Emits: OracleUpdated


transferOwnership

Transfers contract ownership.

function transferOwnership(address newOwner) external onlyOwner

Emits: OwnershipTransferred


Events

EventParametersDescription
SnapshotTakenpairId, price, timestampPrice snapshot recorded
OracleUpdatedoldOracle, newOracleOracle reference changed
OwnershipTransferredpreviousOwner, newOwnerOwnership changed

TWAP Example

Scenario

ETH-USD option expires at timestamp 1700000000. Price history:

TimestampPrice
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 = 120

Settlement

// In SeriesRegistry.settle()
(uint256 twapPrice, ) = priceHistory.getTwap(pairId, series.expiry);
// Series settles at $2997 TWAP, not instantaneous price

Integration Points

Depends On

ContractPurpose
DiffusalOracleCurrent spot price for snapshots

Used By

ContractPurpose
DiffusalOptionsSeriesRegistryTWAP 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;

On this page