pragma solidity ^0.5.16; // Inheritance import "./Owned.sol"; import "./MixinResolver.sol"; import "./Proxyable.sol"; import "./interfaces/IFuturesMarketManager.sol"; // Libraries import "openzeppelin-solidity-2.3.0/contracts/math/SafeMath.sol"; import "./AddressSetLib.sol"; // Internal references import "./interfaces/IFuturesMarket.sol"; import "./interfaces/ISynth.sol"; import "./interfaces/IFeePool.sol"; import "./interfaces/IExchanger.sol"; import "./interfaces/IERC20.sol"; // https://docs.oikos.cash/contracts/source/contracts/FuturesMarketManager contract FuturesMarketManager is Owned, MixinResolver, IFuturesMarketManager { using SafeMath for uint; using AddressSetLib for AddressSetLib.AddressSet; /* ========== STATE VARIABLES ========== */ AddressSetLib.AddressSet internal _markets; mapping(bytes32 => address) public marketForKey; /* ========== ADDRESS RESOLVER CONFIGURATION ========== */ bytes32 internal constant SUSD = "oUSD"; bytes32 internal constant CONTRACT_SYNTHSUSD = "SynthoUSD"; bytes32 internal constant CONTRACT_FEEPOOL = "FeePool"; bytes32 internal constant CONTRACT_EXCHANGER = "Exchanger"; /* ========== CONSTRUCTOR ========== */ constructor(address _owner, address _resolver) public Owned(_owner) MixinResolver(_resolver) {} /* ========== VIEWS ========== */ function resolverAddressesRequired() public view returns (bytes32[] memory addresses) { addresses = new bytes32[](3); addresses[0] = CONTRACT_SYNTHSUSD; addresses[1] = CONTRACT_FEEPOOL; addresses[2] = CONTRACT_EXCHANGER; } function _oUSD() internal view returns (ISynth) { return ISynth(requireAndGetAddress(CONTRACT_SYNTHSUSD)); } function _feePool() internal view returns (IFeePool) { return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL)); } function _exchanger() internal view returns (IExchanger) { return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER)); } /* * Returns slices of the list of all markets. */ function markets(uint index, uint pageSize) external view returns (address[] memory) { return _markets.getPage(index, pageSize); } /* * The number of markets known to the manager. */ function numMarkets() external view returns (uint) { return _markets.elements.length; } /* * The list of all markets. */ function allMarkets() external view returns (address[] memory) { return _markets.getPage(0, _markets.elements.length); } function _marketsForKeys(bytes32[] memory marketKeys) internal view returns (address[] memory) { uint mMarkets = marketKeys.length; address[] memory results = new address[](mMarkets); for (uint i; i < mMarkets; i++) { results[i] = marketForKey[marketKeys[i]]; } return results; } /* * The market addresses for a given set of market key strings. */ function marketsForKeys(bytes32[] calldata marketKeys) external view returns (address[] memory) { return _marketsForKeys(marketKeys); } /* * The accumulated debt contribution of all futures markets. */ function totalDebt() external view returns (uint debt, bool isInvalid) { uint total; bool anyIsInvalid; uint numOfMarkets = _markets.elements.length; for (uint i; i < numOfMarkets; i++) { (uint marketDebt, bool invalid) = IFuturesMarket(_markets.elements[i]).marketDebt(); total = total.add(marketDebt); anyIsInvalid = anyIsInvalid || invalid; } return (total, anyIsInvalid); } /* ========== MUTATIVE FUNCTIONS ========== */ /* * Add a set of new markets. Reverts if some market key already has a market. */ function addMarkets(address[] calldata marketsToAdd) external onlyOwner { uint numOfMarkets = marketsToAdd.length; for (uint i; i < numOfMarkets; i++) { address market = marketsToAdd[i]; require(!_markets.contains(market), "Market already exists"); bytes32 key = IFuturesMarket(market).marketKey(); bytes32 baseAsset = IFuturesMarket(market).baseAsset(); require(marketForKey[key] == address(0), "Market already exists for key"); marketForKey[key] = market; _markets.add(market); emit MarketAdded(market, baseAsset, key); } } function _removeMarkets(address[] memory marketsToRemove) internal { uint numOfMarkets = marketsToRemove.length; for (uint i; i < numOfMarkets; i++) { address market = marketsToRemove[i]; require(market != address(0), "Unknown market"); bytes32 key = IFuturesMarket(market).marketKey(); bytes32 baseAsset = IFuturesMarket(market).baseAsset(); require(marketForKey[key] != address(0), "Unknown market"); delete marketForKey[key]; _markets.remove(market); emit MarketRemoved(market, baseAsset, key); } } /* * Remove a set of markets. Reverts if any market is not known to the manager. */ function removeMarkets(address[] calldata marketsToRemove) external onlyOwner { return _removeMarkets(marketsToRemove); } /* * Remove the markets for a given set of market keys. Reverts if any key has no associated market. */ function removeMarketsByKey(bytes32[] calldata marketKeysToRemove) external onlyOwner { _removeMarkets(_marketsForKeys(marketKeysToRemove)); } /* * Allows a market to issue oUSD to an account when it withdraws margin. * This function is not callable through the proxy, only underlying contracts interact; * it reverts if not called by a known market. */ function issueSUSD(address account, uint amount) external onlyMarkets { // No settlement is required to issue synths into the target account. _oUSD().issue(account, amount); } /* * Allows a market to burn oUSD from an account when it deposits margin. * This function is not callable through the proxy, only underlying contracts interact; * it reverts if not called by a known market. */ function burnSUSD(address account, uint amount) external onlyMarkets returns (uint postReclamationAmount) { // We'll settle first, in order to ensure the user has sufficient balance. // If the settlement reduces the user's balance below the requested amount, // the settled remainder will be the resulting deposit. // Exchanger.settle ensures synth is active ISynth oUSD = _oUSD(); (uint reclaimed, , ) = _exchanger().settle(account, SUSD); uint balanceAfter = amount; if (0 < reclaimed) { balanceAfter = IERC20(address(oUSD)).balanceOf(account); } // Reduce the value to burn if balance is insufficient after reclamation amount = balanceAfter < amount ? balanceAfter : amount; oUSD.burn(account, amount); return amount; } /* * Allows markets to issue exchange fees into the fee pool and notify it that this occurred. * This function is not callable through the proxy, only underlying contracts interact; * it reverts if not called by a known market. */ function payFee(uint amount) external onlyMarkets { IFeePool pool = _feePool(); _oUSD().issue(pool.FEE_ADDRESS(), amount); pool.recordFeePaid(amount); } /* ========== MODIFIERS ========== */ function _requireIsMarket() internal view { require(_markets.contains(msg.sender), "Permitted only for markets"); } modifier onlyMarkets() { _requireIsMarket(); _; } /* ========== EVENTS ========== */ event MarketAdded(address market, bytes32 indexed asset, bytes32 indexed marketKey); event MarketRemoved(address market, bytes32 indexed asset, bytes32 indexed marketKey); }