ChainlinkOracle
Inherits: IChainlinkOracle, Initializable, AccessControlUpgradeable, PausableUpgradeable, UUPSUpgradeable, IVersioned
Title: ChainlinkOracle
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
Constants
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 the EUR/USD price is considered stale (2 hours)
7200 seconds gives the primary FX feed limited heartbeat headroom while keeping mint/redeem pricing fresh.
uint256 public constant MAX_PRICE_STALENESS = 2 hours
MAX_USDC_PRICE_STALENESS
Maximum duration before the USDC/USD price is considered stale (25 hours)
Chainlink stablecoin feeds often refresh on a daily heartbeat; this is slightly above that cadence.
uint256 public constant MAX_USDC_PRICE_STALENESS = 25 hours
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
DEV_MODE_DELAY
MED-1: Minimum delay before a proposed dev-mode change takes effect
uint256 public constant DEV_MODE_DELAY = 48 hours
TIME_PROVIDER
TimeProvider contract for centralized time management
Used to replace direct block.timestamp usage for testability and consistency
TimeProvider public immutable TIME_PROVIDER
State Variables
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
sequencerUptimeFeed
Optional Chainlink L2 sequencer uptime feed (Base/Arbitrum/etc)
Set to address(0) on L1 (check skipped). On Base set to the Chainlink sequencer feed.
AggregatorV3Interface public sequencerUptimeFeed
sequencerGracePeriod
Grace period after sequencer restart before prices are trusted again
Recommended: 3600 seconds (1 hour) to allow stale data to flush
uint256 public sequencerGracePeriod
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
devModeEnabled
Dev mode flag to disable spread deviation checks
When enabled, price deviation checks are skipped (dev/testing only)
bool public devModeEnabled
pendingDevMode
MED-1: Pending dev-mode value awaiting the timelock delay
bool public pendingDevMode
devModePendingAt
MED-1: Timestamp at which pendingDevMode may be applied (0 = no pending proposal)
uint256 public devModePendingAt
Functions
version
Returns the semantic version of this implementation.
Pure getter (no storage slot) read through the proxy, so it reflects the deployed
implementation. Bump per semver on any change; enforced by make check-version-bump.
See deployments/{chainId}/versions.json for the deployed impl/commit provenance.
Notes:
-
security: No security implications - returns a compile-time constant.
-
validation: No input validation required.
-
state-changes: None - pure function.
-
events: None.
-
errors: None.
-
reentrancy: Not applicable - pure function.
-
access: Public - anyone can read the version.
-
oracle: No oracle dependencies.
function version() external pure virtual override returns (string memory);
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | Semantic version string (e.g. "1.0.0"). |
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
| Name | Type | Description |
|---|---|---|
_TIME_PROVIDER | TimeProvider | Address 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
| Name | Type | Description |
|---|---|---|
admin | address | Address with administrator privileges |
_eurUsdPriceFeed | address | Chainlink EUR/USD price feed address on Base |
_usdcUsdPriceFeed | address | Chainlink USDC/USD price feed address on Base |
_treasury | address | Treasury 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: Not protected by a reentrancy guard
-
access: Restricted to authorized roles
-
oracle: Not applicable - no oracle dependency
function updateTreasury(address _treasury) external onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
_treasury | address | New 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
| Name | Type | Description |
|---|---|---|
a | uint256 | Numerator |
b | uint256 | Denominator |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | Result 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
| Name | Type | Description |
|---|---|---|
reportedTime | uint256 | The timestamp to validate |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | true if the timestamp is valid, false otherwise |
_validateTimestampWithMaxAge
Validates a feed timestamp against a caller-selected maximum age.
Rejects future-dated timestamps and timestamps older than maxStaleness + MAX_TIMESTAMP_DRIFT.
Notes:
-
security: Validates timestamp is not in future and within acceptable age.
-
validation: Validates reportedTime <= currentTime and within maxStaleness + 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 _validateTimestampWithMaxAge(uint256 reportedTime, uint256 maxStaleness) internal view returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
reportedTime | uint256 | Timestamp reported by the feed. |
maxStaleness | uint256 | Maximum accepted age before drift tolerance. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | true if the timestamp is valid, false otherwise. |
_validateEurUsdPriceData
Validates a raw EUR/USD feed value against freshness, bounds, and deviation policy.
Checks freshness via _validateTimestamp, scales the raw price, enforces the [minEurUsdPrice, maxEurUsdPrice] band, and (unless devModeEnabled) rejects a price deviating more than MAX_PRICE_DEVIATION bps from lastValidEurUsdPrice.
Notes:
-
security: Enforces staleness, circuit-breaker bounds, and deviation limits before acceptance.
-
validation: Returns isValid=false on stale, non-positive, out-of-bounds, or over-deviation input.
-
state-changes: None - view function.
-
events: None.
-
errors: None - signals failure via the (0, false) return.
-
reentrancy: Not applicable - view function.
-
access: Internal - no access restrictions.
-
oracle: Reads cached bounds/baseline; does not call external feeds.
function _validateEurUsdPriceData(int256 rawPrice, uint256 updatedAt, uint8 feedDecimals)
internal
view
returns (uint256 price, bool isValid);
Parameters
| Name | Type | Description |
|---|---|---|
rawPrice | int256 | Raw EUR/USD price from the feed. |
updatedAt | uint256 | Feed update timestamp. |
feedDecimals | uint8 | Number of decimals used by the feed. |
Returns
| Name | Type | Description |
|---|---|---|
price | uint256 | Scaled EUR/USD price, or 0 if validation fails before scaling. |
isValid | bool | True when the price can be accepted as the next oracle baseline. |
_normalizeUsdcUsdPrice
Normalizes USDC/USD for PriceUpdated events, falling back to $1.00 if invalid.
Returns $1.00 (1e18) unless the feed value is fresh (within MAX_USDC_PRICE_STALENESS), positive, and within usdcToleranceBps of $1.00, in which case the scaled price is returned.
Notes:
-
security: Bounds USDC to a tolerance band so a depegged/stale USDC feed cannot distort events.
-
validation: Falls back to 1e18 on stale, non-positive, or out-of-tolerance input.
-
state-changes: None - view function.
-
events: None.
-
errors: None - falls back to 1e18.
-
reentrancy: Not applicable - view function.
-
access: Internal - no access restrictions.
-
oracle: Used only to enrich PriceUpdated events; does not gate EUR/USD reads.
function _normalizeUsdcUsdPrice(int256 rawPrice, uint256 updatedAt, uint8 feedDecimals)
internal
view
returns (uint256 usdcUsdPrice);
Parameters
| Name | Type | Description |
|---|---|---|
rawPrice | int256 | Raw USDC/USD price from the feed. |
updatedAt | uint256 | Feed update timestamp. |
feedDecimals | uint8 | Number of decimals used by the feed. |
Returns
| Name | Type | Description |
|---|---|---|
usdcUsdPrice | uint256 | Normalized USDC/USD price (18 decimals); $1.00 on any failure. |
_readUsdcUsdPriceForEvent
Reads USDC/USD for update events without making EUR/USD reads depend on USDC health.
try/catch reads usdcUsdPriceFeed.latestRoundData()/decimals(); returns $1.00 if the round is inconsistent or any call reverts, otherwise delegates to _normalizeUsdcUsdPrice. Decoupled so a failing USDC feed can never block an EUR/USD read.
Notes:
-
security: Isolates USDC-feed failures from the EUR/USD path via try/catch.
-
validation: Rejects inconsistent rounds (roundId != answeredInRound, startedAt > updatedAt).
-
state-changes: None - view function.
-
events: None.
-
errors: None - all failures fall back to 1e18.
-
reentrancy: Not applicable - view function (external staticcall only).
-
access: Internal - no access restrictions.
-
oracle: Reads the USDC/USD Chainlink feed; result used only for events.
function _readUsdcUsdPriceForEvent() internal view returns (uint256 usdcUsdPrice);
Returns
| Name | Type | Description |
|---|---|---|
usdcUsdPrice | uint256 | USDC/USD price (18 decimals) for event enrichment; $1.00 on any failure. |
_commitEurUsdPrice
Commits an accepted EUR/USD price as the new oracle deviation baseline.
Stores the accepted EUR/USD price as the new baseline, records update time/block, and emits PriceUpdated. Called only after a value passes _validateEurUsdPriceData.
Notes:
-
security: Reached only after validation; advances the deviation baseline used by later reads.
-
validation: Assumes the caller validated the price; performs no checks itself.
-
state-changes: Sets lastValidEurUsdPrice, lastPriceUpdateTime, lastPriceUpdateBlock.
-
events: Emits PriceUpdated(eurUsdPrice, usdcUsdPrice, timestamp).
-
errors: None.
-
reentrancy: Not applicable - no external calls.
-
access: Internal - no access restrictions.
-
oracle: Updates the cached oracle baseline; does not call external feeds.
function _commitEurUsdPrice(uint256 eurUsdPrice, uint256 usdcUsdPrice) internal;
Parameters
| Name | Type | Description |
|---|---|---|
eurUsdPrice | uint256 | Accepted EUR/USD price to store as the new baseline (18 decimals). |
usdcUsdPrice | uint256 | USDC/USD price included in the emitted event (18 decimals). |
_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
| Name | Type | Description |
|---|---|---|
rawPrice | int256 | Raw price from Chainlink |
decimals | uint8 | Number of decimals in raw price |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | Scaled 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
| Name | Type | Description |
|---|---|---|
isHealthy | bool | true if everything operates normally |
eurUsdFresh | bool | true if EUR/USD price is fresh |
usdcUsdFresh | bool | true 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
| Name | Type | Description |
|---|---|---|
currentPrice | uint256 | Current price (may be fallback) |
lastValidPrice | uint256 | Last validated price |
lastUpdate | uint256 | Timestamp reported by the underlying EUR/USD feed |
isStale | bool | true if the price is stale |
withinBounds | bool | true 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
| Name | Type | Description |
|---|---|---|
minPrice | uint256 | Minimum EUR/USD price |
maxPrice | uint256 | Maximum EUR/USD price |
maxStaleness | uint256 | Maximum duration before staleness |
usdcTolerance | uint256 | USDC tolerance in basis points |
circuitBreakerActive | bool | Circuit 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
| Name | Type | Description |
|---|---|---|
eurUsdFeedAddress | address | EUR/USD feed address |
usdcUsdFeedAddress | address | USDC/USD feed address |
eurUsdDecimals | uint8 | Number of decimals for the EUR/USD feed |
usdcUsdDecimals | uint8 | Number 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
| Name | Type | Description |
|---|---|---|
eurUsdConnected | bool | true if the EUR/USD feed responds |
usdcUsdConnected | bool | true if the USDC/USD feed responds |
eurUsdLatestRound | uint80 | Latest round ID for EUR/USD |
usdcUsdLatestRound | uint80 | Latest round ID for USDC/USD |
_authorizeUpgrade
Authorizes oracle contract upgrades
function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | Address 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
| Name | Type | Description |
|---|---|---|
token | address | Address of the token to recover |
amount | uint256 | Amount 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: Not protected by a reentrancy guard
-
access: Restricted to authorized roles
-
oracle: Not applicable - no oracle dependency
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: Not protected by a 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: Not protected by a reentrancy guard
-
access: Restricted to authorized roles
-
oracle: Not applicable - no oracle dependency
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:
- Check circuit breaker status
- Fetch from Chainlink
- Freshness check (< 1 hour)
- Convert to 18 decimals
- Check min/max bounds
- 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: Updates lastValidEurUsdPrice, lastPriceUpdateTime, and lastPriceUpdateBlock when valid
-
events: Emits PriceUpdated when a valid price advances the baseline
-
errors: No errors thrown - returns fallback price if invalid
-
reentrancy: Not protected - external oracle reads only
-
access: Public - no access restrictions
-
oracle: Requires fresh Chainlink EUR/USD price feed data
function getEurUsdPrice() external returns (uint256 price, bool isValid);
Returns
| Name | Type | Description |
|---|---|---|
price | uint256 | EUR/USD price in 18 decimals (e.g., 1.10e18 = 1.10 USD per EUR) |
isValid | bool | true 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
| Name | Type | Description |
|---|---|---|
price | uint256 | USDC/USD price in 18 decimals (should be close to 1.0e18) |
isValid | bool | true 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
| Name | Type | Description |
|---|---|---|
_minPrice | uint256 | Minimum accepted EUR/USD price (18 decimals) |
_maxPrice | uint256 | Maximum 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
| Name | Type | Description |
|---|---|---|
newToleranceBps | uint256 | New 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
| Name | Type | Description |
|---|---|---|
_eurUsdFeed | address | New EUR/USD feed address |
_usdcUsdFeed | address | New USDC/USD feed address |
proposeDevMode
Toggles dev mode to disable spread deviation checks
MED-1: Propose a dev-mode change; enforces a 48-hour timelock before it can be applied.
DEV ONLY: When enabled, price deviation checks are skipped for testing
Stores the desired devModeEnabled value in pendingDevMode and sets
devModePendingAt to block.timestamp + DEV_MODE_DELAY. No immediate effect
on live price checks until applyDevMode is called after the delay.
Notes:
-
security: Only callable by DEFAULT_ADMIN_ROLE
-
validation: No input validation required
-
state-changes: Updates devModeEnabled flag
-
events: Emits DevModeToggled event
-
errors: No errors thrown
-
reentrancy: Not protected - simple state change
-
access: Restricted to DEFAULT_ADMIN_ROLE
-
oracle: No oracle dependencies
-
security: Only callable by
DEFAULT_ADMIN_ROLE; separates intent (propose) from effect (apply) to protect against rushed changes. -
validation: Accepts both
trueandfalse; uses a fixed delay for all changes. -
state-changes: Updates
pendingDevModeanddevModePendingAt. -
events: Emits
DevModeProposed(enabled, devModePendingAt). -
errors: None – proposal is always recorded.
-
reentrancy: Not applicable – no external calls after state updates.
-
access: Restricted to
DEFAULT_ADMIN_ROLE. -
oracle: No direct oracle dependency; controls deviation checks in price paths.
function proposeDevMode(bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
enabled | bool | True to enable dev mode, false to disable |
applyDevMode
MED-1: Apply a previously proposed dev-mode change after the timelock has elapsed.
Reads pendingDevMode and sets devModeEnabled once block.timestamp
is greater than or equal to devModePendingAt. Resets devModePendingAt
back to 0 to clear the proposal.
Notes:
-
security: Only callable by
DEFAULT_ADMIN_ROLE; enforces a mandatory delay between proposal and activation of dev mode. -
validation: Reverts if there is no pending proposal or the delay has not yet elapsed.
-
state-changes: Updates
devModeEnabledand clearsdevModePendingAt. -
events: Emits
DevModeToggled(devModeEnabled, msg.sender). -
errors: InvalidAmount if no pending proposal; NotActive if delay window not yet reached.
-
reentrancy: Not applicable – no external calls after state updates.
-
access: Restricted to
DEFAULT_ADMIN_ROLE. -
oracle: No direct oracle dependency; influences later deviation checks.
function applyDevMode() external onlyRole(DEFAULT_ADMIN_ROLE);
setSequencerUptimeFeed
Configure the L2 sequencer uptime feed for Base/Arbitrum deployments
Set feed to address(0) on L1 to skip the sequencer check.
On Base mainnet use: 0xBCF85224fc0756B9Fa45aA7892E69A2E01D7580D
Notes:
-
security: Restricted to ORACLE_MANAGER_ROLE; sequencer checks gate oracle usage on L2s
-
validation: Caller must choose a grace period that is long enough to flush stale data
-
state-changes: Updates
sequencerUptimeFeedandsequencerGracePeriod -
events: Emits
SequencerFeedUpdatedwith the new configuration -
errors: No custom errors; relies on AccessControl for authorization checks
-
reentrancy: Not protected - no external calls beyond assigning storage and emitting an event
-
access: Restricted to ORACLE_MANAGER_ROLE
-
oracle: Configures an auxiliary Chainlink feed used in
getEurUsdPrice
function setSequencerUptimeFeed(address feed, uint256 gracePeriod) external onlyRole(ORACLE_MANAGER_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
feed | address | Chainlink sequencer uptime feed address (address(0) to disable) |
gracePeriod | uint256 | Seconds to wait after sequencer restart before trusting prices again |
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
| Name | Type | Description |
|---|---|---|
newTreasury | address | New treasury address |
ETHRecovered
Emitted when ETH is recovered from the contract
event ETHRecovered(address indexed to, uint256 amount);
DevModeToggled
Emitted when dev mode is toggled
event DevModeToggled(bool enabled, address indexed caller);
Parameters
| Name | Type | Description |
|---|---|---|
enabled | bool | Whether dev mode is enabled or disabled |
caller | address | Address that triggered the toggle |
DevModeProposed
MED-1: Emitted when a dev-mode change is proposed
event DevModeProposed(bool pending, uint256 activatesAt);
Parameters
| Name | Type | Description |
|---|---|---|
pending | bool | The proposed dev-mode value |
activatesAt | uint256 | Timestamp at which the change can be applied |
SequencerFeedUpdated
Emitted when the sequencer uptime feed is updated
event SequencerFeedUpdated(address indexed feed, uint256 gracePeriod);