From 757acff1f1578e9907ff3b2cc5cffbba7c0e8ffc Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Thu, 23 Nov 2023 22:50:17 +1100 Subject: [PATCH 1/5] Removed drift protection from Oracle Router --- contracts/contracts/oracle/OracleRouter.sol | 29 +++++---------------- contracts/test/oracle/oracle.js | 8 +++--- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/contracts/contracts/oracle/OracleRouter.sol b/contracts/contracts/oracle/OracleRouter.sol index 35c4745924..c73a76da23 100644 --- a/contracts/contracts/oracle/OracleRouter.sol +++ b/contracts/contracts/oracle/OracleRouter.sol @@ -12,8 +12,6 @@ abstract contract OracleRouterBase is IOracle { using StableMath for uint256; using SafeCast for int256; - uint256 internal constant MIN_DRIFT = 0.7e18; - uint256 internal constant MAX_DRIFT = 1.3e18; address internal constant FIXED_PRICE = 0x0000000000000000000000000000000000000001; // Maximum allowed staleness buffer above normal Oracle maximum staleness @@ -36,14 +34,14 @@ abstract contract OracleRouterBase is IOracle { /** * @notice Returns the total price in 18 digit unit for a given asset. * @param asset address of the asset - * @return uint256 unit price for 1 asset unit, in 18 decimal fixed + * @return _price unit price for 1 asset unit, in 18 decimal fixed */ function price(address asset) external view virtual override - returns (uint256) + returns (uint256 _price) { (address _feed, uint256 maxStaleness) = feedMetadata(asset); require(_feed != address(0), "Asset not available"); @@ -59,12 +57,7 @@ abstract contract OracleRouterBase is IOracle { uint8 decimals = getDecimals(_feed); - uint256 _price = _iprice.toUint256().scaleBy(18, decimals); - if (shouldBePegged(asset)) { - require(_price <= MAX_DRIFT, "Oracle: Price exceeds max"); - require(_price >= MIN_DRIFT, "Oracle: Price under min"); - } - return _price; + _price = _iprice.toUint256().scaleBy(18, decimals); } function getDecimals(address _feed) internal view virtual returns (uint8) { @@ -88,15 +81,6 @@ abstract contract OracleRouterBase is IOracle { decimalsCache[_feed] = decimals; return decimals; } - - function shouldBePegged(address _asset) internal view returns (bool) { - string memory symbol = Helpers.getSymbol(_asset); - bytes32 symbolHash = keccak256(abi.encodePacked(symbol)); - return - symbolHash == keccak256(abi.encodePacked("DAI")) || - symbolHash == keccak256(abi.encodePacked("USDC")) || - symbolHash == keccak256(abi.encodePacked("USDT")); - } } // @notice Oracle Router that denominates all prices in USD @@ -167,14 +151,14 @@ contract OETHOracleRouter is OracleRouter { * This implementation does not (!) do range checks as the * parent OracleRouter does. * @param asset address of the asset - * @return uint256 unit price for 1 asset unit, in 18 decimal fixed + * @return _price unit price for 1 asset unit, in 18 decimal fixed */ function price(address asset) external view virtual override - returns (uint256) + returns (uint256 _price) { (address _feed, uint256 maxStaleness) = feedMetadata(asset); if (_feed == FIXED_PRICE) { @@ -191,8 +175,7 @@ contract OETHOracleRouter is OracleRouter { ); uint8 decimals = getDecimals(_feed); - uint256 _price = uint256(_iprice).scaleBy(18, decimals); - return _price; + _price = uint256(_iprice).scaleBy(18, decimals); } /** diff --git a/contracts/test/oracle/oracle.js b/contracts/test/oracle/oracle.js index cc0969aa4a..36c591c859 100644 --- a/contracts/test/oracle/oracle.js +++ b/contracts/test/oracle/oracle.js @@ -60,15 +60,15 @@ describe("Vault Oracle", async () => { describe("Min/Max Drift", async () => { const tests = [ - ["0.10", "Oracle: Price under min"], - ["0.699", "Oracle: Price under min"], + ["0.10", "Vault: Price under min"], + ["0.699", "Vault: Price under min"], ["0.70"], ["0.98"], ["1.00"], ["1.04"], ["1.30"], - ["1.31", "Oracle: Price exceeds max"], - ["6.00", "Oracle: Price exceeds max"], + ["1.31", "Vault: Price exceeds max"], + ["6.00", "Vault: Price exceeds max"], ]; for (const test of tests) { From 8474598ec234c14d0d4c185fe9c56ee679415ef5 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Fri, 24 Nov 2023 01:40:30 +1100 Subject: [PATCH 2/5] Hardcode Oracle Router decimals for gas savings --- .../contracts/mocks/MockOracleRouter.sol | 27 +++---- .../mocks/MockOracleRouterNoStale.sol | 16 +++- contracts/contracts/oracle/OracleRouter.sol | 78 ++++++++++++------- contracts/deploy/001_core.js | 33 ++++---- contracts/deploy/081_oeth_oracle.js | 29 +++++++ contracts/test/_fixture.js | 4 +- contracts/test/helpers.js | 15 ++-- contracts/test/oracle/oracle.fork-test.js | 1 + contracts/test/oracle/oracle.js | 7 ++ contracts/test/timelock.js | 4 +- contracts/test/vault/compound.js | 4 +- contracts/test/vault/index.js | 12 +-- contracts/test/vault/redeem.js | 4 +- 13 files changed, 150 insertions(+), 84 deletions(-) diff --git a/contracts/contracts/mocks/MockOracleRouter.sol b/contracts/contracts/mocks/MockOracleRouter.sol index 1aecd3df8c..02dda3a988 100644 --- a/contracts/contracts/mocks/MockOracleRouter.sol +++ b/contracts/contracts/mocks/MockOracleRouter.sol @@ -13,6 +13,7 @@ contract MockOracleRouter is OracleRouterBase { struct FeedMetadata { address feedAddress; uint256 maxStaleness; + uint256 decimals; } mapping(address => FeedMetadata) public assetToFeedMetadata; @@ -25,19 +26,14 @@ contract MockOracleRouter is OracleRouterBase { function setFeed( address _asset, address _feed, - uint256 _maxStaleness + uint256 _maxStaleness, + uint256 _decimals ) external { - assetToFeedMetadata[_asset] = FeedMetadata(_feed, _maxStaleness); - } - - /* - * The dev version of the Oracle doesn't need to gas optimize and cache the decimals - */ - function getDecimals(address _feed) internal view override returns (uint8) { - require(_feed != address(0), "Asset not available"); - require(_feed != FIXED_PRICE, "Fixed price feeds not supported"); - - return AggregatorV3Interface(_feed).decimals(); + assetToFeedMetadata[_asset] = FeedMetadata( + _feed, + _maxStaleness, + _decimals + ); } /** @@ -51,10 +47,15 @@ contract MockOracleRouter is OracleRouterBase { internal view override - returns (address feedAddress, uint256 maxStaleness) + returns ( + address feedAddress, + uint256 maxStaleness, + uint256 decimals + ) { FeedMetadata storage fm = assetToFeedMetadata[asset]; feedAddress = fm.feedAddress; maxStaleness = fm.maxStaleness; + decimals = fm.decimals; } } diff --git a/contracts/contracts/mocks/MockOracleRouterNoStale.sol b/contracts/contracts/mocks/MockOracleRouterNoStale.sol index 005e8348a0..7570c13296 100644 --- a/contracts/contracts/mocks/MockOracleRouterNoStale.sol +++ b/contracts/contracts/mocks/MockOracleRouterNoStale.sol @@ -15,9 +15,13 @@ contract MockOracleRouterNoStale is OracleRouter { pure virtual override - returns (address feedAddress, uint256 maxStaleness) + returns ( + address feedAddress, + uint256 maxStaleness, + uint256 decimals + ) { - (feedAddress, ) = super.feedMetadata(asset); + (feedAddress, , decimals) = super.feedMetadata(asset); maxStaleness = 365 days; } } @@ -29,9 +33,13 @@ contract MockOETHOracleRouterNoStale is OETHOracleRouter { pure virtual override - returns (address feedAddress, uint256 maxStaleness) + returns ( + address feedAddress, + uint256 maxStaleness, + uint256 decimals + ) { - (feedAddress, ) = super.feedMetadata(asset); + (feedAddress, , decimals) = super.feedMetadata(asset); maxStaleness = 365 days; } } diff --git a/contracts/contracts/oracle/OracleRouter.sol b/contracts/contracts/oracle/OracleRouter.sol index c73a76da23..a05416d752 100644 --- a/contracts/contracts/oracle/OracleRouter.sol +++ b/contracts/contracts/oracle/OracleRouter.sol @@ -16,7 +16,7 @@ abstract contract OracleRouterBase is IOracle { 0x0000000000000000000000000000000000000001; // Maximum allowed staleness buffer above normal Oracle maximum staleness uint256 internal constant STALENESS_BUFFER = 1 days; - mapping(address => uint8) internal decimalsCache; + mapping(address => uint8) internal _deprecated_decimalsCache; /** * @dev The price feed contract to use for a particular asset along with @@ -29,7 +29,11 @@ abstract contract OracleRouterBase is IOracle { internal view virtual - returns (address feedAddress, uint256 maxStaleness); + returns ( + address feedAddress, + uint256 maxStaleness, + uint256 decimals + ); /** * @notice Returns the total price in 18 digit unit for a given asset. @@ -43,7 +47,9 @@ abstract contract OracleRouterBase is IOracle { override returns (uint256 _price) { - (address _feed, uint256 maxStaleness) = feedMetadata(asset); + (address _feed, uint256 maxStaleness, uint256 decimals) = feedMetadata( + asset + ); require(_feed != address(0), "Asset not available"); require(_feed != FIXED_PRICE, "Fixed price feeds not supported"); @@ -55,31 +61,20 @@ abstract contract OracleRouterBase is IOracle { "Oracle price too old" ); - uint8 decimals = getDecimals(_feed); - _price = _iprice.toUint256().scaleBy(18, decimals); } - function getDecimals(address _feed) internal view virtual returns (uint8) { - uint8 decimals = decimalsCache[_feed]; - require(decimals > 0, "Oracle: Decimals not cached"); - return decimals; - } - /** - * @notice Before an asset/feed price is fetches for the first time the - * decimals need to be cached. This is a gas optimization - * @param asset address of the asset - * @return uint8 corresponding asset decimals + * @notice Checks if the configured decimals matches what's in the aggregated Oracle feed. + * @param asset address of the asset token. eg DAI, stETH or CRV */ - function cacheDecimals(address asset) external returns (uint8) { - (address _feed, ) = feedMetadata(asset); - require(_feed != address(0), "Asset not available"); - require(_feed != FIXED_PRICE, "Fixed price feeds not supported"); + function isDecimalsValid(address asset) external view returns (bool) { + (address _feed, , uint256 decimalsConfig) = feedMetadata(asset); + if (_feed == FIXED_PRICE) return true; - uint8 decimals = AggregatorV3Interface(_feed).decimals(); - decimalsCache[_feed] = decimals; - return decimals; + uint256 decimalsActual = AggregatorV3Interface(_feed).decimals(); + + return decimalsConfig == decimalsActual; } } @@ -91,13 +86,20 @@ contract OracleRouter is OracleRouterBase { * @param asset address of the asset * @return feedAddress address of the price feed for the asset * @return maxStaleness maximum acceptable data staleness duration + * @return decimals number of decimals of the aggregated price. + * This is different to the decimals of the asset itself. + * For example, DAI and USDC are both 8 decimals */ function feedMetadata(address asset) internal pure virtual override - returns (address feedAddress, uint256 maxStaleness) + returns ( + address feedAddress, + uint256 maxStaleness, + uint256 decimals + ) { /* + STALENESS_BUFFER is added in case Oracle for some reason doesn't * update on heartbeat and we add a generous buffer amount. @@ -107,35 +109,42 @@ contract OracleRouter is OracleRouterBase { // Chainlink: DAI/USD feedAddress = 0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9; maxStaleness = 1 hours + STALENESS_BUFFER; + decimals = 8; } else if (asset == 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) { // https://data.chain.link/ethereum/mainnet/stablecoins/usdc-usd // Chainlink: USDC/USD feedAddress = 0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6; maxStaleness = 1 days + STALENESS_BUFFER; + decimals = 8; } else if (asset == 0xdAC17F958D2ee523a2206206994597C13D831ec7) { // https://data.chain.link/ethereum/mainnet/stablecoins/usdt-usd // Chainlink: USDT/USD feedAddress = 0x3E7d1eAB13ad0104d2750B8863b489D65364e32D; maxStaleness = 1 days + STALENESS_BUFFER; + decimals = 8; } else if (asset == 0xc00e94Cb662C3520282E6f5717214004A7f26888) { // https://data.chain.link/ethereum/mainnet/crypto-usd/comp-usd // Chainlink: COMP/USD feedAddress = 0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5; maxStaleness = 1 hours + STALENESS_BUFFER; + decimals = 8; } else if (asset == 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9) { // https://data.chain.link/ethereum/mainnet/crypto-usd/aave-usd // Chainlink: AAVE/USD feedAddress = 0x547a514d5e3769680Ce22B2361c10Ea13619e8a9; maxStaleness = 1 hours + STALENESS_BUFFER; + decimals = 8; } else if (asset == 0xD533a949740bb3306d119CC777fa900bA034cd52) { // https://data.chain.link/ethereum/mainnet/crypto-usd/crv-usd // Chainlink: CRV/USD feedAddress = 0xCd627aA160A6fA45Eb793D19Ef54f5062F20f33f; maxStaleness = 1 days + STALENESS_BUFFER; + decimals = 18; } else if (asset == 0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B) { // Chainlink: CVX/USD feedAddress = 0xd962fC30A72A84cE50161031391756Bf2876Af5D; maxStaleness = 1 days + STALENESS_BUFFER; + decimals = 18; } else { revert("Asset not available"); } @@ -143,7 +152,7 @@ contract OracleRouter is OracleRouterBase { } // @notice Oracle Router that denominates all prices in ETH -contract OETHOracleRouter is OracleRouter { +contract OETHOracleRouter is OracleRouterBase { using StableMath for uint256; /** @@ -160,7 +169,9 @@ contract OETHOracleRouter is OracleRouter { override returns (uint256 _price) { - (address _feed, uint256 maxStaleness) = feedMetadata(asset); + (address _feed, uint256 maxStaleness, uint256 decimals) = feedMetadata( + asset + ); if (_feed == FIXED_PRICE) { return 1e18; } @@ -174,7 +185,6 @@ contract OETHOracleRouter is OracleRouter { "Oracle price too old" ); - uint8 decimals = getDecimals(_feed); _price = uint256(_iprice).scaleBy(18, decimals); } @@ -184,47 +194,61 @@ contract OETHOracleRouter is OracleRouter { * @param asset address of the asset * @return feedAddress address of the price feed for the asset * @return maxStaleness maximum acceptable data staleness duration + * @return decimals number of decimals of the aggregated price. + * This is different to the decimals of the asset itself. + * For example, CRV and CVX are both 8 decimals. */ function feedMetadata(address asset) internal pure virtual override - returns (address feedAddress, uint256 maxStaleness) + returns ( + address feedAddress, + uint256 maxStaleness, + uint256 decimals + ) { if (asset == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) { // FIXED_PRICE: WETH/ETH feedAddress = FIXED_PRICE; maxStaleness = 0; + decimals = 18; } else if (asset == 0x5E8422345238F34275888049021821E8E08CAa1f) { // frxETH/ETH feedAddress = 0xC58F3385FBc1C8AD2c0C9a061D7c13b141D7A5Df; maxStaleness = 18 hours + STALENESS_BUFFER; + decimals = 18; } else if (asset == 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84) { // https://data.chain.link/ethereum/mainnet/crypto-eth/steth-eth // Chainlink: stETH/ETH feedAddress = 0x86392dC19c0b719886221c78AB11eb8Cf5c52812; maxStaleness = 1 days + STALENESS_BUFFER; + decimals = 18; } else if (asset == 0xae78736Cd615f374D3085123A210448E74Fc6393) { // https://data.chain.link/ethereum/mainnet/crypto-eth/reth-eth // Chainlink: rETH/ETH feedAddress = 0x536218f9E9Eb48863970252233c8F271f554C2d0; maxStaleness = 1 days + STALENESS_BUFFER; + decimals = 18; } else if (asset == 0xD533a949740bb3306d119CC777fa900bA034cd52) { // https://data.chain.link/ethereum/mainnet/crypto-eth/crv-eth // Chainlink: CRV/ETH feedAddress = 0x8a12Be339B0cD1829b91Adc01977caa5E9ac121e; maxStaleness = 1 days + STALENESS_BUFFER; + decimals = 18; } else if (asset == 0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B) { // https://data.chain.link/ethereum/mainnet/crypto-eth/cvx-eth // Chainlink: CVX/ETH feedAddress = 0xC9CbF687f43176B302F03f5e58470b77D07c61c6; maxStaleness = 1 days + STALENESS_BUFFER; + decimals = 18; } else if (asset == 0xBe9895146f7AF43049ca1c1AE358B0541Ea49704) { // https://data.chain.link/ethereum/mainnet/crypto-eth/cbeth-eth // Chainlink: cbETH/ETH feedAddress = 0xF017fcB346A1885194689bA23Eff2fE6fA5C483b; maxStaleness = 1 days + STALENESS_BUFFER; + decimals = 18; } else { revert("Asset not available"); } diff --git a/contracts/deploy/001_core.js b/contracts/deploy/001_core.js index 4082032235..bc96c35b79 100644 --- a/contracts/deploy/001_core.js +++ b/contracts/deploy/001_core.js @@ -728,28 +728,31 @@ const deployOracles = async () => { const maxStaleness = 24 * 60 * 60 * 365 * 100; const oracleFeeds = [ - [assetAddresses.DAI, oracleAddresses.chainlink.DAI_USD], - [assetAddresses.USDC, oracleAddresses.chainlink.USDC_USD], - [assetAddresses.USDT, oracleAddresses.chainlink.USDT_USD], - [assetAddresses.TUSD, oracleAddresses.chainlink.TUSD_USD], - [assetAddresses.COMP, oracleAddresses.chainlink.COMP_USD], - [assetAddresses.AAVE, oracleAddresses.chainlink.AAVE_USD], - [assetAddresses.CRV, oracleAddresses.chainlink.CRV_USD], - [assetAddresses.CVX, oracleAddresses.chainlink.CVX_USD], - [assetAddresses.RETH, oracleAddresses.chainlink.RETH_ETH], - [assetAddresses.WETH, oracleAddresses.chainlink.WETH_ETH], - [addresses.mainnet.WETH, oracleAddresses.chainlink.WETH_ETH], - [assetAddresses.stETH, oracleAddresses.chainlink.STETH_ETH], - [assetAddresses.frxETH, oracleAddresses.chainlink.FRXETH_ETH], + [assetAddresses.DAI, oracleAddresses.chainlink.DAI_USD, 8], + [assetAddresses.USDC, oracleAddresses.chainlink.USDC_USD, 8], + [assetAddresses.USDT, oracleAddresses.chainlink.USDT_USD, 8], + [assetAddresses.TUSD, oracleAddresses.chainlink.TUSD_USD, 8], + [assetAddresses.COMP, oracleAddresses.chainlink.COMP_USD, 8], + [assetAddresses.AAVE, oracleAddresses.chainlink.AAVE_USD, 8], + [assetAddresses.CRV, oracleAddresses.chainlink.CRV_USD, 18], + [assetAddresses.CVX, oracleAddresses.chainlink.CVX_USD, 18], + [assetAddresses.RETH, oracleAddresses.chainlink.RETH_ETH, 18], + [assetAddresses.WETH, oracleAddresses.chainlink.WETH_ETH, 18], + [addresses.mainnet.WETH, oracleAddresses.chainlink.WETH_ETH, 18], + [assetAddresses.stETH, oracleAddresses.chainlink.STETH_ETH, 18], + [assetAddresses.frxETH, oracleAddresses.chainlink.FRXETH_ETH, 18], [ assetAddresses.NonStandardToken, oracleAddresses.chainlink.NonStandardToken_USD, + 8, ], ]; - for (const [asset, oracle] of oracleFeeds) { + for (const [asset, oracle, decimals] of oracleFeeds) { await withConfirmation( - oracleRouter.connect(sDeployer).setFeed(asset, oracle, maxStaleness) + oracleRouter + .connect(sDeployer) + .setFeed(asset, oracle, maxStaleness, decimals) ); } }; diff --git a/contracts/deploy/081_oeth_oracle.js b/contracts/deploy/081_oeth_oracle.js index e2eedf3d4a..5764411fa0 100644 --- a/contracts/deploy/081_oeth_oracle.js +++ b/contracts/deploy/081_oeth_oracle.js @@ -1,3 +1,5 @@ +const { expect } = require("chai"); + const addresses = require("../utils/addresses"); const { deploymentWithGovernanceProposal, @@ -19,6 +21,10 @@ module.exports = deploymentWithGovernanceProposal( // 1. Connect to the OETH Vault as its governor via the proxy const cVaultProxy = await ethers.getContract("OETHVaultProxy"); + const cVaultAdmin = await ethers.getContractAt( + "VaultAdmin", + cVaultProxy.address + ); // 2. Deploy the new Vault implementation const dVaultCore = await deployWithConfirmation("OETHVaultCore"); @@ -52,6 +58,24 @@ module.exports = deploymentWithGovernanceProposal( .transferGovernance(addresses.mainnet.Timelock) ); + // 5. Deploy the new OETH Oracle Router + await deployWithConfirmation("OETHOracleRouter", [], null, true); + const cOETHOracleRouter = await ethers.getContract("OETHOracleRouter"); + + // Putting checks in here are the fixture replaced the OETHOracleRouter implementation + expect(await cOETHOracleRouter.isDecimalsValid(addresses.mainnet.WETH)).to + .be.true; + expect(await cOETHOracleRouter.isDecimalsValid(addresses.mainnet.stETH)).to + .be.true; + expect(await cOETHOracleRouter.isDecimalsValid(addresses.mainnet.rETH)).to + .be.true; + expect(await cOETHOracleRouter.isDecimalsValid(addresses.mainnet.frxETH)).to + .be.true; + expect(await cOETHOracleRouter.isDecimalsValid(addresses.mainnet.CRV)).to.be + .true; + expect(await cOETHOracleRouter.isDecimalsValid(addresses.mainnet.CVX)).to.be + .true; + // 4. Governance Actions return { name: "Upgrade the OETH Vault and deploy the OETH Oracle and Oracle Updater.", @@ -74,6 +98,11 @@ module.exports = deploymentWithGovernanceProposal( signature: "claimGovernance()", args: [], }, + { + contract: cVaultAdmin, + signature: "setPriceProvider(address)", + args: [cOETHOracleRouter.address], + }, ], }; } diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index da421fd83a..80974b8d54 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -1856,9 +1856,9 @@ async function hackedVaultFixture() { await oracleRouter.setFeed( evilDAI.address, oracleAddresses.chainlink.DAI_USD, - maxStaleness + maxStaleness, + 8 ); - await oracleRouter.cacheDecimals(evilDAI.address); await fixture.vault.connect(sGovernor).supportAsset(evilDAI.address, 0); diff --git a/contracts/test/helpers.js b/contracts/test/helpers.js index 5c7e82989f..72809e4fb1 100644 --- a/contracts/test/helpers.js +++ b/contracts/test/helpers.js @@ -294,12 +294,13 @@ const getOracleAddress = async (deployments) => { */ const setOracleTokenPriceUsd = async (tokenSymbol, usdPrice) => { const symbolMap = { - USDC: 6, - USDT: 6, - DAI: 6, - COMP: 6, - CVX: 6, - CRV: 6, + USDC: 8, + USDT: 8, + DAI: 8, + COMP: 8, + CVX: 18, + CRV: 18, + RETHETH: 18 }; if (isMainnetOrFork) { @@ -314,7 +315,7 @@ const setOracleTokenPriceUsd = async (tokenSymbol, usdPrice) => { const decimals = Object.keys(symbolMap).includes(tokenSymbol) ? symbolMap[tokenSymbol] - : 18; + : 8; await tokenFeed.setDecimals(decimals); await tokenFeed.setPrice(parseUnits(usdPrice, decimals)); }; diff --git a/contracts/test/oracle/oracle.fork-test.js b/contracts/test/oracle/oracle.fork-test.js index c9e1603067..e763ffa135 100644 --- a/contracts/test/oracle/oracle.fork-test.js +++ b/contracts/test/oracle/oracle.fork-test.js @@ -24,6 +24,7 @@ describe("ForkTest: Oracles", function () { beforeEach(async () => { oethOracleRouter = await ethers.getContract("OETHOracleRouter"); }); + it("should get rETH price", async () => { const { reth } = fixture; diff --git a/contracts/test/oracle/oracle.js b/contracts/test/oracle/oracle.js index 36c591c859..1e955a8fbc 100644 --- a/contracts/test/oracle/oracle.js +++ b/contracts/test/oracle/oracle.js @@ -13,6 +13,13 @@ describe("Vault Oracle", async () => { beforeEach(async () => { fixture = await loadDefaultFixture(); }); + it("should have decimals configured correctly", async () => { + const { oracleRouter, dai, usdc, usdt, crv, cvx } = fixture; + + for (const asset of [dai, usdc, usdt, crv, cvx]) { + expect(await oracleRouter.isDecimalsValid(asset.address)).to.be.true; + } + }); describe("Oracle read methods for DAPP", () => { it("should read the mint price", async () => { const { vault, usdt } = fixture; diff --git a/contracts/test/timelock.js b/contracts/test/timelock.js index 2d470c9e62..f988b268ab 100644 --- a/contracts/test/timelock.js +++ b/contracts/test/timelock.js @@ -30,8 +30,8 @@ describe("Governor's Timelock controls oracleRouter", function () { const args = [ { contract: oracle, - signature: "setFeed(address,address,uint256)", - args: [WETH, WETH, 0], + signature: "setFeed(address,address,uint256,uint256)", + args: [WETH, WETH, 0, 18], }, ]; proposalId = await propose(fixture, args, "Set Feed"); diff --git a/contracts/test/vault/compound.js b/contracts/test/vault/compound.js index dc56d982a0..8cc60450a3 100644 --- a/contracts/test/vault/compound.js +++ b/contracts/test/vault/compound.js @@ -418,10 +418,8 @@ describe("Vault with Compound strategy", function () { }); it("Should handle non-standard token deposits", async () => { - let { ousd, vault, matt, nonStandardToken, oracleRouter, governor } = - fixture; + let { ousd, vault, matt, nonStandardToken, governor } = fixture; - await oracleRouter.cacheDecimals(nonStandardToken.address); if (nonStandardToken) { await vault.connect(governor).supportAsset(nonStandardToken.address, 0); } diff --git a/contracts/test/vault/index.js b/contracts/test/vault/index.js index 816674198b..dd0f107474 100644 --- a/contracts/test/vault/index.js +++ b/contracts/test/vault/index.js @@ -39,9 +39,9 @@ describe("Vault", function () { await oracleRouter.setFeed( ousd.address, oracleAddresses.chainlink.DAI_USD, - maxStaleness + maxStaleness, + 8 ); - await oracleRouter.cacheDecimals(ousd.address); await expect(vault.connect(governor).supportAsset(ousd.address, 0)).to.emit( vault, "AssetSupported" @@ -136,10 +136,8 @@ describe("Vault", function () { }); it("Should correctly handle a deposit failure of Non-Standard ERC20 Token", async function () { - const { ousd, vault, anna, nonStandardToken, oracleRouter, governor } = - fixture; + const { ousd, vault, anna, nonStandardToken, governor } = fixture; - await oracleRouter.cacheDecimals(nonStandardToken.address); await vault.connect(governor).supportAsset(nonStandardToken.address, 0); await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); await setOracleTokenPriceUsd("NonStandardToken", "1.30"); @@ -168,10 +166,8 @@ describe("Vault", function () { }); it("Should correctly handle a deposit of Non-Standard ERC20 Token", async function () { - const { ousd, vault, anna, nonStandardToken, oracleRouter, governor } = - fixture; + const { ousd, vault, anna, nonStandardToken, governor } = fixture; - await oracleRouter.cacheDecimals(nonStandardToken.address); await vault.connect(governor).supportAsset(nonStandardToken.address, 0); await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); diff --git a/contracts/test/vault/redeem.js b/contracts/test/vault/redeem.js index 78e44366bf..a6fd0eaafb 100644 --- a/contracts/test/vault/redeem.js +++ b/contracts/test/vault/redeem.js @@ -94,10 +94,8 @@ describe("Vault Redeem", function () { }); it("Should allow redeems of non-standard tokens", async () => { - const { ousd, vault, anna, governor, oracleRouter, nonStandardToken } = - fixture; + const { ousd, vault, anna, governor, nonStandardToken } = fixture; - await oracleRouter.cacheDecimals(nonStandardToken.address); await vault.connect(governor).supportAsset(nonStandardToken.address, 0); await setOracleTokenPriceUsd("NonStandardToken", "1.00"); From a3e8d93eca85f33320c6102755101a92fa0f6196 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Mon, 27 Nov 2023 21:08:29 +1100 Subject: [PATCH 3/5] Added isDecimalsValid to OETHOracleRouter fork tests --- contracts/test/oracle/oracle.fork-test.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/contracts/test/oracle/oracle.fork-test.js b/contracts/test/oracle/oracle.fork-test.js index e763ffa135..dfeae9be4e 100644 --- a/contracts/test/oracle/oracle.fork-test.js +++ b/contracts/test/oracle/oracle.fork-test.js @@ -25,6 +25,21 @@ describe("ForkTest: Oracles", function () { oethOracleRouter = await ethers.getContract("OETHOracleRouter"); }); + it("should have valid Oracle decimals", async () => { + expect(await oethOracleRouter.isDecimalsValid(addresses.mainnet.WETH)).to + .be.true; + expect(await oethOracleRouter.isDecimalsValid(addresses.mainnet.rETH)).to + .be.true; + expect(await oethOracleRouter.isDecimalsValid(addresses.mainnet.frxETH)) + .to.be.true; + expect(await oethOracleRouter.isDecimalsValid(addresses.mainnet.stETH)).to + .be.true; + expect(await oethOracleRouter.isDecimalsValid(addresses.mainnet.CRV)).to + .be.true; + expect(await oethOracleRouter.isDecimalsValid(addresses.mainnet.CVX)).to + .be.true; + }); + it("should get rETH price", async () => { const { reth } = fixture; @@ -60,6 +75,14 @@ describe("ForkTest: Oracles", function () { await josh.sendTransaction(tx); } }); + it.only("should get gas costs of assets", async () => { + const { weth, josh } = fixture; + + const tx = await oethOracleRouter + .connect(josh) + .populateTransaction.price(weth.address); + await josh.sendTransaction(tx); + }); }); describe("OETH Oracle", () => { it("Should be initialized", async () => { From 916195706ed88aff3586740ff96a98c82f51c1d6 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Mon, 27 Nov 2023 21:18:11 +1100 Subject: [PATCH 4/5] removed single asset price check --- contracts/test/oracle/oracle.fork-test.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/contracts/test/oracle/oracle.fork-test.js b/contracts/test/oracle/oracle.fork-test.js index dfeae9be4e..3f9bffc384 100644 --- a/contracts/test/oracle/oracle.fork-test.js +++ b/contracts/test/oracle/oracle.fork-test.js @@ -75,14 +75,6 @@ describe("ForkTest: Oracles", function () { await josh.sendTransaction(tx); } }); - it.only("should get gas costs of assets", async () => { - const { weth, josh } = fixture; - - const tx = await oethOracleRouter - .connect(josh) - .populateTransaction.price(weth.address); - await josh.sendTransaction(tx); - }); }); describe("OETH Oracle", () => { it("Should be initialized", async () => { From 4bdf8224d9e8880b62370637891f7d0e8f1e1daf Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Mon, 27 Nov 2023 21:34:35 +1100 Subject: [PATCH 5/5] Added under 0.7 and over 1.0 frxETH price tests --- contracts/test/oracle/oracle.fork-test.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/contracts/test/oracle/oracle.fork-test.js b/contracts/test/oracle/oracle.fork-test.js index 3f9bffc384..61449f9429 100644 --- a/contracts/test/oracle/oracle.fork-test.js +++ b/contracts/test/oracle/oracle.fork-test.js @@ -5,6 +5,7 @@ const { loadDefaultFixture } = require("../_fixture"); const { isCI } = require("../helpers"); const { impersonateAndFund } = require("../../utils/signers.js"); const addresses = require("../../utils/addresses"); +const { setFraxOraclePrice } = require("../../utils/frax.js"); const log = require("../../utils/logger")("test:fork:oracles"); @@ -75,6 +76,24 @@ describe("ForkTest: Oracles", function () { await josh.sendTransaction(tx); } }); + it("Should get frxETH price under 0.7", async () => { + const { frxETH } = fixture; + + const testPrice = parseUnits("0.65"); + await setFraxOraclePrice(testPrice); + + const price = await oethOracleRouter.price(frxETH.address); + expect(price).to.eq(testPrice); + }); + it("Should get frxETH price over 1.0", async () => { + const { frxETH } = fixture; + + const testPrice = parseUnits("1.01"); + await setFraxOraclePrice(testPrice); + + const price = await oethOracleRouter.price(frxETH.address); + expect(price).to.eq(testPrice); + }); }); describe("OETH Oracle", () => { it("Should be initialized", async () => {