Architecture
PriceFeedConsumer is a thin, auditable wrapper around AggregatorV3Interface.
The design prioritizes correctness over flexibility: the feed address is fixed
at deployment, validation is synchronous and view-only, and all mutable
parameters are owner-controlled with no time delay.
Contract at a glance
contract PriceFeedConsumer is Ownable { AggregatorV3Interface public immutable priceFeed; address public sequencerUptimeFeed;
uint256 public stalenessThreshold; uint256 public maxDeviationBps; int256 public lastAcceptedPrice;}Why priceFeed is immutable
The feed address is the most critical dependency in the contract. Making it
immutable eliminates an entire class of attack: there is no setFeed
function for an attacker or compromised owner to redirect price reads to a
malicious aggregator.
If you need to upgrade the feed (for example when Chainlink deprecates a
proxy), deploy a new PriceFeedConsumer and migrate. The cost of one
additional deployment is worth the reduction in attack surface.
sequencerUptimeFeed is deliberately mutable. On L1 deployments it stays
at address(0), and the check is skipped. On L2 deployments it is set
post-construction via setSequencerUptimeFeed. This makes the same contract
deployable across both environments without branching the code.
Constructor initialization
constructor(address _feed, uint256 _threshold, uint256 _deviation) Ownable() { priceFeed = AggregatorV3Interface(_feed); stalenessThreshold = _threshold; maxDeviationBps = _deviation;
(, lastAcceptedPrice, , , ) = priceFeed.latestRoundData(); if (lastAcceptedPrice <= 0) revert InvalidPrice();}The constructor reads an initial price from the feed and sets it as the
deviation baseline. There’s no separate acceptPrice call needed after
deployment. The contract is ready to use the moment construction succeeds.
The validity check on lastAcceptedPrice does more than validate the price.
If the feed address is wrong, the aggregator is uninitialized, or the feed
has been deprecated, the constructor reverts. The entire deployment transaction
fails cleanly rather than leaving a misconfigured contract sitting on-chain
waiting to cause problems.
Parameter ownership
stalenessThreshold and maxDeviationBps share a single setter:
function setParams(uint256 _threshold, uint256 _deviation) external onlyOwner { stalenessThreshold = _threshold; maxDeviationBps = _deviation;}One call instead of two. The gas saving is minor, but the design reason is more practical: these parameters are always tuned together in production. Tightening the staleness window usually means tightening the deviation threshold at the same time. A single setter makes it impossible to update one and forget the other.
Constants
uint256 public constant BPS_BASE = 10_000;uint256 public constant GRACE_PERIOD_TIME = 3600;BPS_BASE is public so consuming contracts can reference it directly rather
than hardcoding 10_000 in their own logic. GRACE_PERIOD_TIME is fixed.
It is not a parameter because a shorter grace period would defeat the purpose
of having one. Chainlink’s own guidance is one hour, and that is what
this contract enforces.