pragma solidity ^0.5.16; pragma experimental ABIEncoderV2; // 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/ISynth.sol"; import "./interfaces/IFeePool.sol"; import "./interfaces/IExchanger.sol"; import "./interfaces/IERC20.sol"; // basic views that are expected to be supported by v1 (IFuturesMarket) and v2 markets (IPerpsV2Market) interface IMarketViews { function marketKey() external view returns (bytes32); function baseAsset() external view returns (bytes32); function marketSize() external view returns (uint128); function marketSkew() external view returns (int128); function assetPrice() external view returns (uint price, bool invalid); function marketDebt() external view returns (uint debt, bool isInvalid); function currentFundingRate() external view returns (int fundingRate); } // https://docs.synthetix.io/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 public constant CONTRACT_NAME = "FuturesMarketManager"; bytes32 internal constant SUSD = "sUSD"; bytes32 internal constant CONTRACT_SYNTHSUSD = "SynthsUSD"; 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 _sUSD() 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() public 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 = 0; i < numOfMarkets; i++) { (uint marketDebt, bool invalid) = IMarketViews(_markets.elements[i]).marketDebt(); total = total.add(marketDebt); anyIsInvalid = anyIsInvalid || invalid; } return (total, anyIsInvalid); } struct MarketSummary { address market; bytes32 asset; bytes32 marketKey; uint price; uint marketSize; int marketSkew; uint marketDebt; int currentFundingRate; bool priceInvalid; } function _marketSummaries(address[] memory addresses) internal view returns (MarketSummary[] memory) { uint nMarkets = addresses.length; MarketSummary[] memory summaries = new MarketSummary[](nMarkets); for (uint i; i < nMarkets; i++) { IMarketViews market = IMarketViews(addresses[i]); bytes32 marketKey = market.marketKey(); bytes32 baseAsset = market.baseAsset(); (uint price, bool invalid) = market.assetPrice(); (uint debt, ) = market.marketDebt(); summaries[i] = MarketSummary({ market: address(market), asset: baseAsset, marketKey: marketKey, price: price, marketSize: market.marketSize(), marketSkew: market.marketSkew(), marketDebt: debt, currentFundingRate: market.currentFundingRate(), priceInvalid: invalid }); } return summaries; } function marketSummaries(address[] calldata addresses) external view returns (MarketSummary[] memory) { return _marketSummaries(addresses); } function marketSummariesForKeys(bytes32[] calldata marketKeys) external view returns (MarketSummary[] memory) { return _marketSummaries(_marketsForKeys(marketKeys)); } function allMarketSummaries() external view returns (MarketSummary[] memory) { return _marketSummaries(allMarkets()); } /* ========== 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 = IMarketViews(market).marketKey(); bytes32 baseAsset = IMarketViews(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 = IMarketViews(market).marketKey(); bytes32 baseAsset = IMarketViews(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 sUSD 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. _sUSD().issue(account, amount); } /* * Allows a market to burn sUSD 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 sUSD = _sUSD(); (uint reclaimed, , ) = _exchanger().settle(account, SUSD); uint balanceAfter = amount; if (0 < reclaimed) { balanceAfter = IERC20(address(sUSD)).balanceOf(account); } // Reduce the value to burn if balance is insufficient after reclamation amount = balanceAfter < amount ? balanceAfter : amount; sUSD.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, bytes32 trackingCode) external onlyMarkets { _payFee(amount, trackingCode); } // backwards compatibility with futures v1 function payFee(uint amount) external onlyMarkets { _payFee(amount, bytes32(0)); } function _payFee(uint amount, bytes32 trackingCode) internal { delete trackingCode; // unused for now, will be used SIP 203 IFeePool pool = _feePool(); _sUSD().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); }