Skip to content

Add backup oracles to PSMs#535

Closed
thomas-waite wants to merge 49 commits intodevelopfrom
feat/backup-oracles
Closed

Add backup oracles to PSMs#535
thomas-waite wants to merge 49 commits intodevelopfrom
feat/backup-oracles

Conversation

@thomas-waite
Copy link
Contributor

@thomas-waite thomas-waite commented Feb 10, 2022

Summary

Adds UniswapV3 TWAP backup oracles to the PegStabilityModules (PSMs) for DAI and ETH. These both have highly liquid USDC pools on Uniswap V3:

  • DAI-USDC: ~$300m TVL
  • USDC-ETH: ~$285m TVL

Description

A UniswapV3OracleWrapper has been created which implements IOracle.sol. Each asset requires its own UniswapV3OracleWrapper contract to be deployed.

Calculating oracle price and siloing out Uniswap logic

The Maths of calculating the oracle price is siloed out into UniswapWrapper.sol. This is mostly done because the UniswapV3 contracts are written in a version, >=0.5.0 <0.8.0, which is incompatible with the Fei codebase. They rely on underflows and overflows happening in some of their accounting logic, which in >0.8.0 causes reverts.

Oracle config and safety

The oracles settings are controlled by an OracleConfig that is injected in. It has the following form:

struct OracleConfig {
    uint32 twapPeriod;
    uint128 minPoolLiquidity;
    address uniswapPool;
    uint256 precision;
  }

The minPoolLiquidity config is a safety parameter that is used to enforce a minimum level of liquidity in the relevant pool in order to provide a valid reading.

Testing

Integration tests which fork mainnet have been added to the contracts/test/integration folder. Five oracles have been deployed against the corresponding pools:

  • daiUsdcPool
  • usdcEthPool
  • wbtcUsdcPool
  • usdtAvinocPool
  • usdcFeiPool

The prices returned by the relevant TWAP oracles have been validated manually against the relevant pool spot price. These 5 in particular were chosen because they have parameters (token0, token1, inputNumDecimals, outputNumDecimals) which allow all 4 paths of the Uniswap maths calculation logic to be tested. Details are outlined in the integration tests.

Concern

I think there is a broken abstraction in OracleRef.sol where the decimal normalisation and inversion occurs globally and applies to both the primary and backup oracle price results.

As in this PR, these considerations should be pushed down into the relevant oracle contract. These factors likely need to be taken into account in different ways for different oracles depending on how they calculate the price. For example in calculating the Uni TWAP price, we need to know whether we are inverting when we are calculating the numerator and denominator.

If this UniV3 backupOracle was to be set, and setDecimalsNormalizer() or setDoInvert() were later seperately called on OracleRef.sol - it would result in the backup oracle OracleRef.sol reporting the incorrect price due to this global post processing.

Before deploying this PR, I believe we should:

  1. Remove setDecimalsNormalizer() or setDoInvert() from OracleRef.sol and push these considerations down into the primary Chainlink oracle contract

@thomas-waite thomas-waite self-assigned this Feb 10, 2022
@thomas-waite thomas-waite marked this pull request as draft February 10, 2022 23:48
@thomas-waite thomas-waite force-pushed the feat/backup-oracles branch 4 times, most recently from 038266c to b2a2ff6 Compare February 11, 2022 05:51
@thomas-waite thomas-waite force-pushed the feat/backup-oracles branch 4 times, most recently from 2da7e02 to e0d7604 Compare February 14, 2022 18:46
@thomas-waite thomas-waite changed the title [WIP] Add backup oracles to PSMs Add backup oracles to PSMs Feb 14, 2022
@thomas-waite thomas-waite marked this pull request as ready for review February 14, 2022 22:08
@thomas-waite thomas-waite force-pushed the feat/backup-oracles branch 6 times, most recently from b4b4a6d to 1fc085b Compare February 15, 2022 22:59
eswak
eswak previously approved these changes Feb 28, 2022
validatePoolLiquidity(pool, oracleConfig.minPoolLiquidity);

uint8 inputTokenDecimals = ERC20(inputToken).decimals();
uint8 outputTokenDecimals = ERC20(outputToken).decimals();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gas optimization: Set both of these decimal values as immutable global variables in the constructor so that they don't have to do these redundant 2 calls + SLOADs on the read.

override
hasAnyOfThreeRoles(
TribeRoles.GOVERNOR,
TribeRoles.GUARDIAN,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a power we want to give to a guardian?

uint256 denominator;
if (!invertPrice) {
// price = ((sqrtPrice*2^96) * (sqrtPrice*2^96) * decimalNormaliser) / (2^96)^2
numerator = !invertDecNormaliser
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If statement would be more legible here.

//price = ((2^96)^2 * decimalNormaliser) / (sqrtPrice*2^96) * (sqrtPrice*2^96)
numerator = !invertDecNormaliser
? FullMath.mulDiv(2**192, decimalNormaliser, uint256(1))
: FullMath.mulDiv(2**192, uint256(1), decimalNormaliser);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If statement would be more legible here.

assertTrue(valid);

assertGt(price.value, 2000e18);
assertLt(price.value, 5000e18);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is pretty fragile, I would recommend either widening the range to 1k-10k, or just testing that spot price isn't more than 10 percent away from the TWAP price.


assertTrue(valid);
assertGt(price.value, 2e18 + 5e17);
assertLt(price.value, 3e18 + 5e17);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about test being fragile.

assertTrue(valid);

assertGt(price.value, 40000e18);
assertLt(price.value, 50000e18);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is fragile and needs to be reworked.

@thomas-waite thomas-waite requested a review from a team as a code owner March 29, 2022 16:14
@xklob xklob marked this pull request as draft April 28, 2022 17:07
@Joeysantoro Joeysantoro added the iceberg not planned for short/medium term development label Jun 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

iceberg not planned for short/medium term development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

Comments