From 19f7d15c6f8c97dcf30cf0cd684f184f810dc6b1 Mon Sep 17 00:00:00 2001 From: AlienTornadosaurusHex <> Date: Thu, 8 Jun 2023 23:21:32 +0000 Subject: [PATCH] Write proposals logic Signed-off-by: AlienTornadosaurusHex <> --- foundry.toml | 3 +- src/common/AdminUpgradeableProxy.sol | 25 ++ src/proposals/CRVUSDInstancesProposal.sol | 162 ++++++++++++ .../InfrastructureUpgradeProposal.sol | 238 ++++++++++++++++++ src/v2/FeeOracleManager.sol | 27 +- 5 files changed, 443 insertions(+), 12 deletions(-) create mode 100644 src/common/AdminUpgradeableProxy.sol create mode 100644 src/proposals/CRVUSDInstancesProposal.sol create mode 100644 src/proposals/InfrastructureUpgradeProposal.sol diff --git a/foundry.toml b/foundry.toml index 0f34363..0b46624 100644 --- a/foundry.toml +++ b/foundry.toml @@ -41,5 +41,6 @@ ignore = [ "./src/v1/libraries/*", "./src/v1/staking/*", "./src/v1/tornado-proxy/*", - "./src/v1/utils/*" + "./src/v1/utils/*", + "./src/common/AdminUpgradeableProxy.sol" ] \ No newline at end of file diff --git a/src/common/AdminUpgradeableProxy.sol b/src/common/AdminUpgradeableProxy.sol new file mode 100644 index 0000000..aeb129f --- /dev/null +++ b/src/common/AdminUpgradeableProxy.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.12; + +import "@openzeppelin/contracts/proxy/TransparentUpgradeableProxy.sol"; + +/** + * @dev TransparentUpgradeableProxy where admin is allowed to call implementation methods. + */ +contract AdminUpgradeableProxy is TransparentUpgradeableProxy { + /** + * @dev Initializes an upgradeable proxy backed by the implementation at `_logic`. + */ + constructor( + address _logic, + address _admin, + bytes memory _data + ) public payable TransparentUpgradeableProxy(_logic, _admin, _data) {} + + /** + * @dev Override to allow admin access the fallback function. + */ + function _beforeFallback() internal override {} +} + diff --git a/src/proposals/CRVUSDInstancesProposal.sol b/src/proposals/CRVUSDInstancesProposal.sol new file mode 100644 index 0000000..0257847 --- /dev/null +++ b/src/proposals/CRVUSDInstancesProposal.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.12; +pragma experimental ABIEncoderV2; + +// OZ Imports + +import { AdminUpgradeableProxy } from "../common/AdminUpgradeableProxy.sol"; + +// Tornado imports + +import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/ITornadoInstance.sol"; + +// Local V2 imports + +import { FeeOracleManager } from "../v2/FeeOracleManager.sol"; + +import { InstanceRegistry } from "../v2/InstanceRegistry.sol"; + +import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "../v2/CurveFeeOracle.sol"; + +import { UniswapV3FeeOracle } from "../v2/UniswapV3FeeOracle.sol"; + +import { TornadoRouter } from "../v2/TornadoRouter.sol"; + +/** + * @title CRVUSDInstancesProposal + * @author AlienTornadosaurusHex + * @notice Proposal to add crvUSD instances in 100, 1000, 10000, 100000, 1000000 denominations. + */ +contract CRVUSDInstancesProposal { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + /* @dev The first Curve pool which is capable of being used as a safe oracle due to analytical EMA price */ + address public constant crvUSDUSDCStableswap2Pool = 0x4DEcE678ceceb27446b35C672dC7d61F30bAD69E; + + /* @dev The second Curve pool which is capable of being used as a safe oracle due to analytical EMA price */ + address public constant tricryptoUSDCPool = 0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B; + + /* @dev The InstanceRegistry contract, for registering instances, proxy must be upgraded */ + InstanceRegistry public constant instanceRegistry = + InstanceRegistry(0xB20c66C4DE72433F3cE747b58B86830c459CA911); + + /* @dev The FeeOracleManager contract, proxy must be upgraded */ + FeeOracleManager public constant feeOracleManager = + FeeOracleManager(0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7); + + /* @dev This is the address of the Uniswap V3 Oracle which we will use for all of our traditional + instances, but it will also help the Curve instances, the former must have been deployed with the address + of this */ + address public immutable deployedUniswapV3FeeOracleAddress; + + /* @dev This is the CurveFeeOracle contract which will be deployed beforehand and which will be able to + use multiple Curve pools as oracles, at once. */ + CurveFeeOracle public immutable curveFeeOracle; + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + constructor(address _deployedCurveFeeOracleAddress, address _deployedUniswapV3FeeOracleAddress) public { + curveFeeOracle = CurveFeeOracle(_deployedCurveFeeOracleAddress); + deployedUniswapV3FeeOracleAddress = _deployedUniswapV3FeeOracleAddress; + } + + /** + * @dev This function also executes further internal functions inlined below. + */ + function executeProposal() external { + // Load the deployed crvUSD instances + + ITornadoInstance cu100 = ITornadoInstance(0x913a73486Dc4AA3832A56d461542836C1eeB93be); + ITornadoInstance cu1_000 = ITornadoInstance(0x5A6b3C829dB3e938C885000c6E93CF35E74876a4); + ITornadoInstance cu10_000 = ITornadoInstance(0x49f173CDAB99a2C3800F1255393DF9B7a17B82Bb); + ITornadoInstance cu100_000 = ITornadoInstance(0x4640Dffc9fD0B113B983e3A350b070a119CA143C); + ITornadoInstance cu1_000_000 = ITornadoInstance(0xc4eA8Bd3Fd76f3c255395793B47F7c55aD59d991); + + // Ok, first add the Uniswap V3 Oracle to the contract + + curveFeeOracle.setUniswapV3FeeOracle(UniswapV3FeeOracle(deployedUniswapV3FeeOracleAddress)); + + // Then, add necessary oracles for the CRVUSD price, to the CurveFeeOracle + + _setCurveFeeChainedOracleForInstance(curveFeeOracle, cu100); + _setCurveFeeChainedOracleForInstance(curveFeeOracle, cu1_000); + _setCurveFeeChainedOracleForInstance(curveFeeOracle, cu10_000); + _setCurveFeeChainedOracleForInstance(curveFeeOracle, cu100_000); + _setCurveFeeChainedOracleForInstance(curveFeeOracle, cu1_000_000); + + // Then, add the instances to the InstanceRegistry + + instanceRegistry.addInstance(cu100); + instanceRegistry.addInstance(cu1_000); + instanceRegistry.addInstance(cu10_000); + instanceRegistry.addInstance(cu100_000); + instanceRegistry.addInstance(cu1_000_000); + + // Finally, set all necessary data in the FeeOracleManager + + feeOracleManager.setFeeOracle(address(cu100), address(curveFeeOracle)); + feeOracleManager.setFeeOracle(address(cu1_000), address(curveFeeOracle)); + feeOracleManager.setFeeOracle(address(cu10_000), address(curveFeeOracle)); + feeOracleManager.setFeeOracle(address(cu100_000), address(curveFeeOracle)); + feeOracleManager.setFeeOracle(address(cu1_000_000), address(curveFeeOracle)); + + // The fee will be 0.3 % for those pools for which it makes sense + + feeOracleManager.setFeePercentForInstance(cu100, 0); + feeOracleManager.setFeePercentForInstance(cu1_000, 0); + feeOracleManager.setFeePercentForInstance(cu10_000, 30); + feeOracleManager.setFeePercentForInstance(cu100_000, 30); + feeOracleManager.setFeePercentForInstance(cu1_000_000, 30); + } + + /** + * @dev This function adds pools as fee oracles to the main curve fee oracle contract. The oracles are + * chained, this means that multiple pools can be used as one oracle using the library which can be found + * in the contract file. This means: + * + * (...pool contracts as oracles) ===(`price_oracle`)===> CurveFeeOracle ===(`getFee()`)===> + * FeeOracleManager ===(`instanceFeeWithUpdate`)===> RelayerRegistry + */ + function _setCurveFeeChainedOracleForInstance(CurveFeeOracle feeOracle, ITornadoInstance _instance) + internal + { + // Add the oracles which are the USDC/CRVUSD stableswap and tricryptoUSDC pools + + ICurvePriceOracle[] memory _oracles = new ICurvePriceOracle[](2); + + _oracles[0] = ICurvePriceOracle(crvUSDUSDCStableswap2Pool); + _oracles[1] = ICurvePriceOracle(tricryptoUSDCPool); + + // Add the selectors, so which functions to call because tricrypto pools supports getting prices for + // multiple tokens, denominated in the first token, which is usually a stable + + bytes4[] memory _selectors = new bytes4[](2); + + _selectors[0] = CurveChainedOracles.PRICE_ORACLE_SELECTOR; + _selectors[1] = CurveChainedOracles.PRICE_ORACLE_UINT256_SELECTOR; + + // Specify for the second that the ether price must be retrieved + + uint8[] memory _coins = new uint8[](2); + + _coins[1] = 1; // ETHER + + // In order to receive the CRVUSD price, its price in USDC must be read out (the USDC price in crvUSD + // is worthless because we do not have a common denominator then) and then the second price must be + // inverted to receive the accurate ETH/CRVUSD price, meaning ETH per CRVUSD, which will then be + // divided by ETH per TORN, and then (ETH/CRVUSD)/(ETH/TORN) = (ETH/CRVUSD)*(TORN/ETH) = (TORN/CRVUSD) + // which is the price that the oracle should be supplying + + bool[] memory _invert = new bool[](2); + + _invert[0] = false; // DO NOT INVERT, PRICE IS CRVUSD IN USDC + _invert[1] = true; // INVERT, PRICE IS ETH IN USDC, BUT WE NEED USDC IN ETH + + // (USDC/CRVUSD)*(ETH/USDC) = (ETH/CRVUSD) + + feeOracle.modifyChainedOracleForInstance( + _instance, _oracles, _selectors, _coins, _invert, "ETH/CRVUSD" + ); + } +} diff --git a/src/proposals/InfrastructureUpgradeProposal.sol b/src/proposals/InfrastructureUpgradeProposal.sol new file mode 100644 index 0000000..1b10b8c --- /dev/null +++ b/src/proposals/InfrastructureUpgradeProposal.sol @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.12; +pragma experimental ABIEncoderV2; + +// OZ Imports + +import { AdminUpgradeableProxy } from "../common/AdminUpgradeableProxy.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +// Tornado imports + +import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/ITornadoInstance.sol"; + +// Local V2 imports + +import { FeeOracleManager } from "../v2/FeeOracleManager.sol"; + +import { InstanceRegistry } from "../v2/InstanceRegistry.sol"; + +import { UniswapV3FeeOracle } from "../v2/UniswapV3FeeOracle.sol"; + +import { TornadoRouter } from "../v2/TornadoRouter.sol"; + +/** + * @title InfrastructureUpgradeProposal + * @author AlienTornadosaurusHex + * @notice Proposal which will upgrade only the SURROUNDING infrastrucure in connection with the relayer + * registry such that multiple oracles can be used and so the DAO can add more instances. + */ +contract InfrastructureUpgradeProposal { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + /* @dev The address of the current FeeManager proxy, future FeeOracleManager */ + address payable public constant feeManagerProxyAddress = 0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7; + + /* @dev The address of the current InstanceRegistry proxy, this will also be upgraded */ + address payable public constant instanceRegistryProxyAddress = 0xB20c66C4DE72433F3cE747b58B86830c459CA911; + + /* @dev This is the Uniswap V3 Oracle which we will use for all of our traditional instances, but it will + also help the CurveFeeOracle, the former must have been deployed witht the address of this */ + address public immutable deployedUniswapV3FeeOracleAddress; + + /* @dev The implementation address of the FeeManager upgrade contract */ + address public immutable deployedFeeOracleManagerImplementationAddress; + + /* @dev The implementation address of the InstanceRegistry upgrade contract */ + address public immutable deployedInstanceRegistryImplementationAddress; + + /* @dev The address of the new, cleaner, better TornadoRouter */ + address public immutable deployedTornadoRouterAddress; + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + constructor( + address _deployedUniswapV3FeeOracleAddress, + address _deployedFeeOracleManagerImplementationAddress, + address _deployedInstanceRegistryImplementationAddress, + address _deployedTornadoRouterAddress + ) public { + deployedUniswapV3FeeOracleAddress = _deployedUniswapV3FeeOracleAddress; + deployedFeeOracleManagerImplementationAddress = _deployedFeeOracleManagerImplementationAddress; + deployedInstanceRegistryImplementationAddress = _deployedInstanceRegistryImplementationAddress; + deployedTornadoRouterAddress = _deployedTornadoRouterAddress; + } + + /** + * @dev This function also executes further internal functions inlined below. + */ + function executeProposal() external { + // Upgrade FeeManager (V1) Proxy to FeeOracleManager (V2) + + AdminUpgradeableProxy(feeManagerProxyAddress).upgradeTo(deployedFeeOracleManagerImplementationAddress); + + // Now initialize the FeeOracleManager immediately, this initialization will record the old legacy + // data in combination with the new oracle (which returns a fee value compatible with legacy) and in + // accordance with the new data structures being used, which are more logical and segmented than what + // the original implementation was using. The data which is used in the internal functions was read + // out from the on-chain instance registry. + + FeeOracleManager(feeManagerProxyAddress).initialize( + deployedUniswapV3FeeOracleAddress, + instanceRegistryProxyAddress, + _getAllInstances(), + _getAllInstanceFeePercents() + ); + + // Upgrade InstanceRegistry (V1) Proxy to InstanceRegistry (V2) + + AdminUpgradeableProxy(instanceRegistryProxyAddress).upgradeTo( + deployedInstanceRegistryImplementationAddress + ); + + // Initialize this one too, we are abandoning all of the older data here because either the data + // structures don't fit or we can't do a clean addition of all instances, it is better to add the + // instances as fresh data because it's anyways a simple contract, it just stores some basic data on + // instances which isn't mutable for them, instead only determined for each. The new Tornado Router is + // also set. + + InstanceRegistry(instanceRegistryProxyAddress).initialize( + _getAllInstances(), TornadoRouter(deployedTornadoRouterAddress) + ); + + // The Uniswap V3 Fee Oracle also needs global data like the old FeeManager (Uniswap V3 functionality + // has now been split out) did, the legacy data will be used and in this version also the minimum + // observation cardinality for Uniswap V3 pools is set, it'll be a value that most pools can supply + // and in general if it is too strict a proposal adding instances can also just set it to a lower + // value, kind of gamey, but just to "enforce" some constraint or inform the user one exists + + UniswapV3FeeOracle v3FeeOracle = UniswapV3FeeOracle(deployedUniswapV3FeeOracleAddress); + + v3FeeOracle.setGlobalTornPoolFee(10_000, true); // Legacy value, still makes sense + + v3FeeOracle.setGlobalTwapIntervalSeconds(5400); // Legacy value, still makes sense + + v3FeeOracle.setGlobalMinObservationCardinality(10); // TODO: Check the value + + // Each of the instances are going to require a Uniswap Pool Fee to be set such that we actually know + // what pools to lookup when querying for Oracle data. This data has also been read out from the + // instance registry and it basically has the 3000 (0.3%) and 500 (0.05%) non-stable and stable + // respectively "default" pool fees. This is not to be confused with pool protocol fees, these have + // been recorded in the (first) legacy initialization above + + _setAllInstancePoolFeesInOracle(v3FeeOracle); + } + + function _setAllInstancePoolFeesInOracle(UniswapV3FeeOracle _v3FeeOracle) internal { + ITornadoInstance[] memory instances = _getAllInstances(); + + IERC20 weth = IERC20(instances[0].token()); + IERC20 dai = IERC20(instances[4].token()); + IERC20 cdai = IERC20(instances[8].token()); + IERC20 usdc = IERC20(instances[12].token()); + IERC20 usdt = IERC20(instances[14].token()); + IERC20 wbtc = IERC20(instances[16].token()); + + /* ETH instances */ + _v3FeeOracle.setPoolFeeForToken(weth, uint24(0x000)); + _v3FeeOracle.setPoolFeeForToken(weth, uint24(0x000)); + _v3FeeOracle.setPoolFeeForToken(weth, uint24(0x000)); + _v3FeeOracle.setPoolFeeForToken(weth, uint24(0x000)); + + /* DAI instances */ + _v3FeeOracle.setPoolFeeForToken(dai, uint24(0xbb8)); + _v3FeeOracle.setPoolFeeForToken(dai, uint24(0xbb8)); + _v3FeeOracle.setPoolFeeForToken(dai, uint24(0xbb8)); + _v3FeeOracle.setPoolFeeForToken(dai, uint24(0xbb8)); + + /* cDAI instances */ + _v3FeeOracle.setPoolFeeForToken(cdai, uint24(0xbb8)); + _v3FeeOracle.setPoolFeeForToken(cdai, uint24(0xbb8)); + _v3FeeOracle.setPoolFeeForToken(cdai, uint24(0xbb8)); + _v3FeeOracle.setPoolFeeForToken(cdai, uint24(0xbb8)); + + /* USDC instances */ + _v3FeeOracle.setPoolFeeForToken(usdc, uint24(0x1f4)); + _v3FeeOracle.setPoolFeeForToken(usdc, uint24(0x1f4)); + + /* USDT instances */ + _v3FeeOracle.setPoolFeeForToken(usdt, uint24(0x1f4)); + _v3FeeOracle.setPoolFeeForToken(usdt, uint24(0x1f4)); + + /* WBTC instances */ + _v3FeeOracle.setPoolFeeForToken(wbtc, uint24(0xbb8)); + _v3FeeOracle.setPoolFeeForToken(wbtc, uint24(0xbb8)); + _v3FeeOracle.setPoolFeeForToken(wbtc, uint24(0xbb8)); + } + + function _getAllInstances() internal pure returns (ITornadoInstance[] memory _addresses) { + _addresses = new ITornadoInstance[](19); + + /* ETH instances */ + _addresses[0] = ITornadoInstance(0x12D66f87A04A9E220743712cE6d9bB1B5616B8Fc); + _addresses[1] = ITornadoInstance(0x47CE0C6eD5B0Ce3d3A51fdb1C52DC66a7c3c2936); + _addresses[2] = ITornadoInstance(0x910Cbd523D972eb0a6f4cAe4618aD62622b39DbF); + _addresses[3] = ITornadoInstance(0xA160cdAB225685dA1d56aa342Ad8841c3b53f291); + + /* DAI instances */ + _addresses[4] = ITornadoInstance(0xD4B88Df4D29F5CedD6857912842cff3b20C8Cfa3); + _addresses[5] = ITornadoInstance(0xFD8610d20aA15b7B2E3Be39B396a1bC3516c7144); + _addresses[6] = ITornadoInstance(0x07687e702b410Fa43f4cB4Af7FA097918ffD2730); + _addresses[7] = ITornadoInstance(0x23773E65ed146A459791799d01336DB287f25334); + + /* cDAI instances */ + _addresses[8] = ITornadoInstance(0x22aaA7720ddd5388A3c0A3333430953C68f1849b); + _addresses[9] = ITornadoInstance(0x03893a7c7463AE47D46bc7f091665f1893656003); + _addresses[10] = ITornadoInstance(0x2717c5e28cf931547B621a5dddb772Ab6A35B701); + _addresses[11] = ITornadoInstance(0xD21be7248e0197Ee08E0c20D4a96DEBdaC3D20Af); + + /* USDC instances */ + _addresses[12] = ITornadoInstance(0xd96f2B1c14Db8458374d9Aca76E26c3D18364307); + _addresses[13] = ITornadoInstance(0x4736dCf1b7A3d580672CcE6E7c65cd5cc9cFBa9D); + + /* USDT instances */ + _addresses[14] = ITornadoInstance(0x169AD27A470D064DEDE56a2D3ff727986b15D52B); + _addresses[15] = ITornadoInstance(0x0836222F2B2B24A3F36f98668Ed8F0B38D1a872f); + + /* WBTC instances */ + _addresses[16] = ITornadoInstance(0x178169B423a011fff22B9e3F3abeA13414dDD0F1); + _addresses[17] = ITornadoInstance(0x610B717796ad172B316836AC95a2ffad065CeaB4); + _addresses[18] = ITornadoInstance(0xbB93e510BbCD0B7beb5A853875f9eC60275CF498); + } + + function _getAllInstanceFeePercents() internal pure returns (uint256[] memory _percents) { + _percents = new uint256[](19); + + /* ETH instances */ + _percents[0] = uint256(0x0000000000000000000000000000000000000000000000000000000000000000); + _percents[1] = uint256(0x000000000000000000000000000000000000000000000000000000000000001e); + _percents[2] = uint256(0x000000000000000000000000000000000000000000000000000000000000001e); + _percents[3] = uint256(0x000000000000000000000000000000000000000000000000000000000000001e); + + /* DAI instances */ + _percents[4] = uint256(0x0000000000000000000000000000000000000000000000000000000000000000); + _percents[5] = uint256(0x0000000000000000000000000000000000000000000000000000000000000000); + _percents[6] = uint256(0x000000000000000000000000000000000000000000000000000000000000001e); + _percents[7] = uint256(0x000000000000000000000000000000000000000000000000000000000000001e); + + /* cDAI instances */ + _percents[8] = uint256(0x0000000000000000000000000000000000000000000000000000000000000000); + _percents[9] = uint256(0x0000000000000000000000000000000000000000000000000000000000000000); + _percents[11] = uint256(0x0000000000000000000000000000000000000000000000000000000000000000); + _percents[12] = uint256(0x0000000000000000000000000000000000000000000000000000000000000000); + + /* USDC instances */ + _percents[13] = uint256(0x0000000000000000000000000000000000000000000000000000000000000000); + _percents[14] = uint256(0x0000000000000000000000000000000000000000000000000000000000000000); + + /* USDT instances */ + _percents[15] = uint256(0x0000000000000000000000000000000000000000000000000000000000000000); + _percents[16] = uint256(0x0000000000000000000000000000000000000000000000000000000000000000); + + /* WBTC instances */ + _percents[17] = uint256(0x000000000000000000000000000000000000000000000000000000000000001e); + _percents[18] = uint256(0x000000000000000000000000000000000000000000000000000000000000001e); + _percents[19] = uint256(0x000000000000000000000000000000000000000000000000000000000000001e); + } +} diff --git a/src/v2/FeeOracleManager.sol b/src/v2/FeeOracleManager.sol index a6eb6a2..a2781f5 100644 --- a/src/v2/FeeOracleManager.sol +++ b/src/v2/FeeOracleManager.sol @@ -104,15 +104,15 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable { function initialize( address _uniswapV3FeeOracle, address _instanceRegistryAddress, - address[] calldata _instanceAddresses, + ITornadoInstance[] calldata _instances, uint256[] calldata _feePercents ) external onlyGovernance initializer { // Get num of existing instances - uint256 numInstances = _instanceAddresses.length; + uint256 numInstances = _instances.length; for (uint256 i = 0; i < numInstances; i++) { // For each instance - ITornadoInstance instance = ITornadoInstance(_instanceAddresses[i]); + ITornadoInstance instance = _instances[i]; // Store it's old data and the percent fees which Governance will command feeDataForInstance[instance] = FeeData({ @@ -165,14 +165,19 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable { // Now update if we do not respect the interval or we respect it and are in the interval if (!_respectFeeUpdateInterval || (feeUpdateInterval < -feeData.lastUpdated + now)) { - // This will revert if no contract is set - feeData.feeAmount = instanceFeeOracles[_instance].getFee( - torn, - _instance, - instanceRegistry.getInstanceData(_instance), - feeData.feePercent, - FEE_PERCENT_DIVISOR - ); + // There must a be a fee set otherwise it's just 0 + if (feeData.feePercent != 0) { + // This will revert if no contract is set + feeData.feeAmount = instanceFeeOracles[_instance].getFee( + torn, + _instance, + instanceRegistry.getInstanceData(_instance), + feeData.feePercent, + FEE_PERCENT_DIVISOR + ); + } else { + feeData.feeAmount = 0; + } // Store feeDataForInstance[_instance] = FeeData({