Quantillon Protocol

Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ChainlinkOracle

Git Source

Inherits: IChainlinkOracle, Initializable, AccessControlUpgradeable, PausableUpgradeable, UUPSUpgradeable

Author: Quantillon Labs - Nicolas Bellengé - @chewbaccoin

EUR/USD and USDC/USD price manager for Quantillon Protocol

Key features:

  • Fetch EUR/USD price from Chainlink
  • Validate USDC/USD (should remain close to $1.00)
  • Circuit breakers against outlier prices
  • Fallbacks in case of oracle outage
  • Data freshness checks

Note: security-contact: team@quantillon.money

State Variables

ORACLE_MANAGER_ROLE

Role to manage oracle configurations

bytes32 public constant ORACLE_MANAGER_ROLE = keccak256("ORACLE_MANAGER_ROLE")

EMERGENCY_ROLE

Role for emergency actions

bytes32 public constant EMERGENCY_ROLE = keccak256("EMERGENCY_ROLE")

UPGRADER_ROLE

Role for contract upgrades

bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE")

MAX_PRICE_STALENESS

Maximum duration before a price is considered stale (1 hour)

3600 seconds = reasonable limit for real-time DeFi

uint256 public constant MAX_PRICE_STALENESS = 3600

MAX_PRICE_DEVIATION

Maximum allowed deviation from previous price (5%)

500 basis points = 5% in basis points (500/10000)

uint256 public constant MAX_PRICE_DEVIATION = 500

BASIS_POINTS

Basis for basis points calculations

uint256 public constant BASIS_POINTS = 10000

MAX_TIMESTAMP_DRIFT

Maximum timestamp drift tolerance (15 minutes)

Prevents timestamp manipulation attacks by miners

uint256 public constant MAX_TIMESTAMP_DRIFT = 900

BLOCKS_PER_HOUR

Blocks per hour for block-based staleness checks

~12 second blocks on Ethereum, ~2 second blocks on L2s

uint256 public constant BLOCKS_PER_HOUR = 300

eurUsdPriceFeed

Interface to Chainlink EUR/USD price feed

AggregatorV3Interface public eurUsdPriceFeed

usdcUsdPriceFeed

Interface to Chainlink USDC/USD price feed

Used for USDC price validation and cross-checking

Should be the official USDC/USD Chainlink feed

AggregatorV3Interface public usdcUsdPriceFeed

treasury

Treasury address for ETH recovery

SECURITY: Only this address can receive ETH from recoverETH function

address public treasury

minEurUsdPrice

Minimum accepted EUR/USD price (lower circuit breaker)

Initialized to 0.80 USD per EUR (extreme crisis)

uint256 public minEurUsdPrice

maxEurUsdPrice

Maximum accepted EUR/USD price (upper circuit breaker)

Initialized to 1.40 USD per EUR (extreme scenario)

uint256 public maxEurUsdPrice

lastValidEurUsdPrice

Last valid EUR/USD price recorded (18 decimals)

Used as fallback if oracle is down

uint256 public lastValidEurUsdPrice

lastPriceUpdateTime

Timestamp of the last valid price update

uint256 public lastPriceUpdateTime

lastPriceUpdateBlock

Block number of the last valid price update

Used for block-based staleness checks to prevent timestamp manipulation

uint256 public lastPriceUpdateBlock

circuitBreakerTriggered

Circuit breaker status (true = triggered, fixed prices)

bool public circuitBreakerTriggered

usdcToleranceBps

USDC/USD tolerance (USDC should remain close to $1.00)

200 basis points = 2% (USDC can vary between 0.98 and 1.02)

uint256 public usdcToleranceBps

TIME_PROVIDER

TimeProvider contract for centralized time management

Used to replace direct block.timestamp usage for testability and consistency

TimeProvider public immutable TIME_PROVIDER

Functions

constructor

Constructor for ChainlinkOracle contract

Initializes the TimeProvider and disables initializers for proxy pattern

Notes:

  • security: Validates TimeProvider address is not zero

  • validation: Validates _TIME_PROVIDER is not address(0)

  • state-changes: Sets TIME_PROVIDER immutable variable and disables initializers

  • events: No events emitted

  • errors: Throws "Zero address" if _TIME_PROVIDER is address(0)

  • reentrancy: Not applicable - constructor

  • access: Public - anyone can deploy

  • oracle: No oracle dependencies

  • oz-upgrades-unsafe-allow: constructor

constructor(TimeProvider _TIME_PROVIDER) ;

Parameters

NameTypeDescription
_TIME_PROVIDERTimeProviderAddress of the TimeProvider contract for centralized time management

initialize

Initializes the oracle contract with Chainlink price feeds

Sets up all core dependencies, roles, and default configuration parameters

Notes:

  • security: Validates all addresses are not zero, grants admin roles

  • validation: Validates all input addresses are not address(0)

  • state-changes: Initializes all state variables, sets default price bounds

  • events: Emits PriceUpdated during initial price update

  • errors: Throws "Oracle: Admin cannot be zero" if admin is address(0)

  • reentrancy: Protected by initializer modifier

  • access: Public - only callable once during deployment

  • oracle: Initializes Chainlink price feed interfaces

function initialize(address admin, address _eurUsdPriceFeed, address _usdcUsdPriceFeed, address _treasury)
    public
    initializer;

Parameters

NameTypeDescription
adminaddressAddress with administrator privileges
_eurUsdPriceFeedaddressChainlink EUR/USD price feed address on Base
_usdcUsdPriceFeedaddressChainlink USDC/USD price feed address on Base
_treasuryaddressTreasury address for ETH recovery

updateTreasury

Update treasury address

SECURITY: Only admin can update treasury address

Notes:

  • security: Validates input parameters and enforces security checks

  • validation: Validates input parameters and business logic constraints

  • state-changes: Updates contract state variables

  • events: Emits relevant events for state changes

  • errors: Throws custom errors for invalid conditions

  • reentrancy: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function updateTreasury(address _treasury) external onlyRole(DEFAULT_ADMIN_ROLE);

Parameters

NameTypeDescription
_treasuryaddressNew treasury address

unpause

Removes pause and resumes oracle operations

Allows emergency role to unpause the oracle after resolving issues

Notes:

  • security: Validates emergency role authorization

  • validation: No input validation required

  • state-changes: Removes pause state, resumes oracle operations

  • events: Emits Unpaused event from OpenZeppelin

  • errors: No errors thrown - safe unpause operation

  • reentrancy: Not protected - no external calls

  • access: Restricted to EMERGENCY_ROLE

  • oracle: No oracle dependencies for unpause

function unpause() external onlyRole(EMERGENCY_ROLE);

_divRound

Performs division with proper rounding to nearest integer

Adds half the divisor before division to achieve proper rounding

Notes:

  • security: Validates denominator is not zero to prevent division by zero

  • validation: Validates b > 0

  • state-changes: No state changes - pure function

  • events: No events emitted

  • errors: Throws "Oracle: Division by zero" if b is 0

  • reentrancy: Not applicable - pure function

  • access: Internal function - no access restrictions

  • oracle: No oracle dependencies

function _divRound(uint256 a, uint256 b) internal pure returns (uint256);

Parameters

NameTypeDescription
auint256Numerator
buint256Denominator

Returns

NameTypeDescription
<none>uint256Result of division with rounding to nearest

_validateTimestamp

Validates if a timestamp is recent enough to prevent manipulation attacks

Checks timestamp is not in future and not too old beyond staleness + drift limits

Notes:

  • security: Validates timestamp is not in future and within acceptable age

  • validation: Validates reportedTime <= currentTime and within MAX_PRICE_STALENESS + MAX_TIMESTAMP_DRIFT

  • state-changes: No state changes - view function

  • events: No events emitted

  • errors: No errors thrown - safe view function

  • reentrancy: Not applicable - view function

  • access: Internal function - no access restrictions

  • oracle: No oracle dependencies for timestamp validation

function _validateTimestamp(uint256 reportedTime) internal view returns (bool);

Parameters

NameTypeDescription
reportedTimeuint256The timestamp to validate

Returns

NameTypeDescription
<none>booltrue if the timestamp is valid, false otherwise

_updatePrices

Updates and validates internal prices

Internal function called during initialization and resets, fetches fresh prices from Chainlink

Notes:

  • security: Validates price data integrity, circuit breaker bounds, and deviation limits

  • validation: Validates roundId == answeredInRound, startedAt <= updatedAt, price > 0

  • state-changes: Updates lastValidEurUsdPrice, lastPriceUpdateTime, lastPriceUpdateBlock

  • events: Emits PriceUpdated with current prices or CircuitBreakerTriggered if invalid

  • errors: Throws "EUR/USD price data is stale" if roundId != answeredInRound

  • reentrancy: Not protected - internal function only

  • access: Internal function - no access restrictions

  • oracle: Fetches fresh prices from Chainlink EUR/USD and USDC/USD feeds

function _updatePrices() internal;

_scalePrice

Scale price to 18 decimals for consistency

Converts Chainlink price from its native decimals to 18 decimals with proper rounding

Notes:

  • security: Validates rawPrice > 0 and handles decimal conversion safely

  • validation: Validates rawPrice > 0, returns 0 if invalid

  • state-changes: No state changes - pure function

  • events: No events emitted

  • errors: No errors thrown - safe arithmetic used

  • reentrancy: Not applicable - pure function

  • access: Internal function - no access restrictions

  • oracle: No oracle dependencies for price scaling

function _scalePrice(int256 rawPrice, uint8 decimals) internal pure returns (uint256);

Parameters

NameTypeDescription
rawPriceint256Raw price from Chainlink
decimalsuint8Number of decimals in raw price

Returns

NameTypeDescription
<none>uint256Scaled price with 18 decimals

getOracleHealth

Retrieves the oracle global health status

Checks freshness of both price feeds and overall system health

Notes:

  • security: Validates price feed connectivity and data integrity

  • validation: No input validation required - view function

  • state-changes: No state changes - view function only

  • events: No events emitted

  • errors: No errors thrown - safe view function with try/catch

  • reentrancy: Not applicable - view function

  • access: Public - anyone can check oracle health

  • oracle: Checks connectivity to Chainlink EUR/USD and USDC/USD feeds

function getOracleHealth() external view returns (bool isHealthy, bool eurUsdFresh, bool usdcUsdFresh);

Returns

NameTypeDescription
isHealthybooltrue if everything operates normally
eurUsdFreshbooltrue if EUR/USD price is fresh
usdcUsdFreshbooltrue if USDC/USD price is fresh

getEurUsdDetails

Retrieves detailed information about the EUR/USD price

Provides comprehensive EUR/USD price data including staleness and bounds checks

Notes:

  • security: Validates price feed data integrity and circuit breaker status

  • validation: No input validation required - view function

  • state-changes: No state changes - view function only

  • events: No events emitted

  • errors: No errors thrown - safe view function with try/catch

  • reentrancy: Not applicable - view function

  • access: Public - anyone can query EUR/USD details

  • oracle: Fetches fresh data from Chainlink EUR/USD price feed

function getEurUsdDetails()
    external
    view
    returns (uint256 currentPrice, uint256 lastValidPrice, uint256 lastUpdate, bool isStale, bool withinBounds);

Returns

NameTypeDescription
currentPriceuint256Current price (may be fallback)
lastValidPriceuint256Last validated price
lastUpdateuint256Timestamp of last update
isStalebooltrue if the price is stale
withinBoundsbooltrue if within acceptable bounds

getOracleConfig

Retrieves current configuration parameters

Returns all key configuration values for oracle operations

Notes:

  • security: No security validations required - view function

  • validation: No input validation required - view function

  • state-changes: No state changes - view function only

  • events: No events emitted

  • errors: No errors thrown - safe view function

  • reentrancy: Not applicable - view function

  • access: Public - anyone can query configuration

  • oracle: No oracle dependencies for configuration query

function getOracleConfig()
    external
    view
    returns (
        uint256 minPrice,
        uint256 maxPrice,
        uint256 maxStaleness,
        uint256 usdcTolerance,
        bool circuitBreakerActive
    );

Returns

NameTypeDescription
minPriceuint256Minimum EUR/USD price
maxPriceuint256Maximum EUR/USD price
maxStalenessuint256Maximum duration before staleness
usdcToleranceuint256USDC tolerance in basis points
circuitBreakerActiveboolCircuit breaker status

getPriceFeedAddresses

Retrieves addresses of the Chainlink price feeds used

Returns feed addresses and their decimal configurations

Notes:

  • security: No security validations required - view function

  • validation: No input validation required - view function

  • state-changes: No state changes - view function only

  • events: No events emitted

  • errors: No errors thrown - safe view function

  • reentrancy: Not applicable - view function

  • access: Public - anyone can query feed addresses

  • oracle: Queries decimal configuration from Chainlink feeds

function getPriceFeedAddresses()
    external
    view
    returns (address eurUsdFeedAddress, address usdcUsdFeedAddress, uint8 eurUsdDecimals, uint8 usdcUsdDecimals);

Returns

NameTypeDescription
eurUsdFeedAddressaddressEUR/USD feed address
usdcUsdFeedAddressaddressUSDC/USD feed address
eurUsdDecimalsuint8Number of decimals for the EUR/USD feed
usdcUsdDecimalsuint8Number of decimals for the USDC/USD feed

checkPriceFeedConnectivity

Tests connectivity to the Chainlink price feeds

Tests if both price feeds are responding and returns latest round information

Notes:

  • security: Validates price feed connectivity and data integrity

  • validation: No input validation required - view function

  • state-changes: No state changes - view function only

  • events: No events emitted

  • errors: No errors thrown - safe view function with try/catch

  • reentrancy: Not applicable - view function

  • access: Public - anyone can test feed connectivity

  • oracle: Tests connectivity to Chainlink EUR/USD and USDC/USD feeds

function checkPriceFeedConnectivity()
    external
    view
    returns (bool eurUsdConnected, bool usdcUsdConnected, uint80 eurUsdLatestRound, uint80 usdcUsdLatestRound);

Returns

NameTypeDescription
eurUsdConnectedbooltrue if the EUR/USD feed responds
usdcUsdConnectedbooltrue if the USDC/USD feed responds
eurUsdLatestRounduint80Latest round ID for EUR/USD
usdcUsdLatestRounduint80Latest round ID for USDC/USD

_authorizeUpgrade

Authorizes oracle contract upgrades

function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE);

Parameters

NameTypeDescription
newImplementationaddressAddress of the new implementation

recoverToken

Recovers tokens accidentally sent to the contract to treasury only

Emergency function to recover ERC20 tokens that are not part of normal operations

Notes:

  • security: Validates admin role and uses secure recovery library

  • validation: No input validation required - library handles validation

  • state-changes: Transfers tokens from contract to treasury

  • events: No events emitted - library handles events

  • errors: No errors thrown - library handles error cases

  • reentrancy: Not protected - library handles reentrancy

  • access: Restricted to DEFAULT_ADMIN_ROLE

  • oracle: No oracle dependencies for token recovery

function recoverToken(address token, uint256 amount) external onlyRole(DEFAULT_ADMIN_ROLE);

Parameters

NameTypeDescription
tokenaddressAddress of the token to recover
amountuint256Amount to recover

recoverETH

Recover ETH to treasury address only

SECURITY: Restricted to treasury to prevent arbitrary ETH transfers

Security considerations:

  • Only DEFAULT_ADMIN_ROLE can recover
  • Prevents sending to zero address
  • Validates balance before attempting transfer
  • Uses call() for reliable ETH transfers to any contract

Notes:

  • security: Validates input parameters and enforces security checks

  • validation: Validates input parameters and business logic constraints

  • state-changes: Updates contract state variables

  • events: Emits relevant events for state changes

  • errors: Throws custom errors for invalid conditions

  • reentrancy: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function recoverETH() external onlyRole(DEFAULT_ADMIN_ROLE);

resetCircuitBreaker

Resets the circuit breaker and resumes oracle usage

Emergency action after resolving an incident. Restarts price updates and disables fallback mode.

Notes:

  • security: Validates input parameters and enforces security checks

  • validation: Validates input parameters and business logic constraints

  • state-changes: Updates contract state variables

  • events: Emits relevant events for state changes

  • errors: Throws custom errors for invalid conditions

  • reentrancy: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function resetCircuitBreaker() external onlyRole(EMERGENCY_ROLE);

triggerCircuitBreaker

Manually triggers the circuit breaker

Used when the team detects an issue with the oracles. Forces the use of the last known valid price.

Notes:

  • security: Validates input parameters and enforces security checks

  • validation: Validates input parameters and business logic constraints

  • state-changes: Updates contract state variables

  • events: Emits relevant events for state changes

  • errors: Throws custom errors for invalid conditions

  • reentrancy: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function triggerCircuitBreaker() external onlyRole(EMERGENCY_ROLE);

pause

Pauses all oracle operations

Emergency function to pause oracle in case of critical issues

Notes:

  • security: Validates emergency role authorization

  • validation: No input validation required

  • state-changes: Sets pause state, stops oracle operations

  • events: Emits Paused event from OpenZeppelin

  • errors: No errors thrown - safe pause operation

  • reentrancy: Not protected - no external calls

  • access: Restricted to EMERGENCY_ROLE

  • oracle: No oracle dependencies for pause

function pause() external onlyRole(EMERGENCY_ROLE);

getEurUsdPrice

Retrieves the current EUR/USD price with full validation

Validation process:

  1. Check circuit breaker status
  2. Fetch from Chainlink
  3. Freshness check (< 1 hour)
  4. Convert to 18 decimals
  5. Check min/max bounds
  6. Return valid price or fallback

Notes:

  • security: Validates timestamp freshness, circuit breaker status, price bounds

  • validation: Checks price > 0, timestamp < 1 hour old, within min/max bounds

  • state-changes: No state changes - view function only

  • events: No events emitted

  • errors: No errors thrown - returns fallback price if invalid

  • reentrancy: Not applicable - view function

  • access: Public - no access restrictions

  • oracle: Requires fresh Chainlink EUR/USD price feed data

function getEurUsdPrice() external view returns (uint256 price, bool isValid);

Returns

NameTypeDescription
priceuint256EUR/USD price in 18 decimals (e.g., 1.10e18 = 1.10 USD per EUR)
isValidbooltrue if the price is fresh and within acceptable bounds

getUsdcUsdPrice

Retrieves the USDC/USD price with validation

USDC is expected to maintain parity with USD. A large deviation indicates a systemic issue.

Notes:

  • security: Validates timestamp freshness, USDC tolerance bounds

  • validation: Checks price > 0, timestamp < 1 hour old, within USDC tolerance

  • state-changes: No state changes - view function only

  • events: No events emitted

  • errors: No errors thrown - returns $1.00 fallback if invalid

  • reentrancy: Not applicable - view function

  • access: Public - no access restrictions

  • oracle: Requires fresh Chainlink USDC/USD price feed data

function getUsdcUsdPrice() external view returns (uint256 price, bool isValid);

Returns

NameTypeDescription
priceuint256USDC/USD price in 18 decimals (should be close to 1.0e18)
isValidbooltrue if USDC remains close to $1.00

updatePriceBounds

Updates price bounds for the circuit breaker

Allows oracle manager to adjust price thresholds based on market conditions

Notes:

  • security: Validates oracle manager role and price bounds constraints

  • validation: Validates _minPrice > 0, _maxPrice > _minPrice, _maxPrice < 10e18

  • state-changes: Updates minEurUsdPrice and maxEurUsdPrice

  • events: Emits PriceBoundsUpdated with new bounds

  • errors: Throws "Oracle: Min price must be positive" if _minPrice <= 0

  • reentrancy: Not protected - no external calls

  • access: Restricted to ORACLE_MANAGER_ROLE

  • oracle: No oracle dependencies for bounds update

function updatePriceBounds(uint256 _minPrice, uint256 _maxPrice) external onlyRole(ORACLE_MANAGER_ROLE);

Parameters

NameTypeDescription
_minPriceuint256Minimum accepted EUR/USD price (18 decimals)
_maxPriceuint256Maximum accepted EUR/USD price (18 decimals)

updateUsdcTolerance

Updates the tolerance for USDC/USD

Allows oracle manager to adjust USDC price tolerance around $1.00

Notes:

  • security: Validates oracle manager role and tolerance constraints

  • validation: Validates newToleranceBps <= 1000 (max 10%)

  • state-changes: Updates usdcToleranceBps

  • events: No events emitted for tolerance update

  • errors: Throws "Oracle: Tolerance too high" if newToleranceBps > 1000

  • reentrancy: Not protected - no external calls

  • access: Restricted to ORACLE_MANAGER_ROLE

  • oracle: No oracle dependencies for tolerance update

function updateUsdcTolerance(uint256 newToleranceBps) external onlyRole(ORACLE_MANAGER_ROLE);

Parameters

NameTypeDescription
newToleranceBpsuint256New tolerance in basis points (e.g., 200 = 2%)

updatePriceFeeds

Updates the Chainlink price feed addresses

Allows oracle manager to update price feed addresses for maintenance or upgrades

Notes:

  • security: Validates oracle manager role and feed address constraints

  • validation: Validates both feed addresses are not address(0)

  • state-changes: Updates eurUsdPriceFeed and usdcUsdPriceFeed interfaces

  • events: Emits PriceFeedsUpdated with new feed addresses

  • errors: Throws "Oracle: EUR/USD feed cannot be zero" if _eurUsdFeed is address(0)

  • reentrancy: Not protected - no external calls

  • access: Restricted to ORACLE_MANAGER_ROLE

  • oracle: Updates Chainlink price feed interface addresses

function updatePriceFeeds(address _eurUsdFeed, address _usdcUsdFeed) external onlyRole(ORACLE_MANAGER_ROLE);

Parameters

NameTypeDescription
_eurUsdFeedaddressNew EUR/USD feed address
_usdcUsdFeedaddressNew USDC/USD feed address

Events

PriceUpdated

Emitted on each valid price update

OPTIMIZED: Indexed timestamp for efficient time-based filtering

event PriceUpdated(uint256 eurUsdPrice, uint256 usdcUsdPrice, uint256 indexed timestamp);

CircuitBreakerTriggered

Emitted when the circuit breaker is triggered

OPTIMIZED: Indexed reason for efficient filtering by trigger type

event CircuitBreakerTriggered(uint256 attemptedPrice, uint256 lastValidPrice, string indexed reason);

CircuitBreakerReset

Emitted when the circuit breaker is reset

event CircuitBreakerReset(address indexed admin);

PriceBoundsUpdated

Emitted when price bounds are modified

OPTIMIZED: Indexed bound type for efficient filtering

event PriceBoundsUpdated(string indexed boundType, uint256 newMinPrice, uint256 newMaxPrice);

PriceFeedsUpdated

Emitted when price feed addresses are updated

event PriceFeedsUpdated(address newEurUsdFeed, address newUsdcUsdFeed);

TreasuryUpdated

Emitted when treasury address is updated

event TreasuryUpdated(address indexed newTreasury);

Parameters

NameTypeDescription
newTreasuryaddressNew treasury address

ETHRecovered

Emitted when ETH is recovered from the contract

event ETHRecovered(address indexed to, uint256 amount);