SIP-86: ExchangeRates Chainlink Aggregator V2V3
Author | |
---|---|
Status | Implemented |
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 currencyKeyDecimals
to store the currency keys and their number of decimals addAggregator()
callingaggregator.decimals()
and storing the returned value intocurrencyKeyDecimals
.removeAggregator()
removingcurrencyKey
from thecurrencyKeyDecimals
mapping.- New function
_formatAggregatorAnswer(bytes32 currencyKey, int256 rate)
readingcurrencyKeyDecimals
and 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()
currencyKeyDecimals
mapping now containsBTC => 8
- When user calls
rateAndTimestampAtRound()
- should return
rate
andtime
in correct formats
- should return
- When user calls
rateAndUpdatedTime()
- should return
rate
andtime
in correct formats
- should return
Given one aggregator, tracking fasGasPrice
price:
- When owner calls
addAggregator()
currencyKeyDecimals
mapping now containsfasGasPrice => 0
- When user calls
rateAndTimestampAtRound()
- should return
rate
andtime
in correct formats
- should return
- When user calls
rateAndUpdatedTime()
- should return
rate
andtime
in correct formats
- should return
Configurable Values (Via SCCP)
N/A
Copyright
Copyright and related rights waived via CC0.