SIP-86: ExchangeRates Chainlink Aggregator V2V3
| Author | |
|---|---|
| Status | Implemented |
| Type | Governance |
| Network | Ethereum |
| Implementor | TBD |
| Release | TBD |
| Created | 2020-09-02 |
Simple Summary
Updating the ExchangeRates contract to use the ChainLink Aggregator V2V3 Interface.
Abstract
The current ExchangeRates contract uses a deprecated version of Chainlink's aggregator interface to get its prices. This SIP proposes to update ExchangeRates to be using a newer version, the AggregatorV2V3Interface, which is a hybrid interface including functions from both V2 and V3 interfaces.
Motivation
SIP-79 requires ExchangeRates to add a new Chainlink aggregator in order to track fastGasPrice. This is the first time this contract is using an aggregator to fetch a value which is not related to an asset price. Although it is not mandatory for ExchangeRates to track asset rates only, the current implementation may introduce some issues related to how many decimals a value returned by an aggregator contains.
All the current aggregators used by the system return 8 decimals, which are then converted to 18 decimals using a multiplier.
However, fasGasPrice aggregator returns a value which is already in WEI (0 decimals), meaning ExchangeRates will have to check if whether or not, the value is already in the expected format.
Another issue with using the current Aggregator Interface is the amount of gas needed to update a price. To do so, the ExchangeRates contract needs to make two calls to an aggregator in order to get the value and its last update timestamp.
This SIP proposes to update ExchangeRates to use AggregatorV2V3Interface, which contains a few enhancements that will solve both of the issues explained above.
Specification
Overview
The interface proposed by Chainlink provides a decimals() function which returns the number of decimals the aggregator response contains. This will be used to determine if a value needs to be formatted with a multiplier or not in the ExchangeRates contract.
It also returns all the required data for a price update (value and timestamp) in one single function call, meaning this process will consume a smaller amount of gas.
Rationale
Adding new kind of aggregators into ExchangeRates such as fastGasPrice introduces the necessity of handling multiple data formats. If BTC/USD rate contains 8 decimals and fastGasPrice none, then we only need to convert the former.
Switching to this new interface will allow ExchangeRates to know if an aggregator answer needs to be formatted without the need to hardcode the currencyKey directly into the contract. Then, if let's say the lowGasPrice aggregator needs to be added in the future, no change and redeployment will be required.
The fact that this interface includes V2 and V3 will allow ExchangeRates to continue using some functions from the current aggregator which were removed from V3, such as latestRound(). It will also allow the contract to use the following new functions:
decimals()which indicates what kind of format is expected from an aggregatorgetRoundData(uint80 _roundId)to get the aggregator data at a specific round IDlatestRoundData()to get the latest aggregator data
In the ExchangeRates, two functions are used to fetch data from an aggregator: _getRateAndUpdatedTime() and _getRateAndTimestampAtRound().
They both require two calls to get a price and a timestamp so it can be stored in the contract.
Current V2 interface doesn't allow these two values to be fetched at the same time, into a single call:
function getAnswer(uint256 roundId) external view returns (int256);
function getTimestamp(uint256 roundId) external view returns (uint256);
However, the new aggregator simplifies this process by returning the data from a single function, which should decrease the gas cost significantly:
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
Technical Specification
This SIP requires the ExchangeRates contract to update some of its logic to be compatible with the new interface. It also includes finding a solution to store the number of decimals an aggregator will return to avoid any unnecessary calls while fetching a new price update.
Here is a list of most of the required changes and new implementations:
- New mapping
mapping(bytes32 -> unit8) public currencyKeyDecimalsto store the currency keys and their number of decimals addAggregator()callingaggregator.decimals()and storing the returned value intocurrencyKeyDecimals.removeAggregator()removingcurrencyKeyfrom thecurrencyKeyDecimalsmapping.- New function
_formatAggregatorAnswer(bytes32 currencyKey, int256 rate)readingcurrencyKeyDecimalsand applying the multiplier on a rate if needed. _getRateAndUpdatedTime()and_getRateAndTimestampAtRound()callinglatestRoundData()andgetRoundData()to fetch the new rates from an aggregator, and using_formatAggregatorAnswer()to format them.
Test Cases
Given one aggregator, tracking BTC/USD price:
- When owner calls
addAggregator()currencyKeyDecimalsmapping now containsBTC => 8
- When user calls
rateAndTimestampAtRound()- should return
rateandtimein correct formats
- should return
- When user calls
rateAndUpdatedTime()- should return
rateandtimein correct formats
- should return
Given one aggregator, tracking fasGasPrice price:
- When owner calls
addAggregator()currencyKeyDecimalsmapping now containsfasGasPrice => 0
- When user calls
rateAndTimestampAtRound()- should return
rateandtimein correct formats
- should return
- When user calls
rateAndUpdatedTime()- should return
rateandtimein correct formats
- should return
Configurable Values (Via SCCP)
N/A