Aerodrome implements a Time-Weighted Average Price (TWAP) oracle directly within its liquidity pool contracts, providing a decentralized and manipulation-resistant price feed mechanism. This document provides a comprehensive analysis of the TWAP functionality and how to reliably use Aero pools as price oracles.
The TWAP implementation in Aerodrome uses the following key data structure defined in contracts/interfaces/IPool.sol:32-37:
struct Observation {
uint256 timestamp;
uint256 reserve0Cumulative;
uint256 reserve1Cumulative;
}Each pool maintains:
- An array of
Observationstructs stored incontracts/Pool.sol:44 - Cumulative reserve trackers:
reserve0CumulativeLastandreserve1CumulativeLastatcontracts/Pool.sol:57-59 - Fixed observation period of 1800 seconds (30 minutes) defined at
contracts/Pool.sol:42
The price accumulation occurs in the _update function at contracts/Pool.sol:219-236:
-
Continuous Accumulation: On every block where the pool is interacted with, cumulative reserves are updated:
reserve0CumulativeLast += _reserve0 * timeElapsed; reserve1CumulativeLast += _reserve1 * timeElapsed;
-
Periodic Snapshots: Every 30 minutes, a new observation is recorded:
if (timeElapsed > periodSize) { observations.push(Observation(blockTimestamp, reserve0CumulativeLast, reserve1CumulativeLast)); }
This dual mechanism ensures continuous price tracking with periodic checkpoints for efficient historical queries.
Aerodrome provides multiple functions to query TWAP prices, each suited for different use cases:
Located at contracts/Pool.sol:259-267
- Purpose: Returns a single TWAP price averaged over multiple observation points
- Parameters:
tokenIn: The input token addressamountIn: Amount of input tokengranularity: Number of observations to average
- Usage: Best for simple price queries requiring smoothed prices
Located at contracts/Pool.sol:270-272
- Purpose: Returns an array of TWAP prices at different observation points
- Parameters:
tokenIn: The input token addressamountIn: Amount of input tokenpoints: Number of price points to return
- Usage: Useful for analyzing price trends over time
Located at contracts/Pool.sol:275-302
- Purpose: Advanced TWAP query with configurable observation windows
- Parameters:
tokenIn: The input token addressamountIn: Amount of input tokenpoints: Number of price points to returnwindow: Multiplier for observation intervals (e.g., 2 = 1 hour intervals)
- Usage: Most flexible option for custom TWAP calculations
Located at contracts/Pool.sol:239-256
- Purpose: Returns current cumulative prices with counterfactual calculations
- Returns: Current cumulative reserves and block timestamp
- Usage: For protocols needing to calculate custom TWAP intervals
Based on the test at test/Oracle.t.sol:128-133, here's how to use the TWAP oracle:
// Query TWAP price for USDC -> FRAX conversion
uint256 fraxOut = pool.quote(address(USDC), 1e9, 1); // 1000 USDC with granularity of 1
// Query TWAP price for FRAX -> USDC conversion
uint256 usdcOut = pool.quote(address(FRAX), 1e21, 1); // 1000 FRAX with granularity of 1- 30-Minute Observation Period: The fixed 30-minute observation window at
contracts/Pool.sol:42provides significant manipulation resistance - Cumulative Price Tracking: Continuous accumulation makes spot price manipulation ineffective for TWAP manipulation
- Multiple Observations: Using higher granularity in
quote()increases resistance to short-term manipulation
- Automatic Updates: Prices update on every interaction with the pool via
_update()atcontracts/Pool.sol:219-236 - Minimum Observations: Ensure sufficient observations exist before relying on TWAP data
- Check
observationLength(): Usecontracts/Pool.sol:114-116to verify adequate price history
- Liquidity Depth: TWAP accuracy depends on pool liquidity depth
- Minimum K Requirement: Stable pools enforce minimum K at
contracts/Pool.sol:318to ensure pricing stability - Reserve Validation: Always verify pools have sufficient reserves before using as oracles
// Use multiple observation points for better accuracy
uint256 avgPrice = pool.quote(tokenIn, amountIn, 5); // Average over 5 observations// Get multiple price points to detect anomalies
uint256[] memory priceHistory = pool.prices(tokenIn, amountIn, 10);
// Analyze price variance and trendsFor critical applications, validate prices across multiple pools:
- Compare stable and volatile pool prices
- Use multiple pool pairs for triangulation
- Implement sanity checks against expected price ranges
// Verify recent observations
uint256 obsLength = pool.observationLength();
Observation memory lastObs = pool.lastObservation();
require(block.timestamp - lastObs.timestamp < 3600, "Stale price data");- Aerodrome stores periodic observations (every 30 min) vs. V2's continuous updates
- More gas-efficient for oracle queries
- Built-in multi-point TWAP functions
- Aerodrome uses reserve-based TWAP (like V2) vs. V3's tick-based system
- Fixed observation periods vs. V3's flexible storage
- Simpler implementation suitable for both stable and volatile pools
- Lending Protocols: Use
quote()with granularity 3-5 for collateral pricing - Derivatives: Use
sample()with custom windows for mark price calculations - Liquidations: Implement multiple observation points with sanity checks
- Regular Updates: Query prices at consistent intervals
- Multiple Pools: Aggregate prices from multiple pool sources
- Outlier Detection: Implement statistical analysis on price arrays
Always implement proper error handling:
- Check for zero liquidity pools
- Validate observation array length
- Handle pools with insufficient price history
- Implement fallback oracles for critical systems
Aerodrome's TWAP oracle provides a robust, manipulation-resistant price feed directly integrated into the liquidity pools. By understanding the observation mechanism, utilizing appropriate query functions, and following security best practices, protocols can reliably use Aero pools as decentralized price oracles. The 30-minute observation period strikes a balance between gas efficiency and manipulation resistance, making it suitable for a wide range of DeFi applications.