diff --git a/contracts/contracts/harvest/Harvester.sol b/contracts/contracts/harvest/Harvester.sol index 7d5d40d04d..f64bf9f065 100644 --- a/contracts/contracts/harvest/Harvester.sol +++ b/contracts/contracts/harvest/Harvester.sol @@ -382,13 +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 - ) / 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); diff --git a/contracts/contracts/oracle/OracleRouter.sol b/contracts/contracts/oracle/OracleRouter.sol index 5a897abee1..daeac71307 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 virtual returns (uint8) { uint8 decimals = decimalsCache[_asset]; require(decimals > 0, "Oracle: Decimals not cached"); return decimals; @@ -164,6 +160,22 @@ 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 + view + override + 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/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/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..44ec574cbf 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}` @@ -223,8 +232,12 @@ const setOracleTokenPriceUsd = async (tokenSymbol, usdPrice) => { const tokenFeed = await ethers.getContract( "MockChainlinkOracleFeed" + tokenSymbol ); - await tokenFeed.setDecimals(8); - await tokenFeed.setPrice(parseUnits(usdPrice, 8)); + + const decimals = Object.keys(symbolMap).includes(tokenSymbol) + ? symbolMap[tokenSymbol] + : 18; + await tokenFeed.setDecimals(decimals); + await tokenFeed.setPrice(parseUnits(usdPrice, decimals)); }; const getOracleAddresses = async (deployments) => { diff --git a/contracts/test/vault/compound.js b/contracts/test/vault/compound.js index 4cbf9e848e..76013ef933 100644 --- a/contracts/test/vault/compound.js +++ b/contracts/test/vault/compound.js @@ -424,10 +424,10 @@ 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); } diff --git a/contracts/test/vault/exchangeRate.js b/contracts/test/vault/exchangeRate.js index 32813ef7e1..dbbecb5984 100644 --- a/contracts/test/vault/exchangeRate.js +++ b/contracts/test/vault/exchangeRate.js @@ -24,8 +24,12 @@ describe("Vault Redeem", function () { }); 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); diff --git a/contracts/test/vault/index.js b/contracts/test/vault/index.js index f93011ef53..0f34cb2ba4 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" @@ -123,12 +124,11 @@ 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 nonStandardToken @@ -156,9 +156,9 @@ 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); diff --git a/contracts/test/vault/redeem.js b/contracts/test/vault/redeem.js index da84bb85a9..7b4c282cff 100644 --- a/contracts/test/vault/redeem.js +++ b/contracts/test/vault/redeem.js @@ -90,10 +90,10 @@ 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");