pragma solidity ^0.5.16; // Inheritance import "./Owned.sol"; import "./MixinResolver.sol"; import "./interfaces/IDebtCache.sol"; // Libraries import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/IIssuer.sol"; import "./interfaces/IExchanger.sol"; import "./interfaces/IExchangeRates.sol"; import "./interfaces/ISystemStatus.sol"; import "./interfaces/IEtherCollateral.sol"; import "./interfaces/IEtherCollateraloUSD.sol"; import "./interfaces/IERC20.sol"; contract DebtCache is Owned, MixinResolver, IDebtCache { using SafeMath for uint; using SafeDecimalMath for uint; uint internal _cachedDebt; mapping(bytes32 => uint) internal _cachedSynthDebt; uint internal _cacheTimestamp; bool internal _cacheInvalid = true; /* ========== ENCODED NAMES ========== */ bytes32 internal constant oUSD = "oUSD"; bytes32 internal constant oETH = "oETH"; /* ========== ADDRESS RESOLVER CONFIGURATION ========== */ bytes32 private constant CONTRACT_ISSUER = "Issuer"; bytes32 private constant CONTRACT_EXCHANGER = "Exchanger"; bytes32 private constant CONTRACT_EXRATES = "ExchangeRates"; bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus"; bytes32 private constant CONTRACT_ETHERCOLLATERAL = "EtherCollateral"; bytes32 private constant CONTRACT_ETHERCOLLATERAL_SUSD = "EtherCollateraloUSD"; bytes32[24] private addressesToCache = [ CONTRACT_ISSUER, CONTRACT_EXCHANGER, CONTRACT_EXRATES, CONTRACT_SYSTEMSTATUS, CONTRACT_ETHERCOLLATERAL, CONTRACT_ETHERCOLLATERAL_SUSD ]; uint public constant DEBT_SNAPSHOT_STALE_TIME = 43200; constructor(address _owner, address _resolver) public Owned(_owner) MixinResolver(_resolver, addressesToCache) {} /* ========== VIEWS ========== */ function issuer() internal view returns (IIssuer) { return IIssuer(resolver.requireAndGetAddress(CONTRACT_ISSUER, "Missing Issuer address")); } function exchanger() internal view returns (IExchanger) { return IExchanger(resolver.requireAndGetAddress(CONTRACT_EXCHANGER, "Missing Exchanger address")); } function exchangeRates() internal view returns (IExchangeRates) { return IExchangeRates(resolver.requireAndGetAddress(CONTRACT_EXRATES, "Missing ExchangeRates address")); } function systemStatus() internal view returns (ISystemStatus) { return ISystemStatus(resolver.requireAndGetAddress(CONTRACT_SYSTEMSTATUS, "Missing SystemStatus address")); } // function etherCollateral() internal view returns (IEtherCollateral) { // return IEtherCollateral(resolver.requireAndGetAddress(CONTRACT_ETHERCOLLATERAL, "Missing EtherCollateral address")); // } function etherCollateraloUSD() internal view returns (IEtherCollateraloUSD) { return IEtherCollateraloUSD(resolver.requireAndGetAddress(CONTRACT_ETHERCOLLATERAL_SUSD, "Missing EtherCollateraloUSD address")); } function debtSnapshotStaleTime() external view returns (uint) { return DEBT_SNAPSHOT_STALE_TIME; //getDebtSnapshotStaleTime(); } function cachedDebt() external view returns (uint) { return _cachedDebt; } function cachedSynthDebt(bytes32 currencyKey) external view returns (uint) { return _cachedSynthDebt[currencyKey]; } function cacheTimestamp() external view returns (uint) { return _cacheTimestamp; } function cacheInvalid() external view returns (bool) { return _cacheInvalid; } function _cacheStale(uint timestamp) internal view returns (bool) { // Note a 0 timestamp means that the cache is uninitialised. // We'll keep the check explicitly in case the stale time is // ever set to something higher than the current unix time (e.g. to turn off staleness). return DEBT_SNAPSHOT_STALE_TIME < block.timestamp - timestamp || timestamp == 0; } function cacheStale() external view returns (bool) { return _cacheStale(_cacheTimestamp); } function _issuedSynthValues(bytes32[] memory currencyKeys, uint[] memory rates) internal view returns (uint[] memory) { uint numValues = currencyKeys.length; uint[] memory values = new uint[](numValues); ISynth[] memory synths = issuer().getSynths(currencyKeys); for (uint i = 0; i < numValues; i++) { bytes32 key = currencyKeys[i]; address synthAddress = address(synths[i]); require(synthAddress != address(0), "Synth does not exist"); uint supply = IERC20(synthAddress).totalSupply(); // bool isSUSD = key == oUSD; // if (isSUSD || key == oETH) { // IEtherCollateral etherCollateralContract = isSUSD // ? IEtherCollateral(address(etherCollateraloUSD())) // : etherCollateral(); // IEtherCollateral // uint etherCollateralSupply = etherCollateralContract.totalIssuedSynths(); // supply = supply.sub(etherCollateralSupply); // } IEtherCollateral etherCollateralContract = IEtherCollateral(address(etherCollateraloUSD())); uint etherCollateralSupply = etherCollateralContract.totalIssuedSynths(); supply = supply.sub(etherCollateralSupply); values[i] = supply.multiplyDecimalRound(rates[i]); } return values; } function _currentSynthDebts(bytes32[] memory currencyKeys) internal view returns (uint[] memory oksIssuedDebts, bool anyRateIsInvalid) { (uint[] memory rates, bool isInvalid) = exchangeRates().ratesAndStaleForCurrencies(currencyKeys); return (_issuedSynthValues(currencyKeys, rates), isInvalid); } function currentSynthDebts(bytes32[] calldata currencyKeys) external view returns (uint[] memory debtValues, bool anyRateIsInvalid) { return _currentSynthDebts(currencyKeys); } function _cachedSynthDebts(bytes32[] memory currencyKeys) internal view returns (uint[] memory) { uint numKeys = currencyKeys.length; uint[] memory debts = new uint[](numKeys); for (uint i = 0; i < numKeys; i++) { debts[i] = _cachedSynthDebt[currencyKeys[i]]; } return debts; } function cachedSynthDebts(bytes32[] calldata currencyKeys) external view returns (uint[] memory oksIssuedDebts) { return _cachedSynthDebts(currencyKeys); } function _currentDebt() internal view returns (uint debt, bool anyRateIsInvalid) { (uint[] memory values, bool isInvalid) = _currentSynthDebts(issuer().availableCurrencyKeys()); uint numValues = values.length; uint total; for (uint i; i < numValues; i++) { total = total.add(values[i]); } return (total, isInvalid); } function currentDebt() external view returns (uint debt, bool anyRateIsInvalid) { return _currentDebt(); } function cacheInfo() external view returns ( uint debt, uint timestamp, bool isInvalid, bool isStale ) { uint time = _cacheTimestamp; return (_cachedDebt, time, _cacheInvalid, _cacheStale(time)); } /* ========== MUTATIVE FUNCTIONS ========== */ // This function exists in case a synth is ever somehow removed without its snapshot being updated. function purgeCachedSynthDebt(bytes32 currencyKey) external onlyOwner { require(issuer().synths(currencyKey) == ISynth(0), "Synth exists"); delete _cachedSynthDebt[currencyKey]; } function takeDebtSnapshot() external requireSystemActiveIfNotOwner { bytes32[] memory currencyKeys = issuer().availableCurrencyKeys(); (uint[] memory values, bool isInvalid) = _currentSynthDebts(currencyKeys); uint numValues = values.length; uint oksCollateralDebt; for (uint i; i < numValues; i++) { uint value = values[i]; oksCollateralDebt = oksCollateralDebt.add(value); _cachedSynthDebt[currencyKeys[i]] = value; } _cachedDebt = oksCollateralDebt; _cacheTimestamp = block.timestamp; emit DebtCacheUpdated(oksCollateralDebt); emit DebtCacheSnapshotTaken(block.timestamp); // (in)validate the cache if necessary _updateDebtCacheValidity(isInvalid); } function updateCachedSynthDebts(bytes32[] calldata currencyKeys) external requireSystemActiveIfNotOwner { (uint[] memory rates, bool anyRateInvalid) = exchangeRates().ratesAndStaleForCurrencies(currencyKeys); _updateCachedSynthDebtsWithRates(currencyKeys, rates, anyRateInvalid); } function updateCachedSynthDebtWithRate(bytes32 currencyKey, uint currencyRate) external onlyIssuer { bytes32[] memory synthKeyArray = new bytes32[](1); synthKeyArray[0] = currencyKey; uint[] memory synthRateArray = new uint[](1); synthRateArray[0] = currencyRate; _updateCachedSynthDebtsWithRates(synthKeyArray, synthRateArray, false); } function updateCachedSynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates) external onlyIssuerOrExchanger { _updateCachedSynthDebtsWithRates(currencyKeys, currencyRates, false); } function updateDebtCacheValidity(bool currentlyInvalid) external onlyIssuer { _updateDebtCacheValidity(currentlyInvalid); } /* ========== INTERNAL FUNCTIONS ========== */ function _updateDebtCacheValidity(bool currentlyInvalid) internal { if (_cacheInvalid != currentlyInvalid) { _cacheInvalid = currentlyInvalid; emit DebtCacheValidityChanged(currentlyInvalid); } } function _updateCachedSynthDebtsWithRates( bytes32[] memory currencyKeys, uint[] memory currentRates, bool anyRateIsInvalid ) internal { uint numKeys = currencyKeys.length; require(numKeys == currentRates.length, "Input array lengths differ"); // Update the cached values for each synth, saving the sums as we go. uint cachedSum; uint currentSum; uint[] memory currentValues = _issuedSynthValues(currencyKeys, currentRates); for (uint i = 0; i < numKeys; i++) { bytes32 key = currencyKeys[i]; uint currentSynthDebt = currentValues[i]; cachedSum = cachedSum.add(_cachedSynthDebt[key]); currentSum = currentSum.add(currentSynthDebt); _cachedSynthDebt[key] = currentSynthDebt; } // Compute the difference and apply it to the snapshot if (cachedSum != currentSum) { uint debt = _cachedDebt; // This requirement should never fail, as the total debt snapshot is the sum of the individual synth // debt snapshots. require(cachedSum <= debt, "Cached synth sum exceeds total debt"); debt = debt.sub(cachedSum).add(currentSum); _cachedDebt = debt; emit DebtCacheUpdated(debt); } // A partial update can invalidate the debt cache, but a full snapshot must be performed in order // to re-validate it. if (anyRateIsInvalid) { _updateDebtCacheValidity(anyRateIsInvalid); } } // function recordExcludedDebtChange(bytes32 currencyKey, int256 delta) external onlyDebtIssuer { // int256 newExcludedDebt = int256(_excludedIssuedDebt[currencyKey]) + delta; // require(newExcludedDebt >= 0, "Excluded debt cannot become negative"); // _excludedIssuedDebt[currencyKey] = uint(newExcludedDebt); // } function updateCachedoUSDDebt(int amount) external onlyIssuer { uint delta = SafeDecimalMath.abs(amount); if (amount > 0) { _cachedSynthDebt[oUSD] = _cachedSynthDebt[oUSD].add(delta); _cachedDebt = _cachedDebt.add(delta); } else { _cachedSynthDebt[oUSD] = _cachedSynthDebt[oUSD].sub(delta); _cachedDebt = _cachedDebt.sub(delta); } emit DebtCacheUpdated(_cachedDebt); } /* ========== MODIFIERS ========== */ function _requireSystemActiveIfNotOwner() internal view { if (msg.sender != owner) { systemStatus().requireSystemActive(); } } modifier requireSystemActiveIfNotOwner() { _requireSystemActiveIfNotOwner(); _; } function _onlyIssuer() internal view { require(msg.sender == address(issuer()), "abcd"); } modifier onlyIssuer() { _onlyIssuer(); _; } function _onlyIssuerOrExchanger() internal view { require(msg.sender == address(issuer()) || msg.sender == address(exchanger()), "Sender is not Issuer or Exchanger"); } modifier onlyIssuerOrExchanger() { _onlyIssuerOrExchanger(); _; } /* ========== EVENTS ========== */ event DebtCacheUpdated(uint cachedDebt); event DebtCacheSnapshotTaken(uint timestamp); event DebtCacheValidityChanged(bool indexed isInvalid); }