# The RAI/USD Market Price Oracle ## Overview of Price Feeds in the RAI System The RAI system currently uses two external price feeds: 1) [Chainlink ETH/USD feed](https://data.chain.link/ethereum/mainnet/crypto-usd/eth-usd) 2) [Uniswap V2 RAI/ETH pair](https://v2.info.uniswap.org/pair/0x8ae720a71622e824f576b4a8c03031066548a3b1) The Chainlink ETH/USD feed is used by the system to price ETH collateral. This price determines collateralization ratios and the system relies on it to trigger liquidations. The RAI/ETH price is used in combination with the Chainlink ETH/USD feed to calculate the RAI/USD market price. The market price is then used to determine a deviation (aka "error") from the system's redemption price. In turn, this error is used by the PI/D controller (Money God) to calculate a new redemption rate, thus increasing or decreasing the internal value of RAI. See [here](https://) for a refresher on how the PID controller influences the system. ### System Diagram The top portion of the [full GEB System Diagram](https://viewer.diagrams.net/?target=blank&highlight=0000ff&layers=1&nav=1&title=GEB_overview.drawio#Uhttps%3A%2F%2Fdrive.google.com%2Fuc%3Fid%3D1nIcaY8N8StVCfyAL_ztbmETJX2bvY3a9%26export%3Ddownload) shows the components of the system that we will be discussing. The left cloud represents the Chainlink ETH/USD feed and the right cloud represents the Uniswap V2 RAI/ETH pool. The Chainlink ETH/USD feed is consumed by the `Chainlink Relayer` and the Uniswap V2 Pool prices are consumed by the `Uniswap Medianizer`. ![](https://i.imgur.com/xSXoNvB.png) ### Chainlink Relayer: ETH/USD #### Chainlink ETH/USD Feed The Chainlink ETH/USD feed is currently an [aggregation of 31 off-chain oracles](https://data.chain.link/ethereum/mainnet/crypto-usd/eth-usd). The frequency of aggregated price updates, aka *answers*, are controlled by two trigger parameters. 1) *Deviation threshold*: the percentage the off-chain aggregated price must move before a new on-chain answer is set 2) *Staleness*: the longest time (in seconds) an on-chain answer can exist before an update is forced The current settings for the Chainlink ETH/USD feed are: *Deviation Threshold = **0.5%*** *Staleness: **86,400 seconds*** Therefore if the median price of the oracles moves more than 0.5% ***or*** if it's been at least 86,400 seconds since the last update, Chainlink will push a new price update. To learn more about Chainlink feeds and their architecture, you can read the [Chainlink docs](https://docs.chain.link/docs/architecture-overview/). #### Chainlink Relayer The ETH/USD feed is initially consumed by the `Chainlink Relayer`. The latest ETH/USD answer is set in the `Chainlink Relayer` and then queried by two other system components: 1) The `OSM`, which enforces a one hour delay between ETH price updates 2) The `Uniswap Medianizer`, which calculates the RAI/USD price This post will focus on the `Uniswap Medianizer` since we are discussing the calculation of the RAI/USD market price. ### Uniswap Medianizer: Combining RAI/ETH and ETH/USD #### Uniswap V2 TWAPs One feature of Uniswap V2 pairs is the ability to construct a **time-weighted average price (TWAP)**. Each Uniswap pair contract tracks a cumulative price that represents the "sum of the Uniswap price for every second in the entire history of the contract" From the [Uniswap V2 oracle docs](https://docs.uniswap.org/protocol/V2/concepts/core-concepts/oracles): ``` The TWAP is constructed by reading the cumulative price from an ERC20 token pair at the beginning and at the end of the desired interval. The difference in this cumulative price can then be divided by the length of the interval to create a TWAP for that period. ``` ![](https://i.imgur.com/wcKkQ5V.png) A TWAP for a specific time window can be constructed by calling the pair contract at the beginning and end of a window. The `Uniswap Medianizer` performs this type of sampling. #### Uniswap Medianizer: RAI/ETH TWAP Despite its name, the `Uniswap Medianizer` does not produce a median, but rather a Time-Weighted Average Price(TWAP). It does this by sampling Uniswap V2 `priceCumulative` and storing its recent values. The following are `Uniswap Medianizer` parameters: **Period size**: how often Uniswap V2 `priceCumulative` can be sampled **Window size**: size of the window used for the TWAP **Max window size**: largest possible window size allowed. Hard limit on how old the oldest `priceCumulative` sample can be. This allows extra time if price isn't sampled exactly every `period size` due to gas spikes, off-chain outages, etc. ![](https://i.imgur.com/h81DaAA.png) #### Uniswap Medianizer: ETH/USD TWAP In addition to tracking the RAI/ETH TWAP, the `Uniswap Medianizer` also creates an ETH/USD TWAP, using the price data from the `ChainlinkRelayer`. For the ETH/USD TWAP, the medianizer uses the same parameters (**window size, period size** and **max window size**). However, there is no ETH/USD `priceCumulative` variable that updates every block as in the Uniswap pair. The Medianizer maintains this value from periodic ETH/USD samples during the window, not after every block. #### Uniswap Medianizer: Calculating the Final TWAP Price To calculate the RAI/USD TWAP, the medianizer simply multiplies the RAI/ETH TWAP by the ETH/USD TWAP. Let $\mu$ be the mean price over the previous window, the TWAP. Then: $\mu_{RAI/USD} = \mu_{RAI/ETH} * \mu_{ETH/USD}$ For an example of how the complete TWAP is calculated, see the [Appendix](#Appendix) Let's see how the RAI/USD TWAP performs in the production system. ### Results in Production Below we can see a chart comparing two price series: 1) Prod TWAP(blue): $\mu_{RAI/USD}$values produced by the `Uniswap Medianizer` 2) Spot RAI/USD(red): Calculated by multiplying per block Uniswap V2 RAI/ETH price by per block Chainlink ETH/USD feed ![](https://i.imgur.com/ngSxKty.png) #### Observations Notice there are periods where the TWAP (blue) doesn't seem to represent the spot prices from the previous window. Specifically, the TWAP dips around the 23rd and 25th don't seem to match market behavior. We noticed this mismatch between the TWAP price and recent RAI/USD market prices was happening consistently. There was more volatilty in the RAI/USD TWAP than expected, given the volatility of RAI and ETH. This is an issue as even errors in the TWAP of 1% are significant . With current controller parameters, a 1% deviation between the TWAP price and redemption price creates around a positive or negative 6% annual redemption rate. As the TWAP directly influences the rates of the system, it should have minimal error. ### What is happening? What is causing the production RAI/USD TWAP to not be representative of current market behavior? 1) Estimation of ETH/USD TWAP. While we can contruct the exact RAI/ETH TWAP of a window through `priceCumulative`, we are estimating the TWAP of ETH/USD for this window through sampling. The current production TWAP only uses 4 ETH/USD samples over a window of 16 hours to calculate the ETH/USD TWAP. This will inevitably lead to error from the true ETH/USD TWAP. 2) Chainlink ETH/USD deviation threshold. As the Chainlink ETH/USD feed is not an instantaneous spot price, it can have up to 0.5% error from the true spot price. This error is unavoidable and will inevitably lead to some deviation from the true ETH/USD TWAP. 3) Not considering covariance when combining RAI/ETH and ETH/USD If we go back to the Medianizer calculation, we are calculating the RAI/USD TWAP as: $\mu_{RAI/USD} = \mu_{RAI/ETH} * \mu_{ETH/USD}$ We've attempted to construct a third pair TWAP by multiplying the TWAPs of two composite pairs. However, this is a product of two means and will not necessarily be equal to the mean of two products (what we actually want, the mean of RAI/ETH*ETH/USD). Let's explore this more. #### When Is Our Assumption True? Our assumption: $\mu_{RAI/ETH*ETH/USD} = \mu_{RAI/ETH} * \mu_{ETH/USD}$ We can use the [definition of covariance](https://en.wikipedia.org/wiki/Covariance#Definition) to see under what conditions the above statement is true. Consider the covariance of two random variables, `X` and `Y`. These represent `RAI/ETH` and `ETH/USD` prices in our context. $cov(X, Y) = E[(X-E[X])(Y-E[Y])]$ $\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ = E[XY - XE[Y] - E[X]Y + E[X]E[Y]]$ $\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ = E[XY] - E[X]E[Y] - E[X]E[Y] + E[X]E[Y]$ $\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ = E[XY] - E[X]E[Y]$ On the final line, we see that $cov(X, Y)$ is equal to the mean of the product of two random variables minus the product of the mean of two random variables. Thus, $E[XY] = E[X]E[Y] \iff cov(X, Y) = 0$ *The product of two means equals the means of two products if and only if the covariance of the two variables equals 0.* So the final $\mu_{RAI/ETH*ETH/USD}$ will only be accurate if RAI/ETH and ETH/USD have a covariance of 0! Since we cannot expect the two pairs to have zero covariance, there will be error in the third, combined TWAP. ### Can We Fix This? From the above, we could possibly construct the true RAI/USD TWAP by adding the covariance to the product of the two TWAPs. $E[XY] = E[X]E[Y] + cov(X, Y)$ However, we do not have access to $cov(X, Y)$ over the window. We could possibly estimate the covariance, but the estimate of the window is limited by how often we update the `Uniswap Medianizer`. The medianizer is updated every `window_size/period_size` hours. With current production settings of `window_size=16, period_size=4`, the covariance of a 16-hour window would be estimated from only 4 samples. Further, this would need to be a time-weighted covariance to match the time-weighted prices used. ### Extra Limitations In addition to the shortcomings addressed, a TWAP constructed from Uniswap V2 RAI/ETH doesn't cover all relevant RAI markets (e.g Coinbase, Uniswap V3 RAI/DAI, RAI/USDC, etc). In the next post, we will explore the implications of having Chainlink RAI/USD as the price feed for the RAI market price. ## References [Uniswap Medianizer source code](https://github.com/reflexer-labs/geb-uniswap-median/blob/master/src/UniswapConsecutiveSlotsPriceFeedMedianizer.sol) [Chainlink Relayer source code](https://github.com/reflexer-labs/geb-chainlink-median/blob/master/src/ChainlinkRelayer.sol) [GEB Oracle docs](https://docs.reflexer.finance/system-contracts/oracle-module) ## Appendix #### Example: RAI/ETH TWAP Calculation Consider the following samples of RAI/ETH `priceCumulative` and these TWAP parameters: $window\_size = 600$ $period\_size = 120$. $granularity = 600 / 120 = 5$ ![](https://i.imgur.com/wbH4yDg.png) Let: $pc_{n} = priceCumulative\ at\ block\ n$ $t_{n} = timestamp\ at\ block\ n$ The current TWAP is calculated as such: $RAIETH\_TWAP_{current} = \displaystyle \frac{pc_{502} - pc_{100}} {t_{502} - t_{100}}$ $RAIETH\_TWAP_{current} = \displaystyle\frac{0.452619 -0.070707}{612 - 111} = \frac{0.381912}{501} = 0.0007622994011976$ #### Example: ETH/USD TWAP Calculation Consider the following samples of the ETH/USD price and the same TWAP parameters. ![](https://i.imgur.com/urB7D5N.png) Unlike RAI/ETH, there is no per-second cumulative price for the ETH/USD price. To simulate this, each ETH/USD price is manually weighted by time elapsed, then divided by the total time elapsed since the first sample. Let: $p_n = price\ at\ block\ n$ $t_n = time\ at\ block\ n$ Then: $ETHUSD\_TWAP_{current} = \displaystyle \frac{p_{201}*(t_{201} - t_{100}) + p_{300}*(t_{300} -t_{201})+ p_{404} * (t_{404} -t_{300}) + p_{502} * (t_{502} - t_{404})} {t_{502} - t_{100}}$ $ETHUSD\_TWAP_{current} = \displaystyle \frac{4100 *(236 - 111) + 4075 *(381 -236)+ 4121 * (497 - 381) + 4108 * (612 - 497)} {612 - 111}$ $ETHUSD\_TWAP_{current} = 4099.36$ #### Example: Final RAI/USD Calculation Finally, the two values are multiplied to yield the current RAI/USD TWAP value. $RAIUSD\_TWAP_{current} = RAIETH\_TWAP_{current} * ETHUSD\_TWAP_{current}$ $RAIUSD\_TWAP_{current} = 0.0007622994011976 * 4099.36 \approx 3.12$