// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; import "./ConfirmedOwner.sol"; import "../interfaces/AggregatorProxyInterface.sol"; /** * @title A trusted proxy for updating where current answers are read from * @notice This contract provides a consistent address for the * CurrentAnwerInterface but delegates where it reads from to the owner, who is * trusted to update it. */ contract AggregatorProxy is AggregatorProxyInterface, ConfirmedOwner { struct Phase { uint16 id; AggregatorProxyInterface aggregator; } AggregatorProxyInterface private s_proposedAggregator; mapping(uint16 => AggregatorProxyInterface) private s_phaseAggregators; Phase private s_currentPhase; uint256 constant private PHASE_OFFSET = 64; uint256 constant private PHASE_SIZE = 16; uint256 constant private 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() public 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() public 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) public view virtual override returns (int256 answer) { if (roundId > MAX_ID) return 0; (uint16 phaseId, uint64 aggregatorRoundId) = parseIds(roundId); AggregatorProxyInterface 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) public view virtual override returns (uint256 updatedAt) { if (roundId > MAX_ID) return 0; (uint16 phaseId, uint64 aggregatorRoundId) = parseIds(roundId); AggregatorProxyInterface 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() public 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 override 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 override 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 override returns (address) { return address(s_currentPhase.aggregator); } /** * @notice returns the current phase's ID. */ function phaseId() external view override 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 override returns (string memory) { return s_currentPhase.aggregator.description(); } /** * @notice returns the current proposed aggregator */ function proposedAggregator() external view override returns (address) { return address(s_proposedAggregator); } /** * @notice return a phase aggregator using the phaseId * * @param phaseId uint16 */ function phaseAggregators(uint16 phaseId) external view override 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 = AggregatorProxyInterface(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, AggregatorProxyInterface(aggregatorAddress)); s_phaseAggregators[id] = AggregatorProxyInterface(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"); _; } }