From 9602f0c06a1117768c70789f6cdeb8913b39cb01 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Fri, 14 Apr 2023 16:10:25 +0200 Subject: [PATCH 1/6] harvester now considers different oracle decimals. Fixing unit tests --- contracts/contracts/harvest/Harvester.sol | 5 ++- contracts/contracts/oracle/OracleRouter.sol | 6 +-- contracts/deploy/000_mock.js | 20 ++++----- contracts/deploy/001_core.js | 47 ++++++++++++++++----- contracts/deploy/052_decimal_cache.js | 11 +++++ contracts/test/_fixture.js | 2 + contracts/test/helpers.js | 10 +++-- contracts/test/oracle/oracle.js | 8 ++-- contracts/test/vault/compound.js | 26 ++++++++---- contracts/test/vault/exchangeRate.js | 30 +++++++------ contracts/test/vault/harvester.js | 4 +- contracts/test/vault/index.js | 32 ++++++++------ contracts/test/vault/rebase.js | 20 ++++----- contracts/test/vault/redeem.js | 34 +++++++++------ 14 files changed, 162 insertions(+), 93 deletions(-) diff --git a/contracts/contracts/harvest/Harvester.sol b/contracts/contracts/harvest/Harvester.sol index 7d5d40d04d..7d5168c953 100644 --- a/contracts/contracts/harvest/Harvester.sol +++ b/contracts/contracts/harvest/Harvester.sol @@ -382,12 +382,13 @@ contract Harvester is Governable { // This'll revert if there is no price feed uint256 oraclePrice = IOracle(priceProvider).price(_swapToken); - // Oracle price is 1e8, USDT output is 1e6 + + // Oracle price is 1e18, USDT output is 1e6 uint256 minExpected = (balanceToSwap * oraclePrice * (1e4 - tokenConfig.allowedSlippageBps)).scaleBy( // max allowed slippage 6, - Helpers.getDecimals(_swapToken) + 8 + Helpers.getDecimals(_swapToken) + 18 ) / 1e4; // fix the max slippage decimal position // Uniswap redemption path diff --git a/contracts/contracts/oracle/OracleRouter.sol b/contracts/contracts/oracle/OracleRouter.sol index 5a897abee1..34217d46d6 100644 --- a/contracts/contracts/oracle/OracleRouter.sol +++ b/contracts/contracts/oracle/OracleRouter.sol @@ -48,11 +48,7 @@ abstract contract OracleRouterBase is IOracle { return uint256(_price); } - function getDecimals(address _asset) - internal - view - returns (uint8) - { + function getDecimals(address _asset) internal view returns (uint8) { uint8 decimals = decimalsCache[_asset]; require(decimals > 0, "Oracle: Decimals not cached"); return decimals; diff --git a/contracts/deploy/000_mock.js b/contracts/deploy/000_mock.js index 4f9a2e8c3b..f253ff78b1 100644 --- a/contracts/deploy/000_mock.js +++ b/contracts/deploy/000_mock.js @@ -140,47 +140,47 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { await deploy("MockChainlinkOracleFeedDAI", { from: deployerAddr, contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 18], // 1 DAI = 1 USD, 8 digits decimal. + args: [parseUnits("1", 8).toString(), 8], // 1 DAI = 1 USD, 8 digits decimal. }); await deploy("MockChainlinkOracleFeedUSDT", { from: deployerAddr, contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 18], // 1 USDT = 1 USD, 8 digits decimal. + args: [parseUnits("1", 8).toString(), 8], // 1 USDT = 1 USD, 8 digits decimal. }); await deploy("MockChainlinkOracleFeedUSDC", { from: deployerAddr, contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 18], // 1 USDC = 1 USD, 8 digits decimal. + args: [parseUnits("1", 8).toString(), 8], // 1 USDC = 1 USD, 8 digits decimal. }); await deploy("MockChainlinkOracleFeedTUSD", { from: deployerAddr, contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 18], // 1 TUSD = 1 USD, 8 digits decimal. + args: [parseUnits("1", 8).toString(), 8], // 1 TUSD = 1 USD, 8 digits decimal. }); await deploy("MockChainlinkOracleFeedCOMP", { from: deployerAddr, contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 18], // 1 COMP = 1 USD, 8 digits decimal. + args: [parseUnits("1", 8).toString(), 8], // 1 COMP = 1 USD, 8 digits decimal. }); await deploy("MockChainlinkOracleFeedAAVE", { from: deployerAddr, contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 18], // 1 AAVE = 1 USD, 8 digits decimal. + args: [parseUnits("1", 8).toString(), 8], // 1 AAVE = 1 USD, 8 digits decimal. }); await deploy("MockChainlinkOracleFeedCRV", { from: deployerAddr, contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 18], // 1 CRV = 1 USD, 8 digits decimal. + args: [parseUnits("1", 8).toString(), 8], // 1 CRV = 1 USD, 8 digits decimal. }); await deploy("MockChainlinkOracleFeedCVX", { from: deployerAddr, contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 18], // 1 CVX = 1 USD, 8 digits decimal. + args: [parseUnits("1", 8).toString(), 8], // 1 CVX = 1 USD, 8 digits decimal. }); await deploy("MockChainlinkOracleFeedNonStandardToken", { from: deployerAddr, contract: "MockChainlinkOracleFeed", - args: [parseUnits("1", 8).toString(), 18], // 1 = 1 USD, 8 digits decimal. + args: [parseUnits("1", 8).toString(), 8], // 1 = 1 USD, 8 digits decimal. }); await deploy("MockChainlinkOracleFeedETH", { from: deployerAddr, @@ -195,7 +195,7 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { await deploy("MockChainlinkOracleFeedRETHETH", { from: deployerAddr, contract: "MockChainlinkOracleFeed", - args: [parseUnits("1.2", 8).toString(), 18], // 1 RETH = 1.2 ETH , 8 digits decimal. + args: [parseUnits("1.2", 18).toString(), 18], // 1 RETH = 1.2 ETH , 18 digits decimal. }); // Deploy mock Uniswap router diff --git a/contracts/deploy/001_core.js b/contracts/deploy/001_core.js index 3cf8884d22..51b0de61c5 100644 --- a/contracts/deploy/001_core.js +++ b/contracts/deploy/001_core.js @@ -677,52 +677,79 @@ const deployOracles = async () => { // Not needed in production const oracleAddresses = await getOracleAddresses(deployments); const assetAddresses = await getAssetAddresses(deployments); - withConfirmation( + await withConfirmation( oracleRouter .connect(sDeployer) .setFeed(assetAddresses.DAI, oracleAddresses.chainlink.DAI_USD) ); - withConfirmation( + await withConfirmation( + oracleRouter.connect(sDeployer).cacheDecimals(assetAddresses.DAI) + ); + await withConfirmation( oracleRouter .connect(sDeployer) .setFeed(assetAddresses.USDC, oracleAddresses.chainlink.USDC_USD) ); - withConfirmation( + await withConfirmation( + oracleRouter.connect(sDeployer).cacheDecimals(assetAddresses.USDC) + ); + await withConfirmation( oracleRouter .connect(sDeployer) .setFeed(assetAddresses.USDT, oracleAddresses.chainlink.USDT_USD) ); - withConfirmation( + await withConfirmation( + oracleRouter.connect(sDeployer).cacheDecimals(assetAddresses.USDT) + ); + await withConfirmation( oracleRouter .connect(sDeployer) .setFeed(assetAddresses.TUSD, oracleAddresses.chainlink.TUSD_USD) ); - withConfirmation( + await withConfirmation( + oracleRouter.connect(sDeployer).cacheDecimals(assetAddresses.TUSD) + ); + await withConfirmation( oracleRouter .connect(sDeployer) .setFeed(assetAddresses.COMP, oracleAddresses.chainlink.COMP_USD) ); - withConfirmation( + await withConfirmation( + oracleRouter.connect(sDeployer).cacheDecimals(assetAddresses.COMP) + ); + await withConfirmation( oracleRouter .connect(sDeployer) .setFeed(assetAddresses.AAVE, oracleAddresses.chainlink.AAVE_USD) ); - withConfirmation( + await withConfirmation( + oracleRouter.connect(sDeployer).cacheDecimals(assetAddresses.AAVE) + ); + await withConfirmation( oracleRouter .connect(sDeployer) .setFeed(assetAddresses.CRV, oracleAddresses.chainlink.CRV_USD) ); - withConfirmation( + await withConfirmation( + oracleRouter.connect(sDeployer).cacheDecimals(assetAddresses.CRV) + ); + await withConfirmation( oracleRouter .connect(sDeployer) .setFeed(assetAddresses.CVX, oracleAddresses.chainlink.CVX_USD) ); - withConfirmation( + await withConfirmation( + oracleRouter.connect(sDeployer).cacheDecimals(assetAddresses.CVX) + ); + await withConfirmation( oracleRouter .connect(sDeployer) .setFeed(assetAddresses.RETH, oracleAddresses.chainlink.RETH_ETH) ); - withConfirmation( + await withConfirmation( + oracleRouter.connect(sDeployer).cacheDecimals(assetAddresses.RETH) + ); + await withConfirmation( oracleRouter .connect(sDeployer) .setFeed( diff --git a/contracts/deploy/052_decimal_cache.js b/contracts/deploy/052_decimal_cache.js index e7babd7960..0440ae1269 100644 --- a/contracts/deploy/052_decimal_cache.js +++ b/contracts/deploy/052_decimal_cache.js @@ -34,6 +34,12 @@ module.exports = deploymentWithGovernanceProposal( await cOracleRouter.cacheDecimals(addresses.mainnet.CRV); await cOracleRouter.cacheDecimals(addresses.mainnet.CVX); + const cHarvesterProxy = await ethers.getContract("HarvesterProxy"); + const dHarvester = await deployWithConfirmation("Harvester", [ + cVaultProxy.address, + assetAddresses.USDT, + ]); + const cVaultAdmin = new ethers.Contract(cVaultProxy.address, [ { inputs: [ @@ -93,6 +99,11 @@ module.exports = deploymentWithGovernanceProposal( signature: "cacheDecimals(address)", args: [assetAddresses.USDC], }, + { + contract: cHarvesterProxy, + signature: "upgradeTo(address)", + args: [dHarvester.address], + }, ], }; } diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index bd69aed6b0..a69b22c241 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -1200,6 +1200,8 @@ async function hackedVaultFixture() { evilDAI.address, oracleAddresses.chainlink.DAI_USD ); + await oracleRouter.cacheDecimals(evilDAI.address); + await fixture.vault.connect(sGovernor).supportAsset(evilDAI.address, 0); fixture.evilDAI = evilDAI; diff --git a/contracts/test/helpers.js b/contracts/test/helpers.js index a42ca8d30a..3831ff7e91 100644 --- a/contracts/test/helpers.js +++ b/contracts/test/helpers.js @@ -210,10 +210,11 @@ const getOracleAddress = async (deployments) => { * This first sets the ETH price in USD, then token price in ETH * * @param {string} tokenSymbol: "DAI", USDC", etc... + * @param {string} tokenAddress: 0x.... * @param {number} usdPrice: price of the token in USD. * @returns {Promise} */ -const setOracleTokenPriceUsd = async (tokenSymbol, usdPrice) => { +const setOracleTokenPriceUsd = async (tokenSymbol, tokenAddress, usdPrice) => { if (isMainnetOrFork) { throw new Error( `setOracleTokenPriceUsd not supported on network ${hre.network.name}` @@ -223,8 +224,11 @@ const setOracleTokenPriceUsd = async (tokenSymbol, usdPrice) => { const tokenFeed = await ethers.getContract( "MockChainlinkOracleFeed" + tokenSymbol ); - await tokenFeed.setDecimals(8); - await tokenFeed.setPrice(parseUnits(usdPrice, 8)); + const oracleRouter = await ethers.getContract("OracleRouter"); + + await tokenFeed.setDecimals(18); + await oracleRouter.cacheDecimals(tokenAddress); + await tokenFeed.setPrice(parseUnits(usdPrice, 18)); }; const getOracleAddresses = async (deployments) => { diff --git a/contracts/test/oracle/oracle.js b/contracts/test/oracle/oracle.js index 5eec26ae90..e3c9a7f71b 100644 --- a/contracts/test/oracle/oracle.js +++ b/contracts/test/oracle/oracle.js @@ -23,7 +23,7 @@ describe("Oracle", async () => { ]; for (const test of tests) { const [actual, expectedRead] = test; - await setOracleTokenPriceUsd("USDT", actual); + await setOracleTokenPriceUsd("USDT", usdt.address, actual); expect(await vault.priceUnitMint(usdt.address)).to.equal( ousdUnits(expectedRead) ); @@ -34,7 +34,7 @@ describe("Oracle", async () => { const { vault, usdt } = await loadFixture(defaultFixture); const prices = ["0.85", "0.997"]; for (const price of prices) { - await setOracleTokenPriceUsd("USDT", price); + await setOracleTokenPriceUsd("USDT", usdt.address, price); await expect(vault.priceUnitMint(usdt.address)).to.be.revertedWith( "Asset price below peg" ); @@ -50,7 +50,7 @@ describe("Oracle", async () => { ]; for (const test of tests) { const [actual, expectedRead] = test; - await setOracleTokenPriceUsd("USDT", actual); + await setOracleTokenPriceUsd("USDT", usdt.address, actual); expect(await vault.priceUnitRedeem(usdt.address)).to.equal( ousdUnits(expectedRead) ); @@ -77,7 +77,7 @@ describe("Oracle", async () => { const label = `Should ${revertLabel} because of drift at $${price}`; it(label, async () => { const { vault, usdt } = await loadFixture(defaultFixture); - await setOracleTokenPriceUsd("USDT", price); + await setOracleTokenPriceUsd("USDT", usdt.address, price); if (expectedRevert) { const tx = vault.priceUnitRedeem(usdt.address); await expect(tx).to.be.revertedWith(expectedRevert); diff --git a/contracts/test/vault/compound.js b/contracts/test/vault/compound.js index 4cbf9e848e..7a33b18fca 100644 --- a/contracts/test/vault/compound.js +++ b/contracts/test/vault/compound.js @@ -104,7 +104,7 @@ describe("Vault with Compound strategy", function () { const { anna, ousd, usdc, vault } = await loadFixture(compoundVaultFixture); await expect(anna).has.a.balanceOf("0", ousd); // The mint process maxes out at a 1.0 price - await setOracleTokenPriceUsd("USDC", "1.25"); + await setOracleTokenPriceUsd("USDC", usdc.address, "1.25"); await usdc.connect(anna).approve(vault.address, usdcUnits("50")); await vault.connect(anna).mint(usdc.address, usdcUnits("50"), 0); await expect(anna).has.a.balanceOf("50", ousd); @@ -402,7 +402,7 @@ describe("Vault with Compound strategy", function () { // 100 + 200 + 200 await expect(matt).has.an.approxBalanceOf("500", ousd, "Initial"); - await setOracleTokenPriceUsd("USDC", "1.30"); + await setOracleTokenPriceUsd("USDC", usdc.address, "1.30"); await vault.rebase(); await expectApproxSupply(ousd, ousdUnits("600.0")); @@ -412,7 +412,7 @@ describe("Vault with Compound strategy", function () { "After some assets double" ); - await setOracleTokenPriceUsd("USDC", "1.00"); + await setOracleTokenPriceUsd("USDC", usdc.address, "1.00"); await vault.rebase(); await expectApproxSupply(ousd, ousdUnits("600.0")); @@ -424,15 +424,19 @@ describe("Vault with Compound strategy", function () { }); it("Should handle non-standard token deposits", async () => { - let { ousd, vault, matt, nonStandardToken, governor } = await loadFixture( - compoundVaultFixture - ); + let { ousd, vault, matt, nonStandardToken, oracleRouter, governor } = + await loadFixture(compoundVaultFixture); + await oracleRouter.cacheDecimals(nonStandardToken.address); if (nonStandardToken) { await vault.connect(governor).supportAsset(nonStandardToken.address, 0); } - await setOracleTokenPriceUsd("NonStandardToken", "1.00"); + await setOracleTokenPriceUsd( + "NonStandardToken", + nonStandardToken.address, + "1.00" + ); await nonStandardToken .connect(matt) @@ -466,7 +470,11 @@ describe("Vault with Compound strategy", function () { await expect(matt).has.an.approxBalanceOf("200", ousd, "Initial"); await vault.rebase(); await expect(matt).has.an.approxBalanceOf("200", ousd, "After null rebase"); - await setOracleTokenPriceUsd("NonStandardToken", "1.40"); + await setOracleTokenPriceUsd( + "NonStandardToken", + nonStandardToken.address, + "1.40" + ); await vault.rebase(); await expectApproxSupply(ousd, ousdUnits("300.0")); @@ -753,7 +761,7 @@ describe("Vault with Compound strategy", function () { // Mock router gives 1:1, if we set this to something high there will be // too much slippage - await setOracleTokenPriceUsd("COMP", "1.3"); + await setOracleTokenPriceUsd("COMP", comp.address, "1.3"); const compAmount = utils.parseUnits("100", 18); await comp.connect(governor).mint(compAmount); diff --git a/contracts/test/vault/exchangeRate.js b/contracts/test/vault/exchangeRate.js index 32813ef7e1..96f4fd2b71 100644 --- a/contracts/test/vault/exchangeRate.js +++ b/contracts/test/vault/exchangeRate.js @@ -20,12 +20,16 @@ describe("Vault Redeem", function () { fixture = await loadFixture(defaultFixture); const { vault, reth, governor } = fixture; await vault.connect(governor).supportAsset(reth.address, 1); - await setOracleTokenPriceUsd("RETHETH", "1.2"); + await setOracleTokenPriceUsd("RETHETH", reth.address, "1.2"); }); it("Should mint at a positive exchange rate", async () => { - const { ousd, vault, reth, anna } = fixture; + const { ousd, vault, reth, oracleRouter, anna } = fixture; + console.log( + "ORACLE PRICE", + (await oracleRouter.price(reth.address)).toString() + ); await reth.connect(anna).mint(daiUnits("4.0")); await reth.connect(anna).approve(vault.address, daiUnits("4.0")); await vault.connect(anna).mint(reth.address, daiUnits("4.0"), 0); @@ -35,7 +39,7 @@ describe("Vault Redeem", function () { it("Should mint less at low oracle, positive exchange rate", async () => { const { ousd, vault, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", "1.199"); + await setOracleTokenPriceUsd("RETHETH", reth.address, "1.199"); await reth.connect(anna).mint(daiUnits("4.0")); await reth.connect(anna).approve(vault.address, daiUnits("4.0")); await vault.connect(anna).mint(reth.address, daiUnits("4.0"), 0); @@ -45,7 +49,7 @@ describe("Vault Redeem", function () { it("Should revert mint at too low oracle, positive exchange rate", async () => { const { vault, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", "1.00"); + await setOracleTokenPriceUsd("RETHETH", reth.address, "1.00"); await reth.connect(anna).mint(daiUnits("4.0")); await reth.connect(anna).approve(vault.address, daiUnits("4.0")); const tx = vault.connect(anna).mint(reth.address, daiUnits("4.0"), 0); @@ -55,7 +59,7 @@ describe("Vault Redeem", function () { it("Should mint same at high oracle, positive exchange rate", async () => { const { ousd, vault, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", "1.2"); + await setOracleTokenPriceUsd("RETHETH", reth.address, "1.2"); await reth.connect(anna).mint(daiUnits("4.0")); await reth.connect(anna).approve(vault.address, daiUnits("4.0")); await vault.connect(anna).mint(reth.address, daiUnits("4.0"), 0); @@ -78,7 +82,7 @@ describe("Vault Redeem", function () { "afterGift" ); - await setOracleTokenPriceUsd("RETHETH", "1.4"); + await setOracleTokenPriceUsd("RETHETH", reth.address, "1.4"); await reth.setExchangeRate(daiUnits("1.4")); await vault.rebase(); const afterExchangeUp = await ousd.totalSupply(); @@ -93,7 +97,7 @@ describe("Vault Redeem", function () { it("Should redeem at the expected rate", async () => { const { ousd, vault, dai, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", "2.0"); + await setOracleTokenPriceUsd("RETHETH", reth.address, "2.0"); await reth.setExchangeRate(daiUnits("2.0")); await reth.connect(anna).mint(daiUnits("100.0")); @@ -111,7 +115,7 @@ describe("Vault Redeem", function () { it("Should redeem less at a high oracle", async () => { const { ousd, vault, dai, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", "2.0"); + await setOracleTokenPriceUsd("RETHETH", reth.address, "2.0"); await reth.setExchangeRate(daiUnits("2.0")); await reth.connect(anna).mint(daiUnits("100.0")); @@ -127,7 +131,7 @@ describe("Vault Redeem", function () { // Redeeming $200 == 1/4 vault // 25rETH and 50 DAI - await setOracleTokenPriceUsd("RETHETH", "6.0"); + await setOracleTokenPriceUsd("RETHETH", reth.address, "6.0"); await reth.setExchangeRate(daiUnits("6.0")); await vault.connect(anna).redeem(daiUnits("200.0"), 0); await expect(anna).has.a.balanceOf("25", reth, "RETH"); @@ -137,7 +141,7 @@ describe("Vault Redeem", function () { it("Should redeem same at a low oracle", async () => { const { ousd, vault, dai, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", "2.0"); + await setOracleTokenPriceUsd("RETHETH", reth.address, "2.0"); await reth.setExchangeRate(daiUnits("2.0")); await reth.connect(anna).mint(daiUnits("100.0")); @@ -156,7 +160,7 @@ describe("Vault Redeem", function () { // // And redeeming 200 is 50% of the vault = 50 RETH & 100 DAI - await setOracleTokenPriceUsd("RETHETH", "1.54"); + await setOracleTokenPriceUsd("RETHETH", reth.address, "1.54"); await vault.connect(anna).redeem(daiUnits("200.0"), 0); await expect(anna).has.a.balanceOf("50", reth, "RETH"); await expect(anna).has.a.balanceOf("1100", dai, "USDC"); @@ -165,7 +169,7 @@ describe("Vault Redeem", function () { it("Should redeem same at a low oracle v2", async () => { const { ousd, vault, dai, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", "2.0"); + await setOracleTokenPriceUsd("RETHETH", reth.address, "2.0"); await reth.setExchangeRate(daiUnits("2.0")); await reth.connect(anna).mint(daiUnits("100.0")); @@ -181,7 +185,7 @@ describe("Vault Redeem", function () { // Redeeming $150 == 1/2 vault // 50rETH and 100 DAI - await setOracleTokenPriceUsd("RETHETH", "1.0"); + await setOracleTokenPriceUsd("RETHETH", reth.address, "1.0"); await reth.setExchangeRate(daiUnits("1.0")); await vault.connect(anna).redeem(daiUnits("150.0"), 0); diff --git a/contracts/test/vault/harvester.js b/contracts/test/vault/harvester.js index 234df8245c..acaf6ab477 100644 --- a/contracts/test/vault/harvester.js +++ b/contracts/test/vault/harvester.js @@ -233,7 +233,7 @@ describe("Harvester", function () { .connect(josh) .transfer(mockUniswapRouter.address, usdtUnits("100")); - await setOracleTokenPriceUsd("COMP", "1.0404"); // 1/1.0404 = 0,9611687812 + await setOracleTokenPriceUsd("COMP", comp.address, "1.0404"); // 1/1.0404 = 0,9611687812 await harvester .connect(governor) @@ -279,7 +279,7 @@ describe("Harvester", function () { .connect(josh) .transfer(mockUniswapRouter.address, usdtUnits("100")); - await setOracleTokenPriceUsd("COMP", "1.042"); // 1/1.042 = 0,95969 + await setOracleTokenPriceUsd("COMP", comp.address, "1.042"); // 1/1.042 = 0,95969 await harvester .connect(governor) diff --git a/contracts/test/vault/index.js b/contracts/test/vault/index.js index f93011ef53..3f39b6c3de 100644 --- a/contracts/test/vault/index.js +++ b/contracts/test/vault/index.js @@ -33,6 +33,7 @@ describe("Vault", function () { const origAssetCount = await vault.connect(governor).getAssetCount(); expect(await vault.isSupportedAsset(ousd.address)).to.be.false; await oracleRouter.setFeed(ousd.address, oracleAddresses.chainlink.DAI_USD); + await oracleRouter.cacheDecimals(ousd.address); await expect(vault.connect(governor).supportAsset(ousd.address, 0)).to.emit( vault, "AssetSupported" @@ -97,7 +98,7 @@ describe("Vault", function () { await expect(anna).has.a.balanceOf("0.00", ousd); // We limit to paying to $1 OUSD for for one stable coin, // so this will deposit at a rate of $1. - await setOracleTokenPriceUsd("DAI", "1.30"); + await setOracleTokenPriceUsd("DAI", dai.address, "1.30"); await dai.connect(anna).approve(vault.address, daiUnits("3.0")); await vault.connect(anna).mint(dai.address, daiUnits("3.0"), 0); await expect(anna).has.a.balanceOf("3.00", ousd); @@ -106,7 +107,7 @@ describe("Vault", function () { it("Should correctly handle a deposit of USDC (6 decimals)", async function () { const { ousd, vault, usdc, anna } = await loadFixture(defaultFixture); await expect(anna).has.a.balanceOf("0.00", ousd); - await setOracleTokenPriceUsd("USDC", "0.998"); + await setOracleTokenPriceUsd("USDC", usdc.address, "0.998"); await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); await expect(anna).has.a.balanceOf("49.90", ousd); @@ -115,7 +116,7 @@ describe("Vault", function () { it("Should not allow a below peg deposit", async function () { const { ousd, vault, usdc, anna } = await loadFixture(defaultFixture); await expect(anna).has.a.balanceOf("0.00", ousd); - await setOracleTokenPriceUsd("USDC", "0.95"); + await setOracleTokenPriceUsd("USDC", usdc.address, "0.95"); await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); await expect( vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0) @@ -123,14 +124,17 @@ describe("Vault", function () { }); it("Should correctly handle a deposit failure of Non-Standard ERC20 Token", async function () { - const { ousd, vault, anna, nonStandardToken, governor } = await loadFixture( - defaultFixture - ); + const { ousd, vault, anna, nonStandardToken, oracleRouter, governor } = + await loadFixture(defaultFixture); + 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"); + await setOracleTokenPriceUsd( + "NonStandardToken", + nonStandardToken.address, + "1.30" + ); await nonStandardToken .connect(anna) .approve(vault.address, usdtUnits("1500.0")); @@ -156,13 +160,17 @@ describe("Vault", function () { }); it("Should correctly handle a deposit of Non-Standard ERC20 Token", async function () { - const { ousd, vault, anna, nonStandardToken, governor } = await loadFixture( - defaultFixture - ); + const { ousd, vault, anna, nonStandardToken, oracleRouter, governor } = + await loadFixture(defaultFixture); + 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.00"); + await setOracleTokenPriceUsd( + "NonStandardToken", + nonStandardToken.address, + "1.00" + ); await nonStandardToken .connect(anna) diff --git a/contracts/test/vault/rebase.js b/contracts/test/vault/rebase.js index 3416702bcb..94157faff5 100644 --- a/contracts/test/vault/rebase.js +++ b/contracts/test/vault/rebase.js @@ -69,34 +69,34 @@ describe("Vault rebase pausing", async () => { describe("Vault rebasing", async () => { it("Should not alter balances after an asset price change", async () => { - let { ousd, vault, matt } = await loadFixture(defaultFixture); + let { ousd, vault, dai, matt } = await loadFixture(defaultFixture); await expect(matt).has.a.balanceOf("100.00", ousd); await vault.rebase(); await expect(matt).has.a.balanceOf("100.00", ousd); - await setOracleTokenPriceUsd("DAI", "1.30"); + await setOracleTokenPriceUsd("DAI", dai.address, "1.30"); await vault.rebase(); await expect(matt).has.a.approxBalanceOf("100.00", ousd); - await setOracleTokenPriceUsd("DAI", "1.00"); + await setOracleTokenPriceUsd("DAI", dai.address, "1.00"); await vault.rebase(); await expect(matt).has.a.balanceOf("100.00", ousd); }); it("Should not alter balances after an asset price change, single", async () => { - let { ousd, vault, matt } = await loadFixture(defaultFixture); + let { ousd, vault, matt, dai } = await loadFixture(defaultFixture); await expect(matt).has.a.balanceOf("100.00", ousd); await vault.rebase(); await expect(matt).has.a.balanceOf("100.00", ousd); - await setOracleTokenPriceUsd("DAI", "1.30"); + await setOracleTokenPriceUsd("DAI", dai.address, "1.30"); await vault.rebase(); await expect(matt).has.a.approxBalanceOf("100.00", ousd); - await setOracleTokenPriceUsd("DAI", "1.00"); + await setOracleTokenPriceUsd("DAI", dai.address, "1.00"); await vault.rebase(); await expect(matt).has.a.balanceOf("100.00", ousd); }); it("Should not alter balances after an asset price change with multiple assets", async () => { - let { ousd, vault, matt, usdc } = await loadFixture(defaultFixture); + let { ousd, vault, matt, usdc, dai } = await loadFixture(defaultFixture); await usdc.connect(matt).approve(vault.address, usdcUnits("200")); await vault.connect(matt).mint(usdc.address, usdcUnits("200"), 0); @@ -105,12 +105,12 @@ describe("Vault rebasing", async () => { await vault.rebase(); await expect(matt).has.a.balanceOf("300.00", ousd); - await setOracleTokenPriceUsd("DAI", "1.30"); + await setOracleTokenPriceUsd("DAI", dai.address, "1.30"); await vault.rebase(); expect(await ousd.totalSupply()).to.eq(ousdUnits("400.0")); await expect(matt).has.an.approxBalanceOf("300.00", ousd); - await setOracleTokenPriceUsd("DAI", "1.00"); + await setOracleTokenPriceUsd("DAI", dai.address, "1.00"); await vault.rebase(); expect(await ousd.totalSupply()).to.eq( ousdUnits("400.0"), @@ -195,7 +195,7 @@ describe("Vault rebasing", async () => { const { anna, ousd, usdc, vault } = await loadFixture(defaultFixture); await expect(anna).has.a.balanceOf("0", ousd); // The price should be limited by the code to $1 - await setOracleTokenPriceUsd("USDC", "1.20"); + await setOracleTokenPriceUsd("USDC", usdc.address, "1.20"); await usdc.connect(anna).approve(vault.address, usdcUnits("50")); await vault.connect(anna).mint(usdc.address, usdcUnits("50"), 0); await expect(anna).has.a.balanceOf("50", ousd); diff --git a/contracts/test/vault/redeem.js b/contracts/test/vault/redeem.js index da84bb85a9..f013f7e86f 100644 --- a/contracts/test/vault/redeem.js +++ b/contracts/test/vault/redeem.js @@ -80,7 +80,7 @@ describe("Vault Redeem", function () { await expect(matt).has.a.balanceOf("100.00", ousd); await expect(matt).has.a.balanceOf("900.00", dai); - await setOracleTokenPriceUsd("DAI", "1.25"); + await setOracleTokenPriceUsd("DAI", dai.address, "1.25"); await vault.rebase(); await vault.connect(matt).redeem(ousdUnits("2.0"), 0); @@ -90,13 +90,17 @@ describe("Vault Redeem", function () { }); it("Should allow redeems of non-standard tokens", async () => { - const { ousd, vault, anna, governor, nonStandardToken } = await loadFixture( - defaultFixture - ); + const { ousd, vault, anna, governor, oracleRouter, nonStandardToken } = + await loadFixture(defaultFixture); + await oracleRouter.cacheDecimals(nonStandardToken.address); await vault.connect(governor).supportAsset(nonStandardToken.address, 0); - await setOracleTokenPriceUsd("NonStandardToken", "1.00"); + await setOracleTokenPriceUsd( + "NonStandardToken", + nonStandardToken.address, + "1.00" + ); await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); @@ -202,8 +206,8 @@ describe("Vault Redeem", function () { await vault.connect(anna).mint(dai.address, daiUnits("150.0"), 0); await expect(anna).has.a.balanceOf("250.00", ousd); - await setOracleTokenPriceUsd("USDC", "1.30"); - await setOracleTokenPriceUsd("DAI", "1.20"); + await setOracleTokenPriceUsd("USDC", usdc.address, "1.30"); + await setOracleTokenPriceUsd("DAI", dai.address, "1.20"); await vault.connect(governor).rebase(); // Anna's balance does not change with the rebase @@ -252,8 +256,8 @@ describe("Vault Redeem", function () { await vault.connect(anna).mint(dai.address, daiUnits("150.0"), 0); await expect(anna).has.a.balanceOf("250.00", ousd); - await setOracleTokenPriceUsd("USDC", "0.90"); - await setOracleTokenPriceUsd("DAI", "0.80"); + await setOracleTokenPriceUsd("USDC", usdc.address, "0.90"); + await setOracleTokenPriceUsd("DAI", dai.address, "0.80"); await vault.connect(governor).rebase(); // Anna's share of OUSD is unaffected @@ -342,7 +346,11 @@ describe("Vault Redeem", function () { for (const user of users) { for (const [asset, units] of assetsWithUnits) { for (const price of prices) { - await setOracleTokenPriceUsd(await asset.symbol(), price.toString()); + await setOracleTokenPriceUsd( + await asset.symbol(), + asset.address, + price.toString() + ); // Manually call rebase because not triggered by mint await vault.rebase(); // Rebase could have changed user balance @@ -387,7 +395,7 @@ describe("Vault Redeem", function () { await expect(anna).has.a.balanceOf("3000.00", ousd); //peturb the oracle a slight bit. - await setOracleTokenPriceUsd("USDC", "1.000001"); + await setOracleTokenPriceUsd("USDC", usdc.address, "1.000001"); //redeem without rebasing (not over threshold) await vault.connect(anna).redeem(ousdUnits("200.00"), 0); //redeem with rebasing (over threshold) @@ -409,8 +417,8 @@ describe("Vault Redeem", function () { await expect(anna).has.balanceOf("1000", ousd); await vault.connect(governor).setRedeemFeeBps("500"); - await setOracleTokenPriceUsd("USDC", "1.005"); - await setOracleTokenPriceUsd("DAI", "1"); + await setOracleTokenPriceUsd("USDC", usdc.address, "1.005"); + await setOracleTokenPriceUsd("DAI", dai.address, "1"); await vault.connect(governor).rebase(); await vault.connect(anna).redeemAll(0); From 37009da970514bf61f307344eefc4c2d722b4041 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Sun, 16 Apr 2023 10:23:02 +0200 Subject: [PATCH 2/6] dev oracle doesn't need to cache decimals --- contracts/contracts/oracle/OracleRouter.sol | 13 ++++++++++- contracts/test/helpers.js | 4 +--- contracts/test/oracle/oracle.js | 8 +++---- contracts/test/vault/compound.js | 10 ++++----- contracts/test/vault/exchangeRate.js | 24 ++++++++++----------- contracts/test/vault/harvester.js | 4 ++-- contracts/test/vault/index.js | 8 +++---- contracts/test/vault/rebase.js | 14 ++++++------ contracts/test/vault/redeem.js | 18 +++++++--------- 9 files changed, 53 insertions(+), 50 deletions(-) diff --git a/contracts/contracts/oracle/OracleRouter.sol b/contracts/contracts/oracle/OracleRouter.sol index 34217d46d6..ca2b0c840d 100644 --- a/contracts/contracts/oracle/OracleRouter.sol +++ b/contracts/contracts/oracle/OracleRouter.sol @@ -48,7 +48,7 @@ abstract contract OracleRouterBase is IOracle { return uint256(_price); } - function getDecimals(address _asset) internal view returns (uint8) { + function getDecimals(address _asset) internal virtual view returns (uint8) { uint8 decimals = decimalsCache[_asset]; require(decimals > 0, "Oracle: Decimals not cached"); return decimals; @@ -160,6 +160,17 @@ contract OracleRouterDev is OracleRouterBase { assetToFeed[_asset] = _feed; } + /* + * The dev version of the Oracle doesn't need to gas optimize and cache the decimals + */ + function getDecimals(address _asset) internal override view returns (uint8) { + address _feed = feed(_asset); + require(_feed != address(0), "Asset not available"); + require(_feed != FIXED_PRICE, "Fixed price feeds not supported"); + + return AggregatorV3Interface(_feed).decimals(); + } + /** * @dev The price feed contract to use for a particular asset. * @param asset address of the asset diff --git a/contracts/test/helpers.js b/contracts/test/helpers.js index 3831ff7e91..18bbd0dbbe 100644 --- a/contracts/test/helpers.js +++ b/contracts/test/helpers.js @@ -210,11 +210,10 @@ const getOracleAddress = async (deployments) => { * This first sets the ETH price in USD, then token price in ETH * * @param {string} tokenSymbol: "DAI", USDC", etc... - * @param {string} tokenAddress: 0x.... * @param {number} usdPrice: price of the token in USD. * @returns {Promise} */ -const setOracleTokenPriceUsd = async (tokenSymbol, tokenAddress, usdPrice) => { +const setOracleTokenPriceUsd = async (tokenSymbol, usdPrice) => { if (isMainnetOrFork) { throw new Error( `setOracleTokenPriceUsd not supported on network ${hre.network.name}` @@ -227,7 +226,6 @@ const setOracleTokenPriceUsd = async (tokenSymbol, tokenAddress, usdPrice) => { const oracleRouter = await ethers.getContract("OracleRouter"); await tokenFeed.setDecimals(18); - await oracleRouter.cacheDecimals(tokenAddress); await tokenFeed.setPrice(parseUnits(usdPrice, 18)); }; diff --git a/contracts/test/oracle/oracle.js b/contracts/test/oracle/oracle.js index e3c9a7f71b..5eec26ae90 100644 --- a/contracts/test/oracle/oracle.js +++ b/contracts/test/oracle/oracle.js @@ -23,7 +23,7 @@ describe("Oracle", async () => { ]; for (const test of tests) { const [actual, expectedRead] = test; - await setOracleTokenPriceUsd("USDT", usdt.address, actual); + await setOracleTokenPriceUsd("USDT", actual); expect(await vault.priceUnitMint(usdt.address)).to.equal( ousdUnits(expectedRead) ); @@ -34,7 +34,7 @@ describe("Oracle", async () => { const { vault, usdt } = await loadFixture(defaultFixture); const prices = ["0.85", "0.997"]; for (const price of prices) { - await setOracleTokenPriceUsd("USDT", usdt.address, price); + await setOracleTokenPriceUsd("USDT", price); await expect(vault.priceUnitMint(usdt.address)).to.be.revertedWith( "Asset price below peg" ); @@ -50,7 +50,7 @@ describe("Oracle", async () => { ]; for (const test of tests) { const [actual, expectedRead] = test; - await setOracleTokenPriceUsd("USDT", usdt.address, actual); + await setOracleTokenPriceUsd("USDT", actual); expect(await vault.priceUnitRedeem(usdt.address)).to.equal( ousdUnits(expectedRead) ); @@ -77,7 +77,7 @@ describe("Oracle", async () => { const label = `Should ${revertLabel} because of drift at $${price}`; it(label, async () => { const { vault, usdt } = await loadFixture(defaultFixture); - await setOracleTokenPriceUsd("USDT", usdt.address, price); + await setOracleTokenPriceUsd("USDT", price); if (expectedRevert) { const tx = vault.priceUnitRedeem(usdt.address); await expect(tx).to.be.revertedWith(expectedRevert); diff --git a/contracts/test/vault/compound.js b/contracts/test/vault/compound.js index 7a33b18fca..2c790b980d 100644 --- a/contracts/test/vault/compound.js +++ b/contracts/test/vault/compound.js @@ -104,7 +104,7 @@ describe("Vault with Compound strategy", function () { const { anna, ousd, usdc, vault } = await loadFixture(compoundVaultFixture); await expect(anna).has.a.balanceOf("0", ousd); // The mint process maxes out at a 1.0 price - await setOracleTokenPriceUsd("USDC", usdc.address, "1.25"); + await setOracleTokenPriceUsd("USDC", "1.25"); await usdc.connect(anna).approve(vault.address, usdcUnits("50")); await vault.connect(anna).mint(usdc.address, usdcUnits("50"), 0); await expect(anna).has.a.balanceOf("50", ousd); @@ -402,7 +402,7 @@ describe("Vault with Compound strategy", function () { // 100 + 200 + 200 await expect(matt).has.an.approxBalanceOf("500", ousd, "Initial"); - await setOracleTokenPriceUsd("USDC", usdc.address, "1.30"); + await setOracleTokenPriceUsd("USDC", "1.30"); await vault.rebase(); await expectApproxSupply(ousd, ousdUnits("600.0")); @@ -412,7 +412,7 @@ describe("Vault with Compound strategy", function () { "After some assets double" ); - await setOracleTokenPriceUsd("USDC", usdc.address, "1.00"); + await setOracleTokenPriceUsd("USDC", "1.00"); await vault.rebase(); await expectApproxSupply(ousd, ousdUnits("600.0")); @@ -434,7 +434,6 @@ describe("Vault with Compound strategy", function () { await setOracleTokenPriceUsd( "NonStandardToken", - nonStandardToken.address, "1.00" ); @@ -472,7 +471,6 @@ describe("Vault with Compound strategy", function () { await expect(matt).has.an.approxBalanceOf("200", ousd, "After null rebase"); await setOracleTokenPriceUsd( "NonStandardToken", - nonStandardToken.address, "1.40" ); await vault.rebase(); @@ -761,7 +759,7 @@ describe("Vault with Compound strategy", function () { // Mock router gives 1:1, if we set this to something high there will be // too much slippage - await setOracleTokenPriceUsd("COMP", comp.address, "1.3"); + await setOracleTokenPriceUsd("COMP", "1.3"); const compAmount = utils.parseUnits("100", 18); await comp.connect(governor).mint(compAmount); diff --git a/contracts/test/vault/exchangeRate.js b/contracts/test/vault/exchangeRate.js index 96f4fd2b71..dbbecb5984 100644 --- a/contracts/test/vault/exchangeRate.js +++ b/contracts/test/vault/exchangeRate.js @@ -20,7 +20,7 @@ describe("Vault Redeem", function () { fixture = await loadFixture(defaultFixture); const { vault, reth, governor } = fixture; await vault.connect(governor).supportAsset(reth.address, 1); - await setOracleTokenPriceUsd("RETHETH", reth.address, "1.2"); + await setOracleTokenPriceUsd("RETHETH", "1.2"); }); it("Should mint at a positive exchange rate", async () => { @@ -39,7 +39,7 @@ describe("Vault Redeem", function () { it("Should mint less at low oracle, positive exchange rate", async () => { const { ousd, vault, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", reth.address, "1.199"); + await setOracleTokenPriceUsd("RETHETH", "1.199"); await reth.connect(anna).mint(daiUnits("4.0")); await reth.connect(anna).approve(vault.address, daiUnits("4.0")); await vault.connect(anna).mint(reth.address, daiUnits("4.0"), 0); @@ -49,7 +49,7 @@ describe("Vault Redeem", function () { it("Should revert mint at too low oracle, positive exchange rate", async () => { const { vault, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", reth.address, "1.00"); + await setOracleTokenPriceUsd("RETHETH", "1.00"); await reth.connect(anna).mint(daiUnits("4.0")); await reth.connect(anna).approve(vault.address, daiUnits("4.0")); const tx = vault.connect(anna).mint(reth.address, daiUnits("4.0"), 0); @@ -59,7 +59,7 @@ describe("Vault Redeem", function () { it("Should mint same at high oracle, positive exchange rate", async () => { const { ousd, vault, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", reth.address, "1.2"); + await setOracleTokenPriceUsd("RETHETH", "1.2"); await reth.connect(anna).mint(daiUnits("4.0")); await reth.connect(anna).approve(vault.address, daiUnits("4.0")); await vault.connect(anna).mint(reth.address, daiUnits("4.0"), 0); @@ -82,7 +82,7 @@ describe("Vault Redeem", function () { "afterGift" ); - await setOracleTokenPriceUsd("RETHETH", reth.address, "1.4"); + await setOracleTokenPriceUsd("RETHETH", "1.4"); await reth.setExchangeRate(daiUnits("1.4")); await vault.rebase(); const afterExchangeUp = await ousd.totalSupply(); @@ -97,7 +97,7 @@ describe("Vault Redeem", function () { it("Should redeem at the expected rate", async () => { const { ousd, vault, dai, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", reth.address, "2.0"); + await setOracleTokenPriceUsd("RETHETH", "2.0"); await reth.setExchangeRate(daiUnits("2.0")); await reth.connect(anna).mint(daiUnits("100.0")); @@ -115,7 +115,7 @@ describe("Vault Redeem", function () { it("Should redeem less at a high oracle", async () => { const { ousd, vault, dai, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", reth.address, "2.0"); + await setOracleTokenPriceUsd("RETHETH", "2.0"); await reth.setExchangeRate(daiUnits("2.0")); await reth.connect(anna).mint(daiUnits("100.0")); @@ -131,7 +131,7 @@ describe("Vault Redeem", function () { // Redeeming $200 == 1/4 vault // 25rETH and 50 DAI - await setOracleTokenPriceUsd("RETHETH", reth.address, "6.0"); + await setOracleTokenPriceUsd("RETHETH", "6.0"); await reth.setExchangeRate(daiUnits("6.0")); await vault.connect(anna).redeem(daiUnits("200.0"), 0); await expect(anna).has.a.balanceOf("25", reth, "RETH"); @@ -141,7 +141,7 @@ describe("Vault Redeem", function () { it("Should redeem same at a low oracle", async () => { const { ousd, vault, dai, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", reth.address, "2.0"); + await setOracleTokenPriceUsd("RETHETH", "2.0"); await reth.setExchangeRate(daiUnits("2.0")); await reth.connect(anna).mint(daiUnits("100.0")); @@ -160,7 +160,7 @@ describe("Vault Redeem", function () { // // And redeeming 200 is 50% of the vault = 50 RETH & 100 DAI - await setOracleTokenPriceUsd("RETHETH", reth.address, "1.54"); + await setOracleTokenPriceUsd("RETHETH", "1.54"); await vault.connect(anna).redeem(daiUnits("200.0"), 0); await expect(anna).has.a.balanceOf("50", reth, "RETH"); await expect(anna).has.a.balanceOf("1100", dai, "USDC"); @@ -169,7 +169,7 @@ describe("Vault Redeem", function () { it("Should redeem same at a low oracle v2", async () => { const { ousd, vault, dai, reth, anna } = fixture; - await setOracleTokenPriceUsd("RETHETH", reth.address, "2.0"); + await setOracleTokenPriceUsd("RETHETH", "2.0"); await reth.setExchangeRate(daiUnits("2.0")); await reth.connect(anna).mint(daiUnits("100.0")); @@ -185,7 +185,7 @@ describe("Vault Redeem", function () { // Redeeming $150 == 1/2 vault // 50rETH and 100 DAI - await setOracleTokenPriceUsd("RETHETH", reth.address, "1.0"); + await setOracleTokenPriceUsd("RETHETH", "1.0"); await reth.setExchangeRate(daiUnits("1.0")); await vault.connect(anna).redeem(daiUnits("150.0"), 0); diff --git a/contracts/test/vault/harvester.js b/contracts/test/vault/harvester.js index acaf6ab477..234df8245c 100644 --- a/contracts/test/vault/harvester.js +++ b/contracts/test/vault/harvester.js @@ -233,7 +233,7 @@ describe("Harvester", function () { .connect(josh) .transfer(mockUniswapRouter.address, usdtUnits("100")); - await setOracleTokenPriceUsd("COMP", comp.address, "1.0404"); // 1/1.0404 = 0,9611687812 + await setOracleTokenPriceUsd("COMP", "1.0404"); // 1/1.0404 = 0,9611687812 await harvester .connect(governor) @@ -279,7 +279,7 @@ describe("Harvester", function () { .connect(josh) .transfer(mockUniswapRouter.address, usdtUnits("100")); - await setOracleTokenPriceUsd("COMP", comp.address, "1.042"); // 1/1.042 = 0,95969 + await setOracleTokenPriceUsd("COMP", "1.042"); // 1/1.042 = 0,95969 await harvester .connect(governor) diff --git a/contracts/test/vault/index.js b/contracts/test/vault/index.js index 3f39b6c3de..ebb230ddfc 100644 --- a/contracts/test/vault/index.js +++ b/contracts/test/vault/index.js @@ -98,7 +98,7 @@ describe("Vault", function () { await expect(anna).has.a.balanceOf("0.00", ousd); // We limit to paying to $1 OUSD for for one stable coin, // so this will deposit at a rate of $1. - await setOracleTokenPriceUsd("DAI", dai.address, "1.30"); + await setOracleTokenPriceUsd("DAI", "1.30"); await dai.connect(anna).approve(vault.address, daiUnits("3.0")); await vault.connect(anna).mint(dai.address, daiUnits("3.0"), 0); await expect(anna).has.a.balanceOf("3.00", ousd); @@ -107,7 +107,7 @@ describe("Vault", function () { it("Should correctly handle a deposit of USDC (6 decimals)", async function () { const { ousd, vault, usdc, anna } = await loadFixture(defaultFixture); await expect(anna).has.a.balanceOf("0.00", ousd); - await setOracleTokenPriceUsd("USDC", usdc.address, "0.998"); + await setOracleTokenPriceUsd("USDC", "0.998"); await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); await expect(anna).has.a.balanceOf("49.90", ousd); @@ -116,7 +116,7 @@ describe("Vault", function () { it("Should not allow a below peg deposit", async function () { const { ousd, vault, usdc, anna } = await loadFixture(defaultFixture); await expect(anna).has.a.balanceOf("0.00", ousd); - await setOracleTokenPriceUsd("USDC", usdc.address, "0.95"); + await setOracleTokenPriceUsd("USDC", "0.95"); await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); await expect( vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0) @@ -132,7 +132,6 @@ describe("Vault", function () { await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); await setOracleTokenPriceUsd( "NonStandardToken", - nonStandardToken.address, "1.30" ); await nonStandardToken @@ -168,7 +167,6 @@ describe("Vault", function () { await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); await setOracleTokenPriceUsd( "NonStandardToken", - nonStandardToken.address, "1.00" ); diff --git a/contracts/test/vault/rebase.js b/contracts/test/vault/rebase.js index 94157faff5..91b80dd70e 100644 --- a/contracts/test/vault/rebase.js +++ b/contracts/test/vault/rebase.js @@ -73,11 +73,11 @@ describe("Vault rebasing", async () => { await expect(matt).has.a.balanceOf("100.00", ousd); await vault.rebase(); await expect(matt).has.a.balanceOf("100.00", ousd); - await setOracleTokenPriceUsd("DAI", dai.address, "1.30"); + await setOracleTokenPriceUsd("DAI", "1.30"); await vault.rebase(); await expect(matt).has.a.approxBalanceOf("100.00", ousd); - await setOracleTokenPriceUsd("DAI", dai.address, "1.00"); + await setOracleTokenPriceUsd("DAI", "1.00"); await vault.rebase(); await expect(matt).has.a.balanceOf("100.00", ousd); }); @@ -87,10 +87,10 @@ describe("Vault rebasing", async () => { await expect(matt).has.a.balanceOf("100.00", ousd); await vault.rebase(); await expect(matt).has.a.balanceOf("100.00", ousd); - await setOracleTokenPriceUsd("DAI", dai.address, "1.30"); + await setOracleTokenPriceUsd("DAI", "1.30"); await vault.rebase(); await expect(matt).has.a.approxBalanceOf("100.00", ousd); - await setOracleTokenPriceUsd("DAI", dai.address, "1.00"); + await setOracleTokenPriceUsd("DAI", "1.00"); await vault.rebase(); await expect(matt).has.a.balanceOf("100.00", ousd); }); @@ -105,12 +105,12 @@ describe("Vault rebasing", async () => { await vault.rebase(); await expect(matt).has.a.balanceOf("300.00", ousd); - await setOracleTokenPriceUsd("DAI", dai.address, "1.30"); + await setOracleTokenPriceUsd("DAI", "1.30"); await vault.rebase(); expect(await ousd.totalSupply()).to.eq(ousdUnits("400.0")); await expect(matt).has.an.approxBalanceOf("300.00", ousd); - await setOracleTokenPriceUsd("DAI", dai.address, "1.00"); + await setOracleTokenPriceUsd("DAI", "1.00"); await vault.rebase(); expect(await ousd.totalSupply()).to.eq( ousdUnits("400.0"), @@ -195,7 +195,7 @@ describe("Vault rebasing", async () => { const { anna, ousd, usdc, vault } = await loadFixture(defaultFixture); await expect(anna).has.a.balanceOf("0", ousd); // The price should be limited by the code to $1 - await setOracleTokenPriceUsd("USDC", usdc.address, "1.20"); + await setOracleTokenPriceUsd("USDC", "1.20"); await usdc.connect(anna).approve(vault.address, usdcUnits("50")); await vault.connect(anna).mint(usdc.address, usdcUnits("50"), 0); await expect(anna).has.a.balanceOf("50", ousd); diff --git a/contracts/test/vault/redeem.js b/contracts/test/vault/redeem.js index f013f7e86f..eb0b2a83fb 100644 --- a/contracts/test/vault/redeem.js +++ b/contracts/test/vault/redeem.js @@ -80,7 +80,7 @@ describe("Vault Redeem", function () { await expect(matt).has.a.balanceOf("100.00", ousd); await expect(matt).has.a.balanceOf("900.00", dai); - await setOracleTokenPriceUsd("DAI", dai.address, "1.25"); + await setOracleTokenPriceUsd("DAI", "1.25"); await vault.rebase(); await vault.connect(matt).redeem(ousdUnits("2.0"), 0); @@ -98,7 +98,6 @@ describe("Vault Redeem", function () { await setOracleTokenPriceUsd( "NonStandardToken", - nonStandardToken.address, "1.00" ); @@ -206,8 +205,8 @@ describe("Vault Redeem", function () { await vault.connect(anna).mint(dai.address, daiUnits("150.0"), 0); await expect(anna).has.a.balanceOf("250.00", ousd); - await setOracleTokenPriceUsd("USDC", usdc.address, "1.30"); - await setOracleTokenPriceUsd("DAI", dai.address, "1.20"); + await setOracleTokenPriceUsd("USDC", "1.30"); + await setOracleTokenPriceUsd("DAI", "1.20"); await vault.connect(governor).rebase(); // Anna's balance does not change with the rebase @@ -256,8 +255,8 @@ describe("Vault Redeem", function () { await vault.connect(anna).mint(dai.address, daiUnits("150.0"), 0); await expect(anna).has.a.balanceOf("250.00", ousd); - await setOracleTokenPriceUsd("USDC", usdc.address, "0.90"); - await setOracleTokenPriceUsd("DAI", dai.address, "0.80"); + await setOracleTokenPriceUsd("USDC", "0.90"); + await setOracleTokenPriceUsd("DAI", "0.80"); await vault.connect(governor).rebase(); // Anna's share of OUSD is unaffected @@ -348,7 +347,6 @@ describe("Vault Redeem", function () { for (const price of prices) { await setOracleTokenPriceUsd( await asset.symbol(), - asset.address, price.toString() ); // Manually call rebase because not triggered by mint @@ -395,7 +393,7 @@ describe("Vault Redeem", function () { await expect(anna).has.a.balanceOf("3000.00", ousd); //peturb the oracle a slight bit. - await setOracleTokenPriceUsd("USDC", usdc.address, "1.000001"); + await setOracleTokenPriceUsd("USDC", "1.000001"); //redeem without rebasing (not over threshold) await vault.connect(anna).redeem(ousdUnits("200.00"), 0); //redeem with rebasing (over threshold) @@ -417,8 +415,8 @@ describe("Vault Redeem", function () { await expect(anna).has.balanceOf("1000", ousd); await vault.connect(governor).setRedeemFeeBps("500"); - await setOracleTokenPriceUsd("USDC", usdc.address, "1.005"); - await setOracleTokenPriceUsd("DAI", dai.address, "1"); + await setOracleTokenPriceUsd("USDC", "1.005"); + await setOracleTokenPriceUsd("DAI", "1"); await vault.connect(governor).rebase(); await vault.connect(anna).redeemAll(0); From fd1af23c344119053c83e00546d3ee0a00afda0a Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Sun, 16 Apr 2023 10:34:44 +0200 Subject: [PATCH 3/6] prettier --- contracts/contracts/oracle/OracleRouter.sol | 9 +++++++-- contracts/contracts/utils/StableMath.sol | 1 + contracts/test/vault/compound.js | 10 ++-------- contracts/test/vault/index.js | 10 ++-------- contracts/test/vault/redeem.js | 10 ++-------- 5 files changed, 14 insertions(+), 26 deletions(-) diff --git a/contracts/contracts/oracle/OracleRouter.sol b/contracts/contracts/oracle/OracleRouter.sol index ca2b0c840d..daeac71307 100644 --- a/contracts/contracts/oracle/OracleRouter.sol +++ b/contracts/contracts/oracle/OracleRouter.sol @@ -48,7 +48,7 @@ abstract contract OracleRouterBase is IOracle { return uint256(_price); } - function getDecimals(address _asset) internal virtual view returns (uint8) { + function getDecimals(address _asset) internal view virtual returns (uint8) { uint8 decimals = decimalsCache[_asset]; require(decimals > 0, "Oracle: Decimals not cached"); return decimals; @@ -163,7 +163,12 @@ contract OracleRouterDev is OracleRouterBase { /* * The dev version of the Oracle doesn't need to gas optimize and cache the decimals */ - function getDecimals(address _asset) internal override view returns (uint8) { + function getDecimals(address _asset) + internal + view + override + returns (uint8) + { address _feed = feed(_asset); require(_feed != address(0), "Asset not available"); require(_feed != FIXED_PRICE, "Fixed price feeds not supported"); diff --git a/contracts/contracts/utils/StableMath.sol b/contracts/contracts/utils/StableMath.sol index 625a09538c..5dc19d2960 100644 --- a/contracts/contracts/utils/StableMath.sol +++ b/contracts/contracts/utils/StableMath.sol @@ -32,6 +32,7 @@ library StableMath { if (to > from) { x = x.mul(10**(to - from)); } else if (to < from) { + // slither-disable-next-line divide-before-multiply x = x.div(10**(from - to)); } return x; diff --git a/contracts/test/vault/compound.js b/contracts/test/vault/compound.js index 2c790b980d..76013ef933 100644 --- a/contracts/test/vault/compound.js +++ b/contracts/test/vault/compound.js @@ -432,10 +432,7 @@ describe("Vault with Compound strategy", function () { await vault.connect(governor).supportAsset(nonStandardToken.address, 0); } - await setOracleTokenPriceUsd( - "NonStandardToken", - "1.00" - ); + await setOracleTokenPriceUsd("NonStandardToken", "1.00"); await nonStandardToken .connect(matt) @@ -469,10 +466,7 @@ describe("Vault with Compound strategy", function () { await expect(matt).has.an.approxBalanceOf("200", ousd, "Initial"); await vault.rebase(); await expect(matt).has.an.approxBalanceOf("200", ousd, "After null rebase"); - await setOracleTokenPriceUsd( - "NonStandardToken", - "1.40" - ); + await setOracleTokenPriceUsd("NonStandardToken", "1.40"); await vault.rebase(); await expectApproxSupply(ousd, ousdUnits("300.0")); diff --git a/contracts/test/vault/index.js b/contracts/test/vault/index.js index ebb230ddfc..0f34cb2ba4 100644 --- a/contracts/test/vault/index.js +++ b/contracts/test/vault/index.js @@ -130,10 +130,7 @@ describe("Vault", function () { 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" - ); + await setOracleTokenPriceUsd("NonStandardToken", "1.30"); await nonStandardToken .connect(anna) .approve(vault.address, usdtUnits("1500.0")); @@ -165,10 +162,7 @@ describe("Vault", function () { await vault.connect(governor).supportAsset(nonStandardToken.address, 0); await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); - await setOracleTokenPriceUsd( - "NonStandardToken", - "1.00" - ); + await setOracleTokenPriceUsd("NonStandardToken", "1.00"); await nonStandardToken .connect(anna) diff --git a/contracts/test/vault/redeem.js b/contracts/test/vault/redeem.js index eb0b2a83fb..7b4c282cff 100644 --- a/contracts/test/vault/redeem.js +++ b/contracts/test/vault/redeem.js @@ -96,10 +96,7 @@ describe("Vault Redeem", function () { await oracleRouter.cacheDecimals(nonStandardToken.address); await vault.connect(governor).supportAsset(nonStandardToken.address, 0); - await setOracleTokenPriceUsd( - "NonStandardToken", - "1.00" - ); + await setOracleTokenPriceUsd("NonStandardToken", "1.00"); await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); @@ -345,10 +342,7 @@ describe("Vault Redeem", function () { for (const user of users) { for (const [asset, units] of assetsWithUnits) { for (const price of prices) { - await setOracleTokenPriceUsd( - await asset.symbol(), - price.toString() - ); + await setOracleTokenPriceUsd(await asset.symbol(), price.toString()); // Manually call rebase because not triggered by mint await vault.rebase(); // Rebase could have changed user balance From 6380da10d17cc1364e594caa61cb824511f24cc6 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Sun, 16 Apr 2023 10:35:21 +0200 Subject: [PATCH 4/6] improve code readability --- contracts/contracts/harvest/Harvester.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/contracts/contracts/harvest/Harvester.sol b/contracts/contracts/harvest/Harvester.sol index 7d5168c953..f64bf9f065 100644 --- a/contracts/contracts/harvest/Harvester.sol +++ b/contracts/contracts/harvest/Harvester.sol @@ -385,11 +385,10 @@ contract Harvester is Governable { // Oracle price is 1e18, USDT output is 1e6 uint256 minExpected = (balanceToSwap * - oraclePrice * - (1e4 - tokenConfig.allowedSlippageBps)).scaleBy( // max allowed slippage - 6, - Helpers.getDecimals(_swapToken) + 18 - ) / 1e4; // fix the max slippage decimal position + (1e4 - tokenConfig.allowedSlippageBps) * // max allowed slippage + oraclePrice).scaleBy(6, Helpers.getDecimals(_swapToken)) / + 1e4 / // fix the max slippage decimal position + 1e18; // and oracle price decimals position // Uniswap redemption path address[] memory path = new address[](3); From 907f2f249895f792bffa8495c9192d7f27851ff7 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Sun, 16 Apr 2023 10:41:18 +0200 Subject: [PATCH 5/6] support different decimal formats in unit tests --- contracts/test/helpers.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/contracts/test/helpers.js b/contracts/test/helpers.js index 18bbd0dbbe..6730681969 100644 --- a/contracts/test/helpers.js +++ b/contracts/test/helpers.js @@ -214,6 +214,15 @@ const getOracleAddress = async (deployments) => { * @returns {Promise} */ const setOracleTokenPriceUsd = async (tokenSymbol, usdPrice) => { + const symbolMap = { + USDC: 6, + USDT: 6, + DAI: 6, + COMP: 6, + CVX: 6, + CRV: 6, + }; + if (isMainnetOrFork) { throw new Error( `setOracleTokenPriceUsd not supported on network ${hre.network.name}` @@ -225,6 +234,9 @@ const setOracleTokenPriceUsd = async (tokenSymbol, usdPrice) => { ); const oracleRouter = await ethers.getContract("OracleRouter"); + const decimals = Object.keys(symbolMap).includes(tokenSymbol) + ? symbolMap[tokenSymbol] + : 18; await tokenFeed.setDecimals(18); await tokenFeed.setPrice(parseUnits(usdPrice, 18)); }; From 7a02455d9bbd31c5cc70eac8099a608ab22c90f1 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Sun, 16 Apr 2023 10:43:18 +0200 Subject: [PATCH 6/6] prettier & bug fix --- contracts/test/helpers.js | 5 ++--- contracts/test/vault/rebase.js | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/contracts/test/helpers.js b/contracts/test/helpers.js index 6730681969..44ec574cbf 100644 --- a/contracts/test/helpers.js +++ b/contracts/test/helpers.js @@ -232,13 +232,12 @@ const setOracleTokenPriceUsd = async (tokenSymbol, usdPrice) => { const tokenFeed = await ethers.getContract( "MockChainlinkOracleFeed" + tokenSymbol ); - const oracleRouter = await ethers.getContract("OracleRouter"); const decimals = Object.keys(symbolMap).includes(tokenSymbol) ? symbolMap[tokenSymbol] : 18; - await tokenFeed.setDecimals(18); - await tokenFeed.setPrice(parseUnits(usdPrice, 18)); + await tokenFeed.setDecimals(decimals); + await tokenFeed.setPrice(parseUnits(usdPrice, decimals)); }; const getOracleAddresses = async (deployments) => { diff --git a/contracts/test/vault/rebase.js b/contracts/test/vault/rebase.js index 91b80dd70e..3416702bcb 100644 --- a/contracts/test/vault/rebase.js +++ b/contracts/test/vault/rebase.js @@ -69,7 +69,7 @@ describe("Vault rebase pausing", async () => { describe("Vault rebasing", async () => { it("Should not alter balances after an asset price change", async () => { - let { ousd, vault, dai, matt } = await loadFixture(defaultFixture); + let { ousd, vault, matt } = await loadFixture(defaultFixture); await expect(matt).has.a.balanceOf("100.00", ousd); await vault.rebase(); await expect(matt).has.a.balanceOf("100.00", ousd); @@ -83,7 +83,7 @@ describe("Vault rebasing", async () => { }); it("Should not alter balances after an asset price change, single", async () => { - let { ousd, vault, matt, dai } = await loadFixture(defaultFixture); + let { ousd, vault, matt } = await loadFixture(defaultFixture); await expect(matt).has.a.balanceOf("100.00", ousd); await vault.rebase(); await expect(matt).has.a.balanceOf("100.00", ousd); @@ -96,7 +96,7 @@ describe("Vault rebasing", async () => { }); it("Should not alter balances after an asset price change with multiple assets", async () => { - let { ousd, vault, matt, usdc, dai } = await loadFixture(defaultFixture); + let { ousd, vault, matt, usdc } = await loadFixture(defaultFixture); await usdc.connect(matt).approve(vault.address, usdcUnits("200")); await vault.connect(matt).mint(usdc.address, usdcUnits("200"), 0);