// SPDX-License-Identifier: MIT pragma solidity 0.8.26; import {ConfirmedOwner} from "../../../shared/access/ConfirmedOwner.sol"; import {AggregatorV2V3Interface} from "../../../shared/interfaces/AggregatorV2V3Interface.sol"; /// @title A trusted proxy for updating where current answers are read from /// @notice This contract provides a consistent address for the /// CurrentAnswerInterface but delegates where it reads from to the owner, who is /// trusted to update it. contract DataFeedsLegacyAggregatorProxy is AggregatorV2V3Interface, ConfirmedOwner { struct Phase { uint16 id; AggregatorV2V3Interface aggregator; } AggregatorV2V3Interface private s_proposedAggregator; mapping(uint16 => AggregatorV2V3Interface) private s_phaseAggregators; Phase private s_currentPhase; uint256 private constant PHASE_OFFSET = 64; uint256 private constant PHASE_SIZE = 16; uint256 private constant MAX_ID = 2 ** (PHASE_OFFSET + PHASE_SIZE) - 1; event AggregatorProposed(address indexed current, address indexed proposed); event AggregatorConfirmed(address indexed previous, address indexed latest); constructor( address aggregatorAddress ) ConfirmedOwner(msg.sender) { setAggregator(aggregatorAddress); } /// @notice Reads the current answer from aggregator delegated to. /// @dev #[deprecated] Use latestRoundData instead. This does not error if no /// answer has been reached, it will simply return 0. Either wait to point to /// an already answered Aggregator or use the recommended latestRoundData /// instead which includes better verification information. function latestAnswer() external view virtual override returns (int256 answer) { return s_currentPhase.aggregator.latestAnswer(); } /// @notice Reads the last updated height from aggregator delegated to. /// @dev #[deprecated] Use latestRoundData instead. This does not error if no /// answer has been reached, it will simply return 0. Either wait to point to /// an already answered Aggregator or use the recommended latestRoundData /// instead which includes better verification information. function latestTimestamp() external view virtual override returns (uint256 updatedAt) { return s_currentPhase.aggregator.latestTimestamp(); } /// @notice get past rounds answers /// @param roundId the answer number to retrieve the answer for /// @dev #[deprecated] Use getRoundData instead. This does not error if no /// answer has been reached, it will simply return 0. Either wait to point to /// an already answered Aggregator or use the recommended getRoundData /// instead which includes better verification information. function getAnswer( uint256 roundId ) external view virtual override returns (int256 answer) { if (roundId > MAX_ID) return 0; (uint16 phaseId, uint64 aggregatorRoundId) = parseIds(roundId); AggregatorV2V3Interface aggregator = s_phaseAggregators[phaseId]; if (address(aggregator) == address(0)) return 0; return aggregator.getAnswer(aggregatorRoundId); } /// @notice get block timestamp when an answer was last updated /// @param roundId the answer number to retrieve the updated timestamp for /// @dev #[deprecated] Use getRoundData instead. This does not error if no /// answer has been reached, it will simply return 0. Either wait to point to /// an already answered Aggregator or use the recommended getRoundData /// instead which includes better verification information. function getTimestamp( uint256 roundId ) external view virtual override returns (uint256 updatedAt) { if (roundId > MAX_ID) return 0; (uint16 phaseId, uint64 aggregatorRoundId) = parseIds(roundId); AggregatorV2V3Interface aggregator = s_phaseAggregators[phaseId]; if (address(aggregator) == address(0)) return 0; return aggregator.getTimestamp(aggregatorRoundId); } /// @notice get the latest completed round where the answer was updated. This /// ID includes the proxy's phase, to make sure round IDs increase even when /// switching to a newly deployed aggregator. /// @dev #[deprecated] Use latestRoundData instead. This does not error if no /// answer has been reached, it will simply return 0. Either wait to point to /// an already answered Aggregator or use the recommended latestRoundData /// instead which includes better verification information. function latestRound() external view virtual override returns (uint256 roundId) { Phase memory phase = s_currentPhase; // cache storage reads return addPhase(phase.id, uint64(phase.aggregator.latestRound())); } /// @notice get data about a round. Consumers are encouraged to check /// that they're receiving fresh data by inspecting the updatedAt and /// answeredInRound return values. /// Note that different underlying implementations of AggregatorV3Interface /// have slightly different semantics for some of the return values. Consumers /// should determine what implementations they expect to receive /// data from and validate that they can properly handle return data from all /// of them. /// @param roundId the requested round ID as presented through the proxy, this /// is made up of the aggregator's round ID with the phase ID encoded in the /// two highest order bytes /// @return id is the round ID from the aggregator for which the data was /// retrieved combined with an phase to ensure that round IDs get larger as /// time moves forward. /// @return answer is the answer for the given round /// @return startedAt is the timestamp when the round was started. /// (Only some AggregatorV3Interface implementations return meaningful values) /// @return updatedAt is the timestamp when the round last was updated (i.e. /// answer was last computed) /// @return answeredInRound is the round ID of the round in which the answer /// was computed. /// (Only some AggregatorV3Interface implementations return meaningful values) /// @dev Note that answer and updatedAt may change between queries. function getRoundData( uint80 roundId ) public view virtual override returns (uint80 id, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { (uint16 phaseId, uint64 aggregatorRoundId) = parseIds(roundId); (id, answer, startedAt, updatedAt, answeredInRound) = s_phaseAggregators[phaseId].getRoundData(aggregatorRoundId); return addPhaseIds(id, answer, startedAt, updatedAt, answeredInRound, phaseId); } /// @notice get data about the latest round. Consumers are encouraged to check /// that they're receiving fresh data by inspecting the updatedAt and /// answeredInRound return values. /// Note that different underlying implementations of AggregatorV3Interface /// have slightly different semantics for some of the return values. Consumers /// should determine what implementations they expect to receive /// data from and validate that they can properly handle return data from all /// of them. /// @return id is the round ID from the aggregator for which the data was /// retrieved combined with an phase to ensure that round IDs get larger as /// time moves forward. /// @return answer is the answer for the given round /// @return startedAt is the timestamp when the round was started. /// (Only some AggregatorV3Interface implementations return meaningful values) /// @return updatedAt is the timestamp when the round last was updated (i.e. /// answer was last computed) /// @return answeredInRound is the round ID of the round in which the answer /// was computed. /// (Only some AggregatorV3Interface implementations return meaningful values) /// @dev Note that answer and updatedAt may change between queries. function latestRoundData() public view virtual override returns (uint80 id, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { Phase memory current = s_currentPhase; // cache storage reads (id, answer, startedAt, updatedAt, answeredInRound) = current.aggregator.latestRoundData(); return addPhaseIds(id, answer, startedAt, updatedAt, answeredInRound, current.id); } /// @notice Used if an aggregator contract has been proposed. /// @param roundId the round ID to retrieve the round data for /// @return id is the round ID for which data was retrieved /// @return answer is the answer for the given round /// @return startedAt is the timestamp when the round was started. /// (Only some AggregatorV3Interface implementations return meaningful values) /// @return updatedAt is the timestamp when the round last was updated (i.e. /// answer was last computed) /// @return answeredInRound is the round ID of the round in which the answer /// was computed. function proposedGetRoundData( uint80 roundId ) external view virtual hasProposal returns (uint80 id, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { return s_proposedAggregator.getRoundData(roundId); } /// @notice Used if an aggregator contract has been proposed. /// @return id is the round ID for which data was retrieved /// @return answer is the answer for the given round /// @return startedAt is the timestamp when the round was started. /// (Only some AggregatorV3Interface implementations return meaningful values) /// @return updatedAt is the timestamp when the round last was updated (i.e. /// answer was last computed) /// @return answeredInRound is the round ID of the round in which the answer /// was computed. function proposedLatestRoundData() external view virtual hasProposal returns (uint80 id, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { return s_proposedAggregator.latestRoundData(); } /// @notice returns the current phase's aggregator address. function aggregator() external view returns (address) { return address(s_currentPhase.aggregator); } /// @notice returns the current phase's ID. function phaseId() external view returns (uint16) { return s_currentPhase.id; } /// @notice represents the number of decimals the aggregator responses represent. function decimals() external view override returns (uint8) { return s_currentPhase.aggregator.decimals(); } /// @notice the version number representing the type of aggregator the proxy /// points to. function version() external view override returns (uint256) { return s_currentPhase.aggregator.version(); } /// @notice returns the description of the aggregator the proxy points to. function description() external view returns (string memory) { return s_currentPhase.aggregator.description(); } /// @notice returns the current proposed aggregator function proposedAggregator() external view returns (address) { return address(s_proposedAggregator); } /// @notice return a phase aggregator using the phaseId /// @param phaseId uint16 function phaseAggregators( uint16 phaseId ) external view returns (address) { return address(s_phaseAggregators[phaseId]); } /// @notice Allows the owner to propose a new address for the aggregator /// @param aggregatorAddress The new address for the aggregator contract function proposeAggregator( address aggregatorAddress ) external onlyOwner { s_proposedAggregator = AggregatorV2V3Interface(aggregatorAddress); emit AggregatorProposed(address(s_currentPhase.aggregator), aggregatorAddress); } /// @notice Allows the owner to confirm and change the address /// to the proposed aggregator /// @dev Reverts if the given address doesn't match what was previously /// proposed /// @param aggregatorAddress The new address for the aggregator contract function confirmAggregator( address aggregatorAddress ) external onlyOwner { require(aggregatorAddress == address(s_proposedAggregator), "Invalid proposed aggregator"); address previousAggregator = address(s_currentPhase.aggregator); delete s_proposedAggregator; setAggregator(aggregatorAddress); emit AggregatorConfirmed(previousAggregator, aggregatorAddress); } /// Internal function setAggregator( address aggregatorAddress ) internal { uint16 id = s_currentPhase.id + 1; s_currentPhase = Phase(id, AggregatorV2V3Interface(aggregatorAddress)); s_phaseAggregators[id] = AggregatorV2V3Interface(aggregatorAddress); } function addPhase(uint16 phase, uint64 originalId) internal pure returns (uint80) { return uint80((uint256(phase) << PHASE_OFFSET) | originalId); } function parseIds( uint256 roundId ) internal pure returns (uint16, uint64) { uint16 phaseId = uint16(roundId >> PHASE_OFFSET); uint64 aggregatorRoundId = uint64(roundId); return (phaseId, aggregatorRoundId); } function addPhaseIds( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound, uint16 phaseId ) internal pure returns (uint80, int256, uint256, uint256, uint80) { return (addPhase(phaseId, uint64(roundId)), answer, startedAt, updatedAt, addPhase(phaseId, uint64(answeredInRound))); } /// Modifiers modifier hasProposal() { require(address(s_proposedAggregator) != address(0), "No proposed aggregator present"); _; } }