diff --git a/contracts/EmptyFuturesMarketManager.sol b/contracts/EmptyFuturesMarketManager.sol index 14bd9f90a8..193ed4a621 100644 --- a/contracts/EmptyFuturesMarketManager.sol +++ b/contracts/EmptyFuturesMarketManager.sol @@ -22,13 +22,13 @@ contract EmptyFuturesMarketManager is IFuturesMarketManager { return _markets; } - function marketForAsset(bytes32 asset) external view returns (address) { - asset; + function marketForKey(bytes32 marketKey) external view returns (address) { + marketKey; return address(0); } - function marketsForAssets(bytes32[] calldata assets) external view returns (address[] memory) { - assets; + function marketsForKeys(bytes32[] calldata marketKeys) external view returns (address[] memory) { + marketKeys; address[] memory _markets; return _markets; } diff --git a/contracts/Exchanger.sol b/contracts/Exchanger.sol index f9d5424282..1df3e4cd79 100644 --- a/contracts/Exchanger.sol +++ b/contracts/Exchanger.sol @@ -951,7 +951,7 @@ contract Exchanger is Owned, MixinSystemSettings, IExchanger { (, bool srcInvalid) = exchangeCircuitBreaker().rateWithInvalid(sourceCurrencyKey); (, bool dstInvalid) = exchangeCircuitBreaker().rateWithInvalid(destinationCurrencyKey); require(!srcInvalid, "source synth rate invalid"); - require(!dstInvalid, "destinatio synth rate invalid"); + require(!dstInvalid, "destination synth rate invalid"); // check rates not stale or flagged _ensureCanExchange(sourceCurrencyKey, sourceAmount, destinationCurrencyKey); diff --git a/contracts/FuturesMarket.sol b/contracts/FuturesMarket.sol index 70dd28b781..008c7d1184 100644 --- a/contracts/FuturesMarket.sol +++ b/contracts/FuturesMarket.sol @@ -56,5 +56,9 @@ import "./interfaces/IFuturesMarket.sol"; // https://docs.synthetix.io/contracts/source/contracts/FuturesMarket contract FuturesMarket is IFuturesMarket, FuturesMarketBase, MixinFuturesNextPriceOrders, MixinFuturesViews { - constructor(address _resolver, bytes32 _baseAsset) public FuturesMarketBase(_resolver, _baseAsset) {} + constructor( + address _resolver, + bytes32 _baseAsset, + bytes32 _marketKey + ) public FuturesMarketBase(_resolver, _baseAsset, _marketKey) {} } diff --git a/contracts/FuturesMarketBase.sol b/contracts/FuturesMarketBase.sol index b371c777fe..88f28eaac6 100644 --- a/contracts/FuturesMarketBase.sol +++ b/contracts/FuturesMarketBase.sol @@ -96,6 +96,10 @@ contract FuturesMarketBase is MixinFuturesMarketSettings, IFuturesMarketBaseType /* ========== STATE VARIABLES ========== */ + // The market identifier in the futures system (manager + settings). Multiple markets can co-exist + // for the same asset in order to allow migrations. + bytes32 public marketKey; + // The asset being traded in this market. This should be a valid key into the ExchangeRates contract. bytes32 public baseAsset; @@ -157,8 +161,13 @@ contract FuturesMarketBase is MixinFuturesMarketSettings, IFuturesMarketBaseType /* ========== CONSTRUCTOR ========== */ - constructor(address _resolver, bytes32 _baseAsset) public MixinFuturesMarketSettings(_resolver) { + constructor( + address _resolver, + bytes32 _baseAsset, + bytes32 _marketKey + ) public MixinFuturesMarketSettings(_resolver) { baseAsset = _baseAsset; + marketKey = _marketKey; // Initialise the funding sequence with 0 initially accrued, so that the first usable funding index is 1. fundingSequence.push(0); @@ -222,13 +231,13 @@ contract FuturesMarketBase is MixinFuturesMarketSettings, IFuturesMarketBaseType function _proportionalSkew(uint price) internal view returns (int) { // marketSize is in baseAsset units so we need to convert from USD units require(price > 0, "price can't be zero"); - uint skewScaleBaseAsset = _skewScaleUSD(baseAsset).divideDecimal(price); + uint skewScaleBaseAsset = _skewScaleUSD(marketKey).divideDecimal(price); require(skewScaleBaseAsset != 0, "skewScale is zero"); // don't divide by zero return int(marketSkew).divideDecimal(int(skewScaleBaseAsset)); } function _currentFundingRate(uint price) internal view returns (int) { - int maxFundingRate = int(_maxFundingRate(baseAsset)); + int maxFundingRate = int(_maxFundingRate(marketKey)); // Note the minus sign: funding flows in the opposite direction to the skew. return _min(_max(-_UNIT, -_proportionalSkew(price)), _UNIT).multiplyDecimal(maxFundingRate); } @@ -359,7 +368,7 @@ contract FuturesMarketBase is MixinFuturesMarketSettings, IFuturesMarketBaseType // This should guarantee that the value returned here can always been withdrawn, but there may be // a little extra actually-accessible value left over, depending on the position size and margin. uint milli = uint(_UNIT / 1000); - int maxLeverage = int(_maxLeverage(baseAsset).sub(milli)); + int maxLeverage = int(_maxLeverage(marketKey).sub(milli)); uint inaccessible = _abs(_notionalValue(position.size, price).divideDecimal(maxLeverage)); // If the user has a position open, we'll enforce a min initial margin requirement. @@ -532,7 +541,7 @@ contract FuturesMarketBase is MixinFuturesMarketSettings, IFuturesMarketBaseType { // stack too deep int leverage = int(newPos.size).multiplyDecimal(int(params.price)).divideDecimal(int(newMargin.add(fee))); - if (_maxLeverage(baseAsset).add(uint(_UNIT) / 100) < _abs(leverage)) { + if (_maxLeverage(marketKey).add(uint(_UNIT) / 100) < _abs(leverage)) { return (oldPos, 0, Status.MaxLeverageExceeded); } } @@ -541,7 +550,7 @@ contract FuturesMarketBase is MixinFuturesMarketSettings, IFuturesMarketBaseType // Allow a bit of extra value in case of rounding errors. if ( _orderSizeTooLarge( - uint(int(_maxMarketValueUSD(baseAsset).add(100 * uint(_UNIT))).divideDecimal(int(params.price))), + uint(int(_maxMarketValueUSD(marketKey).add(100 * uint(_UNIT))).divideDecimal(int(params.price))), oldPos.size, newPos.size ) @@ -628,7 +637,7 @@ contract FuturesMarketBase is MixinFuturesMarketSettings, IFuturesMarketBaseType */ function _assetPriceRequireSystemChecks() internal returns (uint) { // check that futures market isn't suspended, revert with appropriate message - _systemStatus().requireFuturesMarketActive(baseAsset); // asset and market may be different + _systemStatus().requireFuturesMarketActive(marketKey); // asset and market may be different // check that synth is active, and wasn't suspended, revert with appropriate message _systemStatus().requireSynthActive(baseAsset); // check if circuit breaker if price is within deviation tolerance and system & synth is active @@ -802,7 +811,7 @@ contract FuturesMarketBase is MixinFuturesMarketSettings, IFuturesMarketBaseType _revertIfError( (margin < _minInitialMargin()) || (margin <= _liquidationMargin(position.size, price)) || - (_maxLeverage(baseAsset) < _abs(_currentLeverage(position, price, margin))), + (_maxLeverage(marketKey) < _abs(_currentLeverage(position, price, margin))), Status.InsufficientMargin ); } @@ -896,7 +905,7 @@ contract FuturesMarketBase is MixinFuturesMarketSettings, IFuturesMarketBaseType _recomputeFunding(price); _modifyPosition( msg.sender, - TradeParams({sizeDelta: sizeDelta, price: price, takerFee: _takerFee(baseAsset), makerFee: _makerFee(baseAsset)}) + TradeParams({sizeDelta: sizeDelta, price: price, takerFee: _takerFee(marketKey), makerFee: _makerFee(marketKey)}) ); } @@ -910,7 +919,7 @@ contract FuturesMarketBase is MixinFuturesMarketSettings, IFuturesMarketBaseType _recomputeFunding(price); _modifyPosition( msg.sender, - TradeParams({sizeDelta: -size, price: price, takerFee: _takerFee(baseAsset), makerFee: _makerFee(baseAsset)}) + TradeParams({sizeDelta: -size, price: price, takerFee: _takerFee(marketKey), makerFee: _makerFee(marketKey)}) ); } diff --git a/contracts/FuturesMarketData.sol b/contracts/FuturesMarketData.sol index c30b3e058b..d32d27ac27 100644 --- a/contracts/FuturesMarketData.sol +++ b/contracts/FuturesMarketData.sol @@ -23,6 +23,7 @@ contract FuturesMarketData { struct MarketSummary { address market; bytes32 asset; + bytes32 key; uint maxLeverage; uint price; uint marketSize; @@ -75,6 +76,7 @@ contract FuturesMarketData { struct MarketData { address market; bytes32 baseAsset; + bytes32 marketKey; FuturesMarketData.FeeRates feeRates; FuturesMarketData.MarketLimits limits; FuturesMarketData.FundingParameters fundingParameters; @@ -130,11 +132,11 @@ contract FuturesMarketData { }); } - function parameters(bytes32 baseAsset) external view returns (IFuturesMarketSettings.Parameters memory) { - return _parameters(baseAsset); + function parameters(bytes32 marketKey) external view returns (IFuturesMarketSettings.Parameters memory) { + return _parameters(marketKey); } - function _parameters(bytes32 baseAsset) internal view returns (IFuturesMarketSettings.Parameters memory) { + function _parameters(bytes32 marketKey) internal view returns (IFuturesMarketSettings.Parameters memory) { ( uint takerFee, uint makerFee, @@ -145,7 +147,7 @@ contract FuturesMarketData { uint maxMarketValueUSD, uint maxFundingRate, uint skewScaleUSD - ) = _futuresMarketSettings().parameters(baseAsset); + ) = _futuresMarketSettings().parameters(marketKey); return IFuturesMarketSettings.Parameters( takerFee, @@ -165,8 +167,9 @@ contract FuturesMarketData { MarketSummary[] memory summaries = new MarketSummary[](numMarkets); for (uint i; i < numMarkets; i++) { IFuturesMarket market = IFuturesMarket(markets[i]); + bytes32 marketKey = market.marketKey(); bytes32 baseAsset = market.baseAsset(); - IFuturesMarketSettings.Parameters memory params = _parameters(baseAsset); + IFuturesMarketSettings.Parameters memory params = _parameters(marketKey); (uint price, ) = market.assetPrice(); (uint debt, ) = market.marketDebt(); @@ -174,6 +177,7 @@ contract FuturesMarketData { summaries[i] = MarketSummary( address(market), baseAsset, + marketKey, params.maxLeverage, price, market.marketSize(), @@ -191,8 +195,8 @@ contract FuturesMarketData { return _marketSummaries(markets); } - function marketSummariesForAssets(bytes32[] calldata assets) external view returns (MarketSummary[] memory) { - return _marketSummaries(_futuresMarketManager().marketsForAssets(assets)); + function marketSummariesForKeys(bytes32[] calldata marketKeys) external view returns (MarketSummary[] memory) { + return _marketSummaries(_futuresMarketManager().marketsForKeys(marketKeys)); } function allMarketSummaries() external view returns (MarketSummary[] memory) { @@ -216,13 +220,15 @@ contract FuturesMarketData { (uint price, bool invalid) = market.assetPrice(); (uint marketDebt, ) = market.marketDebt(); bytes32 baseAsset = market.baseAsset(); + bytes32 marketKey = market.marketKey(); - IFuturesMarketSettings.Parameters memory params = _parameters(baseAsset); + IFuturesMarketSettings.Parameters memory params = _parameters(marketKey); return MarketData( address(market), baseAsset, + marketKey, FeeRates(params.takerFee, params.makerFee, params.takerFeeNextPrice, params.makerFeeNextPrice), MarketLimits(params.maxLeverage, params.maxMarketValueUSD), _fundingParameters(params), @@ -235,8 +241,8 @@ contract FuturesMarketData { return _marketDetails(market); } - function marketDetailsForAsset(bytes32 asset) external view returns (MarketData memory) { - return _marketDetails(IFuturesMarket(_futuresMarketManager().marketForAsset(asset))); + function marketDetailsForKey(bytes32 marketKey) external view returns (MarketData memory) { + return _marketDetails(IFuturesMarket(_futuresMarketManager().marketForKey(marketKey))); } function _position(IFuturesMarket market, address account) @@ -309,7 +315,7 @@ contract FuturesMarketData { return _positionDetails(market, account); } - function positionDetailsForAsset(bytes32 asset, address account) external view returns (PositionData memory) { - return _positionDetails(IFuturesMarket(_futuresMarketManager().marketForAsset(asset)), account); + function positionDetailsForMarketKey(bytes32 marketKey, address account) external view returns (PositionData memory) { + return _positionDetails(IFuturesMarket(_futuresMarketManager().marketForKey(marketKey)), account); } } diff --git a/contracts/FuturesMarketManager.sol b/contracts/FuturesMarketManager.sol index 3d889cea5c..b19b472efd 100644 --- a/contracts/FuturesMarketManager.sol +++ b/contracts/FuturesMarketManager.sol @@ -25,7 +25,7 @@ contract FuturesMarketManager is Owned, MixinResolver, IFuturesMarketManager { /* ========== STATE VARIABLES ========== */ AddressSetLib.AddressSet internal _markets; - mapping(bytes32 => address) public marketForAsset; + mapping(bytes32 => address) public marketForKey; /* ========== ADDRESS RESOLVER CONFIGURATION ========== */ @@ -80,20 +80,20 @@ contract FuturesMarketManager is Owned, MixinResolver, IFuturesMarketManager { return _markets.getPage(0, _markets.elements.length); } - function _marketsForAssets(bytes32[] memory assets) internal view returns (address[] memory) { - uint numAssets = assets.length; - address[] memory results = new address[](numAssets); - for (uint i; i < numAssets; i++) { - results[i] = marketForAsset[assets[i]]; + 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 asset strings. + * The market addresses for a given set of market key strings. */ - function marketsForAssets(bytes32[] calldata assets) external view returns (address[] memory) { - return _marketsForAssets(assets); + function marketsForKeys(bytes32[] calldata marketKeys) external view returns (address[] memory) { + return _marketsForKeys(marketKeys); } /* @@ -114,7 +114,7 @@ contract FuturesMarketManager is Owned, MixinResolver, IFuturesMarketManager { /* ========== MUTATIVE FUNCTIONS ========== */ /* - * Add a set of new markets. Reverts if some market's asset already has a market. + * 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; @@ -122,11 +122,13 @@ contract FuturesMarketManager is Owned, MixinResolver, IFuturesMarketManager { address market = marketsToAdd[i]; require(!_markets.contains(market), "Market already exists"); - bytes32 key = IFuturesMarket(market).baseAsset(); - require(marketForAsset[key] == address(0), "Market already exists for asset"); - marketForAsset[key] = market; + 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, key); + emit MarketAdded(market, baseAsset, key); } } @@ -136,11 +138,13 @@ contract FuturesMarketManager is Owned, MixinResolver, IFuturesMarketManager { address market = marketsToRemove[i]; require(market != address(0), "Unknown market"); - bytes32 key = IFuturesMarket(market).baseAsset(); - require(marketForAsset[key] != address(0), "Unknown market"); - delete marketForAsset[key]; + 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, key); + emit MarketRemoved(market, baseAsset, key); } } @@ -152,10 +156,10 @@ contract FuturesMarketManager is Owned, MixinResolver, IFuturesMarketManager { } /* - * Remove the markets for a given set of assets. Reverts if any asset has no associated market. + * Remove the markets for a given set of market keys. Reverts if any key has no associated market. */ - function removeMarketsByAsset(bytes32[] calldata assetsToRemove) external onlyOwner { - _removeMarkets(_marketsForAssets(assetsToRemove)); + function removeMarketsByKey(bytes32[] calldata marketKeysToRemove) external onlyOwner { + _removeMarkets(_marketsForKeys(marketKeysToRemove)); } /* @@ -219,7 +223,7 @@ contract FuturesMarketManager is Owned, MixinResolver, IFuturesMarketManager { /* ========== EVENTS ========== */ - event MarketAdded(address market, bytes32 indexed asset); + event MarketAdded(address market, bytes32 indexed asset, bytes32 indexed marketKey); - event MarketRemoved(address market, bytes32 indexed asset); + event MarketRemoved(address market, bytes32 indexed asset, bytes32 indexed marketKey); } diff --git a/contracts/FuturesMarketSettings.sol b/contracts/FuturesMarketSettings.sol index 907e2e06c5..2d02e29572 100644 --- a/contracts/FuturesMarketSettings.sol +++ b/contracts/FuturesMarketSettings.sol @@ -39,67 +39,67 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar /* * The fee charged when opening a position on the heavy side of a futures market. */ - function takerFee(bytes32 _baseAsset) external view returns (uint) { - return _takerFee(_baseAsset); + function takerFee(bytes32 _marketKey) external view returns (uint) { + return _takerFee(_marketKey); } /* * The fee charged when opening a position on the light side of a futures market. */ - function makerFee(bytes32 _baseAsset) public view returns (uint) { - return _makerFee(_baseAsset); + function makerFee(bytes32 _marketKey) public view returns (uint) { + return _makerFee(_marketKey); } /* * The fee charged when opening a position on the heavy side of a futures market using next price mechanism. */ - function takerFeeNextPrice(bytes32 _baseAsset) external view returns (uint) { - return _takerFeeNextPrice(_baseAsset); + function takerFeeNextPrice(bytes32 _marketKey) external view returns (uint) { + return _takerFeeNextPrice(_marketKey); } /* * The fee charged when opening a position on the light side of a futures market using next price mechanism. */ - function makerFeeNextPrice(bytes32 _baseAsset) public view returns (uint) { - return _makerFeeNextPrice(_baseAsset); + function makerFeeNextPrice(bytes32 _marketKey) public view returns (uint) { + return _makerFeeNextPrice(_marketKey); } /* * The number of price update rounds during which confirming next-price is allowed */ - function nextPriceConfirmWindow(bytes32 _baseAsset) public view returns (uint) { - return _nextPriceConfirmWindow(_baseAsset); + function nextPriceConfirmWindow(bytes32 _marketKey) public view returns (uint) { + return _nextPriceConfirmWindow(_marketKey); } /* * The maximum allowable leverage in a market. */ - function maxLeverage(bytes32 _baseAsset) public view returns (uint) { - return _maxLeverage(_baseAsset); + function maxLeverage(bytes32 _marketKey) public view returns (uint) { + return _maxLeverage(_marketKey); } /* * The maximum allowable notional value on each side of a market. */ - function maxMarketValueUSD(bytes32 _baseAsset) public view returns (uint) { - return _maxMarketValueUSD(_baseAsset); + function maxMarketValueUSD(bytes32 _marketKey) public view returns (uint) { + return _maxMarketValueUSD(_marketKey); } /* * The maximum theoretical funding rate per day charged by a market. */ - function maxFundingRate(bytes32 _baseAsset) public view returns (uint) { - return _maxFundingRate(_baseAsset); + function maxFundingRate(bytes32 _marketKey) public view returns (uint) { + return _maxFundingRate(_marketKey); } /* * The skew level at which the max funding rate will be charged. */ - function skewScaleUSD(bytes32 _baseAsset) public view returns (uint) { - return _skewScaleUSD(_baseAsset); + function skewScaleUSD(bytes32 _marketKey) public view returns (uint) { + return _skewScaleUSD(_marketKey); } - function parameters(bytes32 _baseAsset) + function parameters(bytes32 _marketKey) external view returns ( @@ -114,15 +114,15 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar uint skewScaleUSD ) { - takerFee = _takerFee(_baseAsset); - makerFee = _makerFee(_baseAsset); - takerFeeNextPrice = _takerFeeNextPrice(_baseAsset); - makerFeeNextPrice = _makerFeeNextPrice(_baseAsset); - nextPriceConfirmWindow = _nextPriceConfirmWindow(_baseAsset); - maxLeverage = _maxLeverage(_baseAsset); - maxMarketValueUSD = _maxMarketValueUSD(_baseAsset); - maxFundingRate = _maxFundingRate(_baseAsset); - skewScaleUSD = _skewScaleUSD(_baseAsset); + takerFee = _takerFee(_marketKey); + makerFee = _makerFee(_marketKey); + takerFeeNextPrice = _takerFeeNextPrice(_marketKey); + makerFeeNextPrice = _makerFeeNextPrice(_marketKey); + nextPriceConfirmWindow = _nextPriceConfirmWindow(_marketKey); + maxLeverage = _maxLeverage(_marketKey); + maxMarketValueUSD = _maxMarketValueUSD(_marketKey); + maxFundingRate = _maxFundingRate(_marketKey); + skewScaleUSD = _skewScaleUSD(_marketKey); } /* @@ -161,70 +161,70 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar /* ---------- Setters --------- */ function _setParameter( - bytes32 _baseAsset, + bytes32 _marketKey, bytes32 key, uint value ) internal { - _flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, keccak256(abi.encodePacked(_baseAsset, key)), value); - emit ParameterUpdated(_baseAsset, key, value); + _flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, keccak256(abi.encodePacked(_marketKey, key)), value); + emit ParameterUpdated(_marketKey, key, value); } - function setTakerFee(bytes32 _baseAsset, uint _takerFee) public onlyOwner { + function setTakerFee(bytes32 _marketKey, uint _takerFee) public onlyOwner { require(_takerFee <= 1e18, "taker fee greater than 1"); - _setParameter(_baseAsset, PARAMETER_TAKER_FEE, _takerFee); + _setParameter(_marketKey, PARAMETER_TAKER_FEE, _takerFee); } - function setMakerFee(bytes32 _baseAsset, uint _makerFee) public onlyOwner { + function setMakerFee(bytes32 _marketKey, uint _makerFee) public onlyOwner { require(_makerFee <= 1e18, "maker fee greater than 1"); - _setParameter(_baseAsset, PARAMETER_MAKER_FEE, _makerFee); + _setParameter(_marketKey, PARAMETER_MAKER_FEE, _makerFee); } - function setTakerFeeNextPrice(bytes32 _baseAsset, uint _takerFeeNextPrice) public onlyOwner { + function setTakerFeeNextPrice(bytes32 _marketKey, uint _takerFeeNextPrice) public onlyOwner { require(_takerFeeNextPrice <= 1e18, "taker fee greater than 1"); - _setParameter(_baseAsset, PARAMETER_TAKER_FEE_NEXT_PRICE, _takerFeeNextPrice); + _setParameter(_marketKey, PARAMETER_TAKER_FEE_NEXT_PRICE, _takerFeeNextPrice); } - function setMakerFeeNextPrice(bytes32 _baseAsset, uint _makerFeeNextPrice) public onlyOwner { + function setMakerFeeNextPrice(bytes32 _marketKey, uint _makerFeeNextPrice) public onlyOwner { require(_makerFeeNextPrice <= 1e18, "maker fee greater than 1"); - _setParameter(_baseAsset, PARAMETER_MAKER_FEE_NEXT_PRICE, _makerFeeNextPrice); + _setParameter(_marketKey, PARAMETER_MAKER_FEE_NEXT_PRICE, _makerFeeNextPrice); } - function setNextPriceConfirmWindow(bytes32 _baseAsset, uint _nextPriceConfirmWindow) public onlyOwner { - _setParameter(_baseAsset, PARAMETER_NEXT_PRICE_CONFIRM_WINDOW, _nextPriceConfirmWindow); + function setNextPriceConfirmWindow(bytes32 _marketKey, uint _nextPriceConfirmWindow) public onlyOwner { + _setParameter(_marketKey, PARAMETER_NEXT_PRICE_CONFIRM_WINDOW, _nextPriceConfirmWindow); } - function setMaxLeverage(bytes32 _baseAsset, uint _maxLeverage) public onlyOwner { - _setParameter(_baseAsset, PARAMETER_MAX_LEVERAGE, _maxLeverage); + function setMaxLeverage(bytes32 _marketKey, uint _maxLeverage) public onlyOwner { + _setParameter(_marketKey, PARAMETER_MAX_LEVERAGE, _maxLeverage); } - function setMaxMarketValueUSD(bytes32 _baseAsset, uint _maxMarketValueUSD) public onlyOwner { - _setParameter(_baseAsset, PARAMETER_MAX_MARKET_VALUE, _maxMarketValueUSD); + function setMaxMarketValueUSD(bytes32 _marketKey, uint _maxMarketValueUSD) public onlyOwner { + _setParameter(_marketKey, PARAMETER_MAX_MARKET_VALUE, _maxMarketValueUSD); } // Before altering parameters relevant to funding rates, outstanding funding on the underlying market // must be recomputed, otherwise already-accrued but unrealised funding in the market can change. - function _recomputeFunding(bytes32 _baseAsset) internal { - IFuturesMarket market = IFuturesMarket(_futuresMarketManager().marketForAsset(_baseAsset)); + function _recomputeFunding(bytes32 _marketKey) internal { + IFuturesMarket market = IFuturesMarket(_futuresMarketManager().marketForKey(_marketKey)); if (market.marketSize() > 0) { // only recompute funding when market has positions, this check is important for initial setup market.recomputeFunding(); } } - function setMaxFundingRate(bytes32 _baseAsset, uint _maxFundingRate) public onlyOwner { - _recomputeFunding(_baseAsset); - _setParameter(_baseAsset, PARAMETER_MAX_FUNDING_RATE, _maxFundingRate); + function setMaxFundingRate(bytes32 _marketKey, uint _maxFundingRate) public onlyOwner { + _recomputeFunding(_marketKey); + _setParameter(_marketKey, PARAMETER_MAX_FUNDING_RATE, _maxFundingRate); } - function setSkewScaleUSD(bytes32 _baseAsset, uint _skewScaleUSD) public onlyOwner { + function setSkewScaleUSD(bytes32 _marketKey, uint _skewScaleUSD) public onlyOwner { require(_skewScaleUSD > 0, "cannot set skew scale 0"); - _recomputeFunding(_baseAsset); - _setParameter(_baseAsset, PARAMETER_MIN_SKEW_SCALE, _skewScaleUSD); + _recomputeFunding(_marketKey); + _setParameter(_marketKey, PARAMETER_MIN_SKEW_SCALE, _skewScaleUSD); } function setParameters( - bytes32 _baseAsset, + bytes32 _marketKey, uint _takerFee, uint _makerFee, uint _takerFeeNextPrice, @@ -235,16 +235,16 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar uint _maxFundingRate, uint _skewScaleUSD ) external onlyOwner { - _recomputeFunding(_baseAsset); - setTakerFee(_baseAsset, _takerFee); - setMakerFee(_baseAsset, _makerFee); - setTakerFeeNextPrice(_baseAsset, _takerFeeNextPrice); - setMakerFeeNextPrice(_baseAsset, _makerFeeNextPrice); - setNextPriceConfirmWindow(_baseAsset, _nextPriceConfirmWindow); - setMaxLeverage(_baseAsset, _maxLeverage); - setMaxMarketValueUSD(_baseAsset, _maxMarketValueUSD); - setMaxFundingRate(_baseAsset, _maxFundingRate); - setSkewScaleUSD(_baseAsset, _skewScaleUSD); + _recomputeFunding(_marketKey); + setTakerFee(_marketKey, _takerFee); + setMakerFee(_marketKey, _makerFee); + setTakerFeeNextPrice(_marketKey, _takerFeeNextPrice); + setMakerFeeNextPrice(_marketKey, _makerFeeNextPrice); + setNextPriceConfirmWindow(_marketKey, _nextPriceConfirmWindow); + setMaxLeverage(_marketKey, _maxLeverage); + setMaxMarketValueUSD(_marketKey, _maxMarketValueUSD); + setMaxFundingRate(_marketKey, _maxFundingRate); + setSkewScaleUSD(_marketKey, _skewScaleUSD); } function setMinKeeperFee(uint _sUSD) external onlyOwner { @@ -271,7 +271,7 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar /* ========== EVENTS ========== */ - event ParameterUpdated(bytes32 indexed asset, bytes32 indexed parameter, uint value); + event ParameterUpdated(bytes32 indexed marketKey, bytes32 indexed parameter, uint value); event MinKeeperFeeUpdated(uint sUSD); event LiquidationFeeRatioUpdated(uint bps); event LiquidationBufferRatioUpdated(uint bps); diff --git a/contracts/MixinFuturesMarketSettings.sol b/contracts/MixinFuturesMarketSettings.sol index 2e7c2fc36d..4d84bdf7eb 100644 --- a/contracts/MixinFuturesMarketSettings.sol +++ b/contracts/MixinFuturesMarketSettings.sol @@ -54,44 +54,44 @@ contract MixinFuturesMarketSettings is MixinResolver { /* ---------- Internals ---------- */ - function _parameter(bytes32 _baseAsset, bytes32 key) internal view returns (uint value) { - return _flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, keccak256(abi.encodePacked(_baseAsset, key))); + function _parameter(bytes32 _marketKey, bytes32 key) internal view returns (uint value) { + return _flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, keccak256(abi.encodePacked(_marketKey, key))); } - function _takerFee(bytes32 _baseAsset) internal view returns (uint) { - return _parameter(_baseAsset, PARAMETER_TAKER_FEE); + function _takerFee(bytes32 _marketKey) internal view returns (uint) { + return _parameter(_marketKey, PARAMETER_TAKER_FEE); } - function _makerFee(bytes32 _baseAsset) internal view returns (uint) { - return _parameter(_baseAsset, PARAMETER_MAKER_FEE); + function _makerFee(bytes32 _marketKey) internal view returns (uint) { + return _parameter(_marketKey, PARAMETER_MAKER_FEE); } - function _takerFeeNextPrice(bytes32 _baseAsset) internal view returns (uint) { - return _parameter(_baseAsset, PARAMETER_TAKER_FEE_NEXT_PRICE); + function _takerFeeNextPrice(bytes32 _marketKey) internal view returns (uint) { + return _parameter(_marketKey, PARAMETER_TAKER_FEE_NEXT_PRICE); } - function _makerFeeNextPrice(bytes32 _baseAsset) internal view returns (uint) { - return _parameter(_baseAsset, PARAMETER_MAKER_FEE_NEXT_PRICE); + function _makerFeeNextPrice(bytes32 _marketKey) internal view returns (uint) { + return _parameter(_marketKey, PARAMETER_MAKER_FEE_NEXT_PRICE); } - function _nextPriceConfirmWindow(bytes32 _baseAsset) internal view returns (uint) { - return _parameter(_baseAsset, PARAMETER_NEXT_PRICE_CONFIRM_WINDOW); + function _nextPriceConfirmWindow(bytes32 _marketKey) internal view returns (uint) { + return _parameter(_marketKey, PARAMETER_NEXT_PRICE_CONFIRM_WINDOW); } - function _maxLeverage(bytes32 _baseAsset) internal view returns (uint) { - return _parameter(_baseAsset, PARAMETER_MAX_LEVERAGE); + function _maxLeverage(bytes32 _marketKey) internal view returns (uint) { + return _parameter(_marketKey, PARAMETER_MAX_LEVERAGE); } - function _maxMarketValueUSD(bytes32 _baseAsset) internal view returns (uint) { - return _parameter(_baseAsset, PARAMETER_MAX_MARKET_VALUE); + function _maxMarketValueUSD(bytes32 _marketKey) internal view returns (uint) { + return _parameter(_marketKey, PARAMETER_MAX_MARKET_VALUE); } - function _skewScaleUSD(bytes32 _baseAsset) internal view returns (uint) { - return _parameter(_baseAsset, PARAMETER_MIN_SKEW_SCALE); + function _skewScaleUSD(bytes32 _marketKey) internal view returns (uint) { + return _parameter(_marketKey, PARAMETER_MIN_SKEW_SCALE); } - function _maxFundingRate(bytes32 _baseAsset) internal view returns (uint) { - return _parameter(_baseAsset, PARAMETER_MAX_FUNDING_RATE); + function _maxFundingRate(bytes32 _marketKey) internal view returns (uint) { + return _parameter(_marketKey, PARAMETER_MAX_FUNDING_RATE); } function _minKeeperFee() internal view returns (uint) { diff --git a/contracts/MixinFuturesNextPriceOrders.sol b/contracts/MixinFuturesNextPriceOrders.sol index 238b1017f8..3385ee0602 100644 --- a/contracts/MixinFuturesNextPriceOrders.sol +++ b/contracts/MixinFuturesNextPriceOrders.sol @@ -43,8 +43,8 @@ contract MixinFuturesNextPriceOrders is FuturesMarketBase { TradeParams({ sizeDelta: sizeDelta, price: price, - takerFee: _takerFeeNextPrice(baseAsset), - makerFee: _makerFeeNextPrice(baseAsset) + takerFee: _takerFeeNextPrice(marketKey), + makerFee: _makerFeeNextPrice(marketKey) }); (, , Status status) = _postTradeDetails(position, params); _revertIfError(status); @@ -191,8 +191,8 @@ contract MixinFuturesNextPriceOrders is FuturesMarketBase { TradeParams({ sizeDelta: order.sizeDelta, // using the pastPrice from the target roundId price: pastPrice, // the funding is applied only from order confirmation time - takerFee: _takerFeeNextPrice(baseAsset), - makerFee: _makerFeeNextPrice(baseAsset) + takerFee: _takerFeeNextPrice(marketKey), + makerFee: _makerFeeNextPrice(marketKey) }) ); @@ -214,7 +214,7 @@ contract MixinFuturesNextPriceOrders is FuturesMarketBase { // confirmation window is over when current roundId is more than nextPriceConfirmWindow // rounds after target roundId function _confirmationWindowOver(uint currentRoundId, uint targetRoundId) internal view returns (bool) { - return (currentRoundId > targetRoundId) && (currentRoundId - targetRoundId > _nextPriceConfirmWindow(baseAsset)); // don't underflow + return (currentRoundId > targetRoundId) && (currentRoundId - targetRoundId > _nextPriceConfirmWindow(marketKey)); // don't underflow } // convenience view to access exchangeRates contract for methods that are not exposed @@ -226,8 +226,8 @@ contract MixinFuturesNextPriceOrders is FuturesMarketBase { // calculate the commitFee, which is the fee that would be charged on the order if it was spot function _nextPriceCommitDeposit(TradeParams memory params) internal view returns (uint) { // modify params to spot fee - params.takerFee = _takerFee(baseAsset); - params.makerFee = _makerFee(baseAsset); + params.takerFee = _takerFee(marketKey); + params.makerFee = _makerFee(marketKey); // Commit fee is equal to the spot fee that would be paid. // This is to prevent free cancellation manipulations (by e.g. withdrawing the margin). // The dynamic fee rate is passed as 0 since for the purposes of the commitment deposit diff --git a/contracts/MixinFuturesViews.sol b/contracts/MixinFuturesViews.sol index 9d4aae6f7d..159193c89c 100644 --- a/contracts/MixinFuturesViews.sol +++ b/contracts/MixinFuturesViews.sol @@ -147,8 +147,8 @@ contract MixinFuturesViews is FuturesMarketBase { TradeParams({ sizeDelta: sizeDelta, price: price, - takerFee: _takerFee(baseAsset), - makerFee: _makerFee(baseAsset) + takerFee: _takerFee(marketKey), + makerFee: _makerFee(marketKey) }); return (_orderFee(params, dynamicFeeRate), isInvalid || tooVolatile); } @@ -178,8 +178,8 @@ contract MixinFuturesViews is FuturesMarketBase { TradeParams({ sizeDelta: sizeDelta, price: price, - takerFee: _takerFee(baseAsset), - makerFee: _makerFee(baseAsset) + takerFee: _takerFee(marketKey), + makerFee: _makerFee(marketKey) }); (Position memory newPosition, uint fee_, Status status_) = _postTradeDetails(positions[sender], params); diff --git a/contracts/interfaces/IFuturesMarket.sol b/contracts/interfaces/IFuturesMarket.sol index 79b6e71372..42649cbc9d 100644 --- a/contracts/interfaces/IFuturesMarket.sol +++ b/contracts/interfaces/IFuturesMarket.sol @@ -7,6 +7,8 @@ interface IFuturesMarket { /* ---------- Market Details ---------- */ + function marketKey() external view returns (bytes32 key); + function baseAsset() external view returns (bytes32 key); function marketSize() external view returns (uint128 size); diff --git a/contracts/interfaces/IFuturesMarketManager.sol b/contracts/interfaces/IFuturesMarketManager.sol index 5540927cfa..1a2f93af30 100644 --- a/contracts/interfaces/IFuturesMarketManager.sol +++ b/contracts/interfaces/IFuturesMarketManager.sol @@ -1,6 +1,5 @@ pragma solidity ^0.5.16; - interface IFuturesMarketManager { function markets(uint index, uint pageSize) external view returns (address[] memory); @@ -8,9 +7,9 @@ interface IFuturesMarketManager { function allMarkets() external view returns (address[] memory); - function marketForAsset(bytes32 asset) external view returns (address); + function marketForKey(bytes32 marketKey) external view returns (address); - function marketsForAssets(bytes32[] calldata assets) external view returns (address[] memory); + function marketsForKeys(bytes32[] calldata marketKeys) external view returns (address[] memory); function totalDebt() external view returns (uint debt, bool isInvalid); } diff --git a/contracts/test-helpers/MockFuturesMarket.sol b/contracts/test-helpers/MockFuturesMarket.sol index 4d7f1fd60b..bdaadc8134 100644 --- a/contracts/test-helpers/MockFuturesMarket.sol +++ b/contracts/test-helpers/MockFuturesMarket.sol @@ -8,6 +8,7 @@ interface IFuturesMarketManagerInternal { contract MockFuturesMarket { bytes32 public baseAsset; + bytes32 public marketKey; uint public debt; bool public invalid; IFuturesMarketManagerInternal public manager; @@ -15,11 +16,13 @@ contract MockFuturesMarket { constructor( IFuturesMarketManagerInternal _manager, bytes32 _baseAsset, + bytes32 _marketKey, uint _debt, bool _invalid ) public { manager = _manager; baseAsset = _baseAsset; + marketKey = _marketKey; debt = _debt; invalid = _invalid; } @@ -32,6 +35,10 @@ contract MockFuturesMarket { baseAsset = _baseAsset; } + function setMarketKey(bytes32 _marketKey) external { + marketKey = _marketKey; + } + function setMarketDebt(uint _debt) external { debt = _debt; } diff --git a/contracts/test-helpers/TestableFuturesMarket.sol b/contracts/test-helpers/TestableFuturesMarket.sol index 97527420d1..befb94f819 100644 --- a/contracts/test-helpers/TestableFuturesMarket.sol +++ b/contracts/test-helpers/TestableFuturesMarket.sol @@ -3,7 +3,11 @@ pragma solidity ^0.5.16; import "../FuturesMarket.sol"; contract TestableFuturesMarket is FuturesMarket { - constructor(address _resolver, bytes32 _baseAsset) public FuturesMarket(_resolver, _baseAsset) {} + constructor( + address _resolver, + bytes32 _baseAsset, + bytes32 _marketKey + ) public FuturesMarket(_resolver, _baseAsset, _marketKey) {} function entryDebtCorrection() external view returns (int) { return _entryDebtCorrection; @@ -15,7 +19,7 @@ contract TestableFuturesMarket is FuturesMarket { } function maxFundingRate() external view returns (uint) { - return _maxFundingRate(baseAsset); + return _maxFundingRate(marketKey); } /* @@ -32,7 +36,7 @@ contract TestableFuturesMarket is FuturesMarket { { uint price; (price, invalid) = assetPrice(); - int sizeLimit = int(_maxMarketValueUSD(baseAsset)).divideDecimal(int(price)); + int sizeLimit = int(_maxMarketValueUSD(marketKey)).divideDecimal(int(price)); (uint longSize, uint shortSize) = marketSizes(); long = uint(sizeLimit.sub(_min(int(longSize), sizeLimit))); short = uint(sizeLimit.sub(_min(int(shortSize), sizeLimit))); diff --git a/publish/deployed/kovan-ovm-futures/futures-markets.json b/publish/deployed/kovan-ovm-futures/futures-markets.json index 1d239c14a3..1006d51d52 100644 --- a/publish/deployed/kovan-ovm-futures/futures-markets.json +++ b/publish/deployed/kovan-ovm-futures/futures-markets.json @@ -1,5 +1,6 @@ [ { + "marketKey": "sBTC", "asset": "sBTC", "takerFee": "0.003", "makerFee": "0.001", @@ -12,6 +13,7 @@ "skewScaleUSD": "50000000" }, { + "marketKey": "sETH", "asset": "sETH", "takerFee": "0.003", "makerFee": "0.001", @@ -24,6 +26,7 @@ "skewScaleUSD": "50000000" }, { + "marketKey": "sLINK", "asset": "sLINK", "takerFee": "0.003", "makerFee": "0.001", diff --git a/publish/deployed/local-ovm/futures-markets.json b/publish/deployed/local-ovm/futures-markets.json index 1d239c14a3..1006d51d52 100644 --- a/publish/deployed/local-ovm/futures-markets.json +++ b/publish/deployed/local-ovm/futures-markets.json @@ -1,5 +1,6 @@ [ { + "marketKey": "sBTC", "asset": "sBTC", "takerFee": "0.003", "makerFee": "0.001", @@ -12,6 +13,7 @@ "skewScaleUSD": "50000000" }, { + "marketKey": "sETH", "asset": "sETH", "takerFee": "0.003", "makerFee": "0.001", @@ -24,6 +26,7 @@ "skewScaleUSD": "50000000" }, { + "marketKey": "sLINK", "asset": "sLINK", "takerFee": "0.003", "makerFee": "0.001", diff --git a/publish/src/commands/deploy/configure-futures.js b/publish/src/commands/deploy/configure-futures.js index 4032503089..2326840b79 100644 --- a/publish/src/commands/deploy/configure-futures.js +++ b/publish/src/commands/deploy/configure-futures.js @@ -65,28 +65,6 @@ module.exports = async ({ comment: 'Set the minimum reward for liquidating a futures position (SIP-80)', }); - // const futuresAssets = futuresMarkets.map(x => x.asset); - - // Some market parameters invoke a recomputation of the funding rate, and - // thus require exchange rates to be fresh. We assume production networks - // have fresh funding rates at the time of deployment. - - // if (freshDeploy || network === 'local') { - // const { timestamp } = await deployer.provider.getBlock(); - // const DUMMY_PRICE = parseEther('1').toString(); - - // console.log(gray(`Updating ExchangeRates for futures assets: ` + futuresAssets.join(', '))); - - // for (const key of futuresAssets.map(toBytes32)) { - // await runStep({ - // contract: 'ExchangeRates', - // target: exchangeRates, - // write: `updateRates`, - // writeArg: [[key], [DUMMY_PRICE], timestamp], - // }); - // } - // } - // // Configure parameters for each market. // @@ -94,6 +72,7 @@ module.exports = async ({ for (const market of Object.values(futuresMarkets)) { const { asset, + marketKey, takerFee, makerFee, takerFeeNextPrice, @@ -107,7 +86,7 @@ module.exports = async ({ console.log(gray(`\n --- MARKET ${asset} ---\n`)); - const baseAsset = toBytes32(asset); + const marketKeyBytes = toBytes32(marketKey); const settings = { takerFee: w3utils.toWei(takerFee), @@ -129,10 +108,10 @@ module.exports = async ({ contract: 'FuturesMarketSettings', target: futuresMarketSettings, read: setting, - readArg: [baseAsset], + readArg: [marketKeyBytes], expected: input => input === value, write: `set${capSetting}`, - writeArg: [baseAsset, value], + writeArg: [marketKeyBytes, value], }); } } diff --git a/publish/src/commands/deploy/deploy-futures.js b/publish/src/commands/deploy/deploy-futures.js index 8dc4195288..ff6db20df1 100644 --- a/publish/src/commands/deploy/deploy-futures.js +++ b/publish/src/commands/deploy/deploy-futures.js @@ -52,14 +52,15 @@ module.exports = async ({ const deployedFuturesMarkets = []; - for (const asset of futuresMarkets.map(x => x.asset)) { - const marketName = 'FuturesMarket' + asset.slice('1'); // remove s prefix - const baseAsset = toBytes32(asset); + for (const marketConfig of futuresMarkets) { + const baseAsset = toBytes32(marketConfig.asset); + const marketKey = toBytes32(marketConfig.marketKey); + const marketName = 'FuturesMarket' + marketConfig.marketKey.slice('1'); // remove s prefix const futuresMarket = await deployer.deployContract({ name: marketName, source: 'FuturesMarket', - args: [addressOf(ReadProxyAddressResolver), baseAsset], + args: [addressOf(ReadProxyAddressResolver), baseAsset, marketKey], }); if (futuresMarket) { diff --git a/test/contracts/DebtCache.js b/test/contracts/DebtCache.js index de104aa9e2..fd2d84bc7c 100644 --- a/test/contracts/DebtCache.js +++ b/test/contracts/DebtCache.js @@ -650,7 +650,13 @@ contract('DebtCache', async accounts => { const market = await setupContract({ accounts, contract: 'MockFuturesMarket', - args: [futuresMarketManager.address, toBytes32('sLINK'), toUnit('1000'), false], + args: [ + futuresMarketManager.address, + toBytes32('sLINK'), + toBytes32('sLINK'), + toUnit('1000'), + false, + ], skipPostDeploy: true, }); await futuresMarketManager.addMarkets([market.address], { from: owner }); diff --git a/test/contracts/FuturesMarket.js b/test/contracts/FuturesMarket.js index 8dd9c40f73..c6eb1f1b57 100644 --- a/test/contracts/FuturesMarket.js +++ b/test/contracts/FuturesMarket.js @@ -54,6 +54,9 @@ contract('FuturesMarket', accounts => { const noBalance = accounts[5]; const traderInitialBalance = toUnit(1000000); + const marketKeySuffix = '-perp'; + + const marketKey = toBytes32('sBTC' + marketKeySuffix); const baseAsset = toBytes32('sBTC'); const takerFee = toUnit('0.003'); const makerFee = toUnit('0.001'); @@ -120,7 +123,7 @@ contract('FuturesMarket', accounts => { contracts: [ 'FuturesMarketManager', 'FuturesMarketSettings', - 'FuturesMarketBTC', + { contract: 'FuturesMarketBTC', properties: { perpSuffix: marketKeySuffix } }, 'AddressResolver', 'FeePool', 'ExchangeRates', @@ -179,7 +182,8 @@ contract('FuturesMarket', accounts => { it('static parameters are set properly at construction', async () => { assert.equal(await futuresMarket.baseAsset(), baseAsset); - const parameters = await futuresMarketSettings.parameters(baseAsset); + assert.equal(await futuresMarket.marketKey(), marketKey); + const parameters = await futuresMarketSettings.parameters(marketKey); assert.bnEqual(parameters.takerFee, takerFee); assert.bnEqual(parameters.makerFee, makerFee); assert.bnEqual(parameters.takerFeeNextPrice, takerFeeNextPrice); @@ -200,7 +204,7 @@ contract('FuturesMarket', accounts => { }); it('market size and skew', async () => { - const minScale = (await futuresMarketSettings.parameters(baseAsset)).skewScaleUSD; + const minScale = (await futuresMarketSettings.parameters(marketKey)).skewScaleUSD; const price = 100; let sizes = await futuresMarket.marketSizes(); let marketSkew = await futuresMarket.marketSkew(); @@ -1192,7 +1196,7 @@ contract('FuturesMarket', accounts => { const leverage = side === 'long' ? toUnit('10') : toUnit('-10'); beforeEach(async () => { - await futuresMarketSettings.setMaxMarketValueUSD(baseAsset, toUnit('10000'), { + await futuresMarketSettings.setMaxMarketValueUSD(marketKey, toUnit('10000'), { from: owner, }); await setPrice(baseAsset, toUnit('1')); @@ -2336,7 +2340,7 @@ contract('FuturesMarket', accounts => { assert.bnEqual(await futuresMarket.currentFundingRate(), toUnit(0)); const minScale = divideDecimal( - (await futuresMarketSettings.parameters(baseAsset)).skewScaleUSD, + (await futuresMarketSettings.parameters(marketKey)).skewScaleUSD, price ); const maxFundingRate = await futuresMarket.maxFundingRate(); @@ -2397,12 +2401,12 @@ contract('FuturesMarket', accounts => { const expectedFunding = toUnit('-0.002'); // 8 * 250 / 100_000 skew * 0.1 max funding rate assert.bnEqual(await futuresMarket.currentFundingRate(), expectedFunding); - await futuresMarketSettings.setMaxFundingRate(baseAsset, toUnit('0.2'), { from: owner }); + await futuresMarketSettings.setMaxFundingRate(marketKey, toUnit('0.2'), { from: owner }); assert.bnEqual( await futuresMarket.currentFundingRate(), multiplyDecimal(expectedFunding, toUnit(2)) ); - await futuresMarketSettings.setMaxFundingRate(baseAsset, toUnit('0'), { from: owner }); + await futuresMarketSettings.setMaxFundingRate(marketKey, toUnit('0'), { from: owner }); assert.bnEqual(await futuresMarket.currentFundingRate(), toUnit('0')); }); @@ -2428,7 +2432,7 @@ contract('FuturesMarket', accounts => { const expectedFunding = toUnit('0.002'); // 8 * 250 / 100_000 skew * 0.1 max funding rate assert.bnEqual(await futuresMarket.currentFundingRate(), expectedFunding); - await futuresMarketSettings.setSkewScaleUSD(baseAsset, toUnit(500 * initialPrice), { + await futuresMarketSettings.setSkewScaleUSD(marketKey, toUnit(500 * initialPrice), { from: owner, }); assert.bnEqual( @@ -2436,7 +2440,7 @@ contract('FuturesMarket', accounts => { multiplyDecimal(expectedFunding, toUnit('2')) ); - await futuresMarketSettings.setSkewScaleUSD(baseAsset, toUnit(250 * initialPrice), { + await futuresMarketSettings.setSkewScaleUSD(marketKey, toUnit(250 * initialPrice), { from: owner, }); assert.bnEqual( @@ -2444,7 +2448,7 @@ contract('FuturesMarket', accounts => { multiplyDecimal(expectedFunding, toUnit('4')) ); - await futuresMarketSettings.setSkewScaleUSD(baseAsset, toUnit(2000 * initialPrice), { + await futuresMarketSettings.setSkewScaleUSD(marketKey, toUnit(2000 * initialPrice), { from: owner, }); assert.bnEqual( @@ -2453,7 +2457,7 @@ contract('FuturesMarket', accounts => { ); // skewScaleUSD is below market size - await futuresMarketSettings.setSkewScaleUSD(baseAsset, toUnit(4 * price), { from: owner }); + await futuresMarketSettings.setSkewScaleUSD(marketKey, toUnit(4 * price), { from: owner }); assert.bnEqual(await futuresMarket.currentFundingRate(), toUnit('0.1')); // max funding rate }); @@ -2462,7 +2466,7 @@ contract('FuturesMarket', accounts => { describe(`${side}`, () => { beforeEach(async () => { - await futuresMarketSettings.setMaxMarketValueUSD(baseAsset, toUnit('100000'), { + await futuresMarketSettings.setMaxMarketValueUSD(marketKey, toUnit('100000'), { from: owner, }); }); @@ -2483,7 +2487,7 @@ contract('FuturesMarket', accounts => { it('Different skew rates induce proportional funding levels', async () => { // skewScaleUSD is below actual skew const skewScaleUSD = toUnit(100 * 100); - await futuresMarketSettings.setSkewScaleUSD(baseAsset, skewScaleUSD, { from: owner }); + await futuresMarketSettings.setSkewScaleUSD(marketKey, skewScaleUSD, { from: owner }); const traderPos = leverage.mul(toBN('10')); await transferMarginAndModifyPosition({ @@ -2497,10 +2501,10 @@ contract('FuturesMarket', accounts => { const points = 5; - setPrice(baseAsset, toUnit('100')); + await setPrice(baseAsset, toUnit('100')); for (const maxFR of ['0.1', '0.2', '0.05'].map(toUnit)) { - await futuresMarketSettings.setMaxFundingRate(baseAsset, maxFR, { from: owner }); + await futuresMarketSettings.setMaxFundingRate(marketKey, maxFR, { from: owner }); for (let i = points; i >= 0; i--) { // now lerp from leverage*k to leverage @@ -2552,9 +2556,9 @@ contract('FuturesMarket', accounts => { await setPrice(baseAsset, price); // pause the market - await systemStatus.suspendFuturesMarket(baseAsset, '0', { from: owner }); + await systemStatus.suspendFuturesMarket(marketKey, '0', { from: owner }); // set funding rate to 0 - await futuresMarketSettings.setMaxFundingRate(baseAsset, toUnit('0'), { from: owner }); + await futuresMarketSettings.setMaxFundingRate(marketKey, toUnit('0'), { from: owner }); // check accrued const accrued = (await futuresMarket.accruedFunding(trader))[0]; @@ -2568,9 +2572,9 @@ contract('FuturesMarket', accounts => { assert.bnEqual((await futuresMarket.accruedFunding(trader))[0], accrued); // set funding rate to 0.1 again - await futuresMarketSettings.setMaxFundingRate(baseAsset, toUnit('0.1'), { from: owner }); + await futuresMarketSettings.setMaxFundingRate(marketKey, toUnit('0.1'), { from: owner }); // resume - await systemStatus.resumeFuturesMarket(baseAsset, { from: owner }); + await systemStatus.resumeFuturesMarket(marketKey, { from: owner }); // 1 day await fastForward(24 * 60 * 60); @@ -2628,7 +2632,7 @@ contract('FuturesMarket', accounts => { it('Funding sequence is recomputed by setting funding rate parameters', async () => { // no skewScaleUSD - await futuresMarketSettings.setSkewScaleUSD(baseAsset, toUnit('10000'), { from: owner }); + await futuresMarketSettings.setSkewScaleUSD(marketKey, toUnit('10000'), { from: owner }); assert.bnEqual( await futuresMarket.fundingSequenceLength(), @@ -2638,7 +2642,7 @@ contract('FuturesMarket', accounts => { await setPrice(baseAsset, toUnit('100')); assert.bnClose((await futuresMarket.unrecordedFunding())[0], toUnit('-6'), toUnit('0.01')); - await futuresMarketSettings.setMaxFundingRate(baseAsset, toUnit('0.2'), { from: owner }); + await futuresMarketSettings.setMaxFundingRate(marketKey, toUnit('0.2'), { from: owner }); const time = await currentTime(); assert.bnEqual( @@ -2923,7 +2927,7 @@ contract('FuturesMarket', accounts => { }); it('Liquidation price is accurate with funding', async () => { - await futuresMarketSettings.setSkewScaleUSD(baseAsset, toUnit('10000'), { from: owner }); + await futuresMarketSettings.setSkewScaleUSD(marketKey, toUnit('10000'), { from: owner }); await setPrice(baseAsset, toUnit('250')); // Submit orders that induce -0.05 funding rate @@ -2950,7 +2954,7 @@ contract('FuturesMarket', accounts => { }); it('Liquidation price reports invalidity properly', async () => { - await futuresMarketSettings.setSkewScaleUSD(baseAsset, toUnit('12500'), { from: owner }); + await futuresMarketSettings.setSkewScaleUSD(marketKey, toUnit('12500'), { from: owner }); await setPrice(baseAsset, toUnit('250')); await futuresMarket.transferMargin(toUnit('1500'), { from: trader }); @@ -3098,7 +3102,7 @@ contract('FuturesMarket', accounts => { }); it('Liquidation properly affects the overall market parameters (long case)', async () => { - await futuresMarketSettings.setSkewScaleUSD(baseAsset, toUnit('20000'), { from: owner }); + await futuresMarketSettings.setSkewScaleUSD(marketKey, toUnit('20000'), { from: owner }); await fastForward(24 * 60 * 60); // wait one day to accrue a bit of funding @@ -3148,7 +3152,7 @@ contract('FuturesMarket', accounts => { }); it('Liquidation properly affects the overall market parameters (short case)', async () => { - await futuresMarketSettings.setSkewScaleUSD(baseAsset, toUnit('20000'), { from: owner }); + await futuresMarketSettings.setSkewScaleUSD(marketKey, toUnit('20000'), { from: owner }); await fastForward(24 * 60 * 60); // wait one day to accrue a bit of funding @@ -3537,15 +3541,15 @@ contract('FuturesMarket', accounts => { const everythingReverts = async () => { it('then futuresMarketSettings parameter changes revert', async () => { await assert.revert( - futuresMarketSettings.setMaxFundingRate(baseAsset, 0, { from: owner }), + futuresMarketSettings.setMaxFundingRate(marketKey, 0, { from: owner }), 'Invalid price' ); await assert.revert( - futuresMarketSettings.setSkewScaleUSD(baseAsset, toUnit('100'), { from: owner }), + futuresMarketSettings.setSkewScaleUSD(marketKey, toUnit('100'), { from: owner }), 'Invalid price' ); await assert.revert( - futuresMarketSettings.setParameters(baseAsset, 0, 0, 0, 0, 0, 0, 0, 0, 0, { + futuresMarketSettings.setParameters(marketKey, 0, 0, 0, 0, 0, 0, 0, 0, 0, { from: owner, }), 'Invalid price' @@ -3644,9 +3648,9 @@ contract('FuturesMarket', accounts => { }); it('then futuresMarketSettings parameter changes do not revert', async () => { - await futuresMarketSettings.setMaxFundingRate(baseAsset, 0, { from: owner }); - await futuresMarketSettings.setSkewScaleUSD(baseAsset, toUnit('100'), { from: owner }); - await futuresMarketSettings.setParameters(baseAsset, 0, 0, 0, 0, 0, 0, 0, 0, 1, { + await futuresMarketSettings.setMaxFundingRate(marketKey, 0, { from: owner }); + await futuresMarketSettings.setSkewScaleUSD(marketKey, toUnit('100'), { from: owner }); + await futuresMarketSettings.setParameters(marketKey, 0, 0, 0, 0, 0, 0, 0, 0, 1, { from: owner, }); }); @@ -3654,7 +3658,7 @@ contract('FuturesMarket', accounts => { it('futuresMarketSettings parameter changes still revert if price is invalid', async () => { await setPrice(baseAsset, toUnit('1'), false); // circuit breaker will revert await assert.revert( - futuresMarketSettings.setParameters(baseAsset, 0, 0, 0, 0, 0, 0, 0, 0, 1, { + futuresMarketSettings.setParameters(marketKey, 0, 0, 0, 0, 0, 0, 0, 0, 1, { from: owner, }), 'Invalid price' @@ -3700,7 +3704,7 @@ contract('FuturesMarket', accounts => { await futuresMarket.transferMargin(toUnit('1000'), { from: trader }); await futuresMarket.modifyPosition(toUnit('1'), { from: trader }); // suspend - await systemStatus.suspendFuturesMarket(baseAsset, toUnit(0), { from: owner }); + await systemStatus.suspendFuturesMarket(marketKey, toUnit(0), { from: owner }); }); // check reverts are as expecte @@ -3709,7 +3713,7 @@ contract('FuturesMarket', accounts => { describe('when market is resumed', () => { beforeEach(async () => { // suspend - await systemStatus.resumeFuturesMarket(baseAsset, { from: owner }); + await systemStatus.resumeFuturesMarket(marketKey, { from: owner }); }); it('then mutative market actions work', async () => { @@ -3798,9 +3802,9 @@ contract('FuturesMarket', accounts => { }); it('futuresMarketSettings parameter changes do not revert', async () => { - await futuresMarketSettings.setMaxFundingRate(baseAsset, 0, { from: owner }); - await futuresMarketSettings.setSkewScaleUSD(baseAsset, toUnit('100'), { from: owner }); - await futuresMarketSettings.setParameters(baseAsset, 0, 0, 0, 0, 0, 0, 0, 0, 1, { + await futuresMarketSettings.setMaxFundingRate(marketKey, 0, { from: owner }); + await futuresMarketSettings.setSkewScaleUSD(marketKey, toUnit('100'), { from: owner }); + await futuresMarketSettings.setParameters(marketKey, 0, 0, 0, 0, 0, 0, 0, 0, 1, { from: owner, }); }); @@ -3870,9 +3874,9 @@ contract('FuturesMarket', accounts => { }); it('futuresMarketSettings parameter changes do not revert', async () => { - await futuresMarketSettings.setMaxFundingRate(baseAsset, 0, { from: owner }); - await futuresMarketSettings.setSkewScaleUSD(baseAsset, toUnit('100'), { from: owner }); - await futuresMarketSettings.setParameters(baseAsset, 0, 0, 0, 0, 0, 0, 0, 0, 1, { + await futuresMarketSettings.setMaxFundingRate(marketKey, 0, { from: owner }); + await futuresMarketSettings.setSkewScaleUSD(marketKey, toUnit('100'), { from: owner }); + await futuresMarketSettings.setParameters(marketKey, 0, 0, 0, 0, 0, 0, 0, 0, 1, { from: owner, }); }); diff --git a/test/contracts/FuturesMarket.nextPrice.js b/test/contracts/FuturesMarket.nextPrice.js index 9341626e86..ebcbcec7cd 100644 --- a/test/contracts/FuturesMarket.nextPrice.js +++ b/test/contracts/FuturesMarket.nextPrice.js @@ -9,7 +9,6 @@ const { getDecodedLogs, decodedEventEqual, updateAggregatorRates } = require('./ contract('FuturesMarket MixinFuturesNextPriceOrders', accounts => { let futuresMarketSettings, - // futuresMarketManager, futuresMarket, exchangeRates, exchangeCircuitBreaker, @@ -24,6 +23,9 @@ contract('FuturesMarket MixinFuturesNextPriceOrders', accounts => { const trader3 = accounts[4]; const traderInitialBalance = toUnit(1000000); + const marketKeySuffix = '-perp'; + + const marketKey = toBytes32('sBTC' + marketKeySuffix); const baseAsset = toBytes32('sBTC'); const takerFeeNextPrice = toUnit('0.0005'); const makerFeeNextPrice = toUnit('0.0001'); @@ -42,7 +44,6 @@ contract('FuturesMarket MixinFuturesNextPriceOrders', accounts => { before(async () => { ({ FuturesMarketSettings: futuresMarketSettings, - // FuturesMarketManager: futuresMarketManager, FuturesMarketBTC: futuresMarket, ExchangeRates: exchangeRates, ExchangeCircuitBreaker: exchangeCircuitBreaker, @@ -56,7 +57,7 @@ contract('FuturesMarket MixinFuturesNextPriceOrders', accounts => { contracts: [ 'FuturesMarketManager', 'FuturesMarketSettings', - 'FuturesMarketBTC', + { contract: 'FuturesMarketBTC', properties: { perpSuffix: marketKeySuffix } }, 'AddressResolver', 'FeePool', 'ExchangeRates', @@ -174,7 +175,7 @@ contract('FuturesMarket MixinFuturesNextPriceOrders', accounts => { }); it('if market is suspended', async () => { - await systemStatus.suspendFuturesMarket(baseAsset, toUnit(0), { from: owner }); + await systemStatus.suspendFuturesMarket(marketKey, toUnit(0), { from: owner }); await assert.revert( futuresMarket.submitNextPriceOrder(size, { from: trader }), 'Market suspended' @@ -277,7 +278,7 @@ contract('FuturesMarket MixinFuturesNextPriceOrders', accounts => { }); it('cannot cancel if market is suspended', async () => { - await systemStatus.suspendFuturesMarket(baseAsset, toUnit(0), { from: owner }); + await systemStatus.suspendFuturesMarket(marketKey, toUnit(0), { from: owner }); await assert.revert( futuresMarket.cancelNextPriceOrder(trader, { from: trader }), 'Market suspended' @@ -597,7 +598,7 @@ contract('FuturesMarket MixinFuturesNextPriceOrders', accounts => { it('reverts if market is suspended', async () => { await setPrice(baseAsset, targetPrice); - await systemStatus.suspendFuturesMarket(baseAsset, toUnit(0), { from: owner }); + await systemStatus.suspendFuturesMarket(marketKey, toUnit(0), { from: owner }); await assert.revert( futuresMarket.executeNextPriceOrder(trader, { from: trader }), 'Market suspended' diff --git a/test/contracts/FuturesMarketData.js b/test/contracts/FuturesMarketData.js index 3391d58e54..230b7dba2a 100644 --- a/test/contracts/FuturesMarketData.js +++ b/test/contracts/FuturesMarketData.js @@ -19,8 +19,11 @@ contract('FuturesMarketData', accounts => { exchangeCircuitBreaker, sUSD, systemSettings, + marketKey, baseAsset; - const newAsset = toBytes32('sETH'); + const keySuffix = '-perp'; + const newMarketKey = toBytes32('sETH' + keySuffix); + const newAssetKey = toBytes32('sETH'); const owner = accounts[1]; const trader1 = accounts[2]; @@ -71,6 +74,7 @@ contract('FuturesMarketData', accounts => { // Add a couple of additional markets. for (const symbol of ['sETH', 'sLINK']) { const assetKey = toBytes32(symbol); + const marketKey = toBytes32(symbol + keySuffix); const market = await setupContract({ accounts, @@ -79,6 +83,7 @@ contract('FuturesMarketData', accounts => { args: [ addressResolver.address, assetKey, // base asset + marketKey, ], }); @@ -90,7 +95,7 @@ contract('FuturesMarketData', accounts => { // Now that the market exists we can set the all its parameters await futuresMarketSettings.setParameters( - assetKey, + marketKey, toWei('0.005'), // 0.5% taker fee toWei('0.001'), // 0.1% maker fee toWei('0.0005'), // 0.05% taker fee next price @@ -105,6 +110,7 @@ contract('FuturesMarketData', accounts => { } baseAsset = await futuresMarket.baseAsset(); + marketKey = await futuresMarket.marketKey(); // Update the rates to ensure they aren't stale await setPrice(baseAsset, toUnit(100)); @@ -128,11 +134,11 @@ contract('FuturesMarketData', accounts => { await futuresMarket.transferMargin(toUnit('4000'), { from: trader3 }); await futuresMarket.modifyPosition(toUnit('1.25'), { from: trader3 }); - sethMarket = await FuturesMarket.at(await futuresMarketManager.marketForAsset(newAsset)); + sethMarket = await FuturesMarket.at(await futuresMarketManager.marketForKey(newMarketKey)); await sethMarket.transferMargin(toUnit('3000'), { from: trader3 }); await sethMarket.modifyPosition(toUnit('4'), { from: trader3 }); - await setPrice(newAsset, toUnit('999')); + await setPrice(newAssetKey, toUnit('999')); }); it('Resolver is properly set', async () => { @@ -190,9 +196,9 @@ contract('FuturesMarketData', accounts => { assert.equal(details.priceDetails.invalid, assetPrice.invalid); }); - it('By asset', async () => { + it('By market key', async () => { const details = await futuresMarketData.marketDetails(futuresMarket.address); - const assetDetails = await futuresMarketData.marketDetailsForAsset(baseAsset); + const assetDetails = await futuresMarketData.marketDetailsForKey(marketKey); assert.equal(JSON.stringify(assetDetails), JSON.stringify(details)); }); }); @@ -223,11 +229,17 @@ contract('FuturesMarketData', accounts => { assert.equal(details.canLiquidatePosition, await futuresMarket.canLiquidate(trader1)); }); - it('By asset', async () => { + it('By market key', async () => { const details = await futuresMarketData.positionDetails(futuresMarket.address, trader3); const details2 = await futuresMarketData.positionDetails(sethMarket.address, trader3); - const detailsByAsset = await futuresMarketData.positionDetailsForAsset(baseAsset, trader3); - const detailsByAsset2 = await futuresMarketData.positionDetailsForAsset(newAsset, trader3); + const detailsByAsset = await futuresMarketData.positionDetailsForMarketKey( + marketKey, + trader3 + ); + const detailsByAsset2 = await futuresMarketData.positionDetailsForMarketKey( + newMarketKey, + trader3 + ); assert.equal(JSON.stringify(detailsByAsset), JSON.stringify(details)); assert.equal(JSON.stringify(detailsByAsset2), JSON.stringify(details2)); @@ -236,14 +248,12 @@ contract('FuturesMarketData', accounts => { describe('Market summaries', () => { it('For markets', async () => { - const sETHSummary = ( - await futuresMarketData.marketSummariesForAssets([toBytes32('sETH')]) - )[0]; + const sETHSummary = (await futuresMarketData.marketSummariesForKeys([newMarketKey]))[0]; - const params = await futuresMarketData.parameters(newAsset); // sETH + const params = await futuresMarketData.parameters(newMarketKey); // sETH assert.equal(sETHSummary.market, sethMarket.address); - assert.equal(sETHSummary.asset, newAsset); + assert.equal(sETHSummary.asset, newAssetKey); assert.equal(sETHSummary.maxLeverage, params.maxLeverage); const price = await sethMarket.assetPrice(); assert.equal(sETHSummary.price, price.price); @@ -256,13 +266,13 @@ contract('FuturesMarketData', accounts => { assert.equal(sETHSummary.feeRates.makerFeeNextPrice, params.makerFeeNextPrice); }); - it('For assets', async () => { + it('For market keys', async () => { const summaries = await futuresMarketData.marketSummaries([ futuresMarket.address, sethMarket.address, ]); - const summariesForAsset = await futuresMarketData.marketSummariesForAssets( - ['sBTC', 'sETH'].map(toBytes32) + const summariesForAsset = await futuresMarketData.marketSummariesForKeys( + ['sBTC', 'sETH' + keySuffix].map(toBytes32) ); assert.equal(JSON.stringify(summaries), JSON.stringify(summariesForAsset)); }); @@ -274,7 +284,7 @@ contract('FuturesMarketData', accounts => { const sETHSummary = summaries.find(summary => summary.asset === toBytes32('sETH')); const sLINKSummary = summaries.find(summary => summary.asset === toBytes32('sLINK')); - const fmParams = await futuresMarketData.parameters(baseAsset); + const fmParams = await futuresMarketData.parameters(marketKey); assert.equal(sBTCSummary.market, futuresMarket.address); assert.equal(sBTCSummary.asset, baseAsset); @@ -289,10 +299,10 @@ contract('FuturesMarketData', accounts => { assert.equal(sBTCSummary.feeRates.takerFeeNextPrice, fmParams.takerFeeNextPrice); assert.equal(sBTCSummary.feeRates.makerFeeNextPrice, fmParams.makerFeeNextPrice); - const sETHParams = await futuresMarketData.parameters(newAsset); // sETH + const sETHParams = await futuresMarketData.parameters(newMarketKey); // sETH assert.equal(sETHSummary.market, sethMarket.address); - assert.equal(sETHSummary.asset, newAsset); + assert.equal(sETHSummary.asset, newAssetKey); assert.equal(sETHSummary.maxLeverage, sETHParams.maxLeverage); price = await sethMarket.assetPrice(); assert.equal(sETHSummary.price, price.price); @@ -306,7 +316,7 @@ contract('FuturesMarketData', accounts => { assert.equal( sLINKSummary.market, - await futuresMarketManager.marketForAsset(toBytes32('sLINK')) + await futuresMarketManager.marketForKey(toBytes32('sLINK' + keySuffix)) ); assert.equal(sLINKSummary.asset, toBytes32('sLINK')); assert.equal(sLINKSummary.maxLeverage, toUnit(5)); diff --git a/test/contracts/FuturesMarketManager.js b/test/contracts/FuturesMarketManager.js index da1b07e971..9bd2426093 100644 --- a/test/contracts/FuturesMarketManager.js +++ b/test/contracts/FuturesMarketManager.js @@ -62,7 +62,7 @@ contract('FuturesMarketManager', accounts => { expected: [ 'addMarkets', 'removeMarkets', - 'removeMarketsByAsset', + 'removeMarketsByKey', 'issueSUSD', 'burnSUSD', 'payFee', @@ -80,7 +80,7 @@ contract('FuturesMarketManager', accounts => { setupContract({ accounts, contract: 'MockFuturesMarket', - args: [futuresMarketManager.address, k, toUnit('1000'), false], + args: [futuresMarketManager.address, k, k, toUnit('1000'), false], skipPostDeploy: true, }) ) @@ -99,14 +99,20 @@ contract('FuturesMarketManager', accounts => { const market = await setupContract({ accounts, contract: 'MockFuturesMarket', - args: [futuresMarketManager.address, toBytes32('sLINK'), toUnit('1000'), false], + args: [ + futuresMarketManager.address, + toBytes32('sLINK'), + toBytes32('sLINK'), + toUnit('1000'), + false, + ], skipPostDeploy: true, }); await futuresMarketManager.addMarkets([market.address], { from: owner }); assert.bnEqual(await futuresMarketManager.numMarkets(), toBN(3)); assert.equal((await futuresMarketManager.markets(2, 1))[0], market.address); - assert.equal(await futuresMarketManager.marketForAsset(toBytes32('sLINK')), market.address); + assert.equal(await futuresMarketManager.marketForKey(toBytes32('sLINK')), market.address); }); it('Adding multiple markets', async () => { @@ -116,7 +122,7 @@ contract('FuturesMarketManager', accounts => { setupContract({ accounts, contract: 'MockFuturesMarket', - args: [futuresMarketManager.address, k, toUnit('1000'), false], + args: [futuresMarketManager.address, k, k, toUnit('1000'), false], skipPostDeploy: true, }) ) @@ -125,29 +131,35 @@ contract('FuturesMarketManager', accounts => { const tx = await futuresMarketManager.addMarkets(addresses, { from: owner }); assert.bnEqual(await futuresMarketManager.numMarkets(), toBN(4)); assert.deepEqual(await futuresMarketManager.markets(2, 2), addresses); - assert.deepEqual(await futuresMarketManager.marketsForAssets(keys), addresses); + assert.deepEqual(await futuresMarketManager.marketsForKeys(keys), addresses); const decodedLogs = await getDecodedLogs({ hash: tx.tx, contracts: [futuresMarketManager] }); assert.equal(decodedLogs.length, 2); decodedEventEqual({ event: 'MarketAdded', emittedFrom: futuresMarketManager.address, - args: [addresses[0], keys[0]], + args: [addresses[0], keys[0], keys[0]], log: decodedLogs[0], }); decodedEventEqual({ event: 'MarketAdded', emittedFrom: futuresMarketManager.address, - args: [addresses[1], keys[1]], + args: [addresses[1], keys[1], keys[1]], log: decodedLogs[1], }); }); - it('Cannot add more than one market for the same asset.', async () => { + it('Cannot add more than one market for the same key.', async () => { const market = await setupContract({ accounts, contract: 'MockFuturesMarket', - args: [futuresMarketManager.address, toBytes32('sETH'), toUnit('1000'), false], + args: [ + futuresMarketManager.address, + toBytes32('sETH'), + toBytes32('sETH'), + toUnit('1000'), + false, + ], skipPostDeploy: true, }); await assert.revert( @@ -156,6 +168,30 @@ contract('FuturesMarketManager', accounts => { ); }); + it('Can add more than one market for the same asset', async () => { + const firstKey = currencyKeys[1]; + const market1 = markets[1]; + + const secondKey = toBytes32('sETH-2'); // different market key + const market2 = await setupContract({ + accounts, + contract: 'MockFuturesMarket', + args: [ + futuresMarketManager.address, + await market1.baseAsset(), + secondKey, + toUnit('1000'), + false, + ], + skipPostDeploy: true, + }); + await futuresMarketManager.addMarkets([market2.address], { from: owner }); + + // check correcr addresses returned + assert.equal(await futuresMarketManager.marketForKey(secondKey), market2.address); + assert.equal(await futuresMarketManager.marketForKey(firstKey), market1.address); + }); + it('Removing a single market', async () => { await futuresMarketManager.removeMarkets([addresses[0]], { from: owner }); @@ -163,7 +199,7 @@ contract('FuturesMarketManager', accounts => { assert.bnEqual(await futuresMarketManager.numMarkets(), toBN(1)); assert.deepEqual(markets, [addresses[1]]); - assert.equal(await futuresMarketManager.marketForAsset(currencyKeys[0]), ZERO_ADDRESS); + assert.equal(await futuresMarketManager.marketForKey(currencyKeys[0]), ZERO_ADDRESS); }); it('Removing multiple markets', async () => { @@ -171,7 +207,7 @@ contract('FuturesMarketManager', accounts => { const markets = await futuresMarketManager.allMarkets(); assert.bnEqual(await futuresMarketManager.numMarkets(), toBN(0)); assert.deepEqual(markets, []); - assert.deepEqual(await futuresMarketManager.marketsForAssets(currencyKeys), [ + assert.deepEqual(await futuresMarketManager.marketsForKeys(currencyKeys), [ ZERO_ADDRESS, ZERO_ADDRESS, ]); @@ -192,8 +228,8 @@ contract('FuturesMarketManager', accounts => { }); }); - it('Removing markets by asset', async () => { - await futuresMarketManager.removeMarketsByAsset([toBytes32('sETH')], { from: owner }); + it('Removing markets by key', async () => { + await futuresMarketManager.removeMarketsByKey([toBytes32('sETH')], { from: owner }); let markets = await futuresMarketManager.allMarkets(); assert.bnEqual(await futuresMarketManager.numMarkets(), toBN(1)); @@ -202,11 +238,17 @@ contract('FuturesMarketManager', accounts => { const market = await setupContract({ accounts, contract: 'MockFuturesMarket', - args: [futuresMarketManager.address, toBytes32('sLINK'), toUnit('1000'), false], + args: [ + futuresMarketManager.address, + toBytes32('sLINK'), + toBytes32('sLINK'), + toUnit('1000'), + false, + ], skipPostDeploy: true, }); await futuresMarketManager.addMarkets([market.address], { from: owner }); - await futuresMarketManager.removeMarketsByAsset(['sBTC', 'sLINK'].map(toBytes32), { + await futuresMarketManager.removeMarketsByKey(['sBTC', 'sLINK'].map(toBytes32), { from: owner, }); @@ -217,14 +259,20 @@ contract('FuturesMarketManager', accounts => { it('Cannot remove a market which does not exist', async () => { await assert.revert( - futuresMarketManager.removeMarketsByAsset([toBytes32('sLINK')], { from: owner }), + futuresMarketManager.removeMarketsByKey([toBytes32('sLINK')], { from: owner }), 'Unknown market' ); const market = await setupContract({ accounts, contract: 'MockFuturesMarket', - args: [futuresMarketManager.address, toBytes32('sLINK'), toUnit('1000'), false], + args: [ + futuresMarketManager.address, + toBytes32('sLINK'), + toBytes32('sLINK'), + toUnit('1000'), + false, + ], skipPostDeploy: true, }); await assert.revert( @@ -237,7 +285,13 @@ contract('FuturesMarketManager', accounts => { const market = await setupContract({ accounts, contract: 'MockFuturesMarket', - args: [futuresMarketManager.address, toBytes32('sLINK'), toUnit('1000'), false], + args: [ + futuresMarketManager.address, + toBytes32('sLINK'), + toBytes32('sLINK'), + toUnit('1000'), + false, + ], skipPostDeploy: true, }); @@ -262,7 +316,7 @@ contract('FuturesMarketManager', accounts => { }); await onlyGivenAddressCanInvoke({ - fnc: futuresMarketManager.removeMarketsByAsset, + fnc: futuresMarketManager.removeMarketsByKey, args: [['sETH', 'sBTC'].map(toBytes32)], accounts, address: owner, @@ -278,7 +332,13 @@ contract('FuturesMarketManager', accounts => { market = await setupContract({ accounts, contract: 'MockFuturesMarket', - args: [futuresMarketManager.address, toBytes32('sLINK'), toUnit('1000'), false], + args: [ + futuresMarketManager.address, + toBytes32('sLINK'), + toBytes32('sLINK'), + toUnit('1000'), + false, + ], skipPostDeploy: true, }); await futuresMarketManager.addMarkets([market.address], { from: owner }); @@ -358,7 +418,7 @@ contract('FuturesMarketManager', accounts => { setupContract({ accounts, contract: 'MockFuturesMarket', - args: [futuresMarketManager.address, k, individualDebt, false], + args: [futuresMarketManager.address, k, k, individualDebt, false], skipPostDeploy: true, }) ) @@ -385,7 +445,13 @@ contract('FuturesMarketManager', accounts => { const market = await setupContract({ accounts, contract: 'MockFuturesMarket', - args: [futuresMarketManager.address, toBytes32('sLINK'), toUnit('4000'), false], + args: [ + futuresMarketManager.address, + toBytes32('sLINK'), + toBytes32('sLINK'), + toUnit('4000'), + false, + ], skipPostDeploy: true, }); await futuresMarketManager.addMarkets([market.address], { from: owner }); diff --git a/test/contracts/FuturesMarketSettings.js b/test/contracts/FuturesMarketSettings.js index 2d0d9ddd7c..216a197b02 100644 --- a/test/contracts/FuturesMarketSettings.js +++ b/test/contracts/FuturesMarketSettings.js @@ -21,7 +21,7 @@ contract('FuturesMarketSettings', accounts => { const owner = accounts[1]; - const baseAsset = toBytes32('sBTC'); + const marketKey = toBytes32('sBTC'); const takerFee = toUnit('0.003'); const makerFee = toUnit('0.001'); const takerFeeNextPrice = toUnit('0.0005'); @@ -56,29 +56,36 @@ contract('FuturesMarketSettings', accounts => { mockFuturesMarketBTC = await artifacts.require('GenericMock').new(); - mockGenericContractFnc({ + await mockGenericContractFnc({ instance: mockFuturesMarketBTC, mock: 'FuturesMarket', fncName: 'recomputeFunding', returns: ['0'], }); - mockGenericContractFnc({ + await mockGenericContractFnc({ instance: mockFuturesMarketBTC, mock: 'FuturesMarket', fncName: 'marketSize', returns: ['1'], }); - mockGenericContractFnc({ + await mockGenericContractFnc({ instance: mockFuturesMarketBTC, mock: 'FuturesMarket', fncName: 'baseAsset', returns: [toBytes32('sBTC')], }); + await mockGenericContractFnc({ + instance: mockFuturesMarketBTC, + mock: 'FuturesMarket', + fncName: 'marketKey', + returns: [toBytes32('sBTC')], + }); + // add the market - futuresMarketManager.addMarkets([mockFuturesMarketBTC.address], { from: owner }); + await futuresMarketManager.addMarkets([mockFuturesMarketBTC.address], { from: owner }); }); it('Only expected functions are mutative', () => { @@ -127,7 +134,7 @@ contract('FuturesMarketSettings', accounts => { describe('bounds checking', async () => { it('should revert if maker fee is greater than 1', async () => { await assert.revert( - futuresMarketSettings.setMakerFee(baseAsset, toUnit('1').add(new BN(1)), { + futuresMarketSettings.setMakerFee(marketKey, toUnit('1').add(new BN(1)), { from: owner, }), 'maker fee greater than 1' @@ -136,7 +143,7 @@ contract('FuturesMarketSettings', accounts => { it('should revert if taker fee is greater than 1', async () => { await assert.revert( - futuresMarketSettings.setTakerFee(baseAsset, toUnit('1').add(new BN(1)), { + futuresMarketSettings.setTakerFee(marketKey, toUnit('1').add(new BN(1)), { from: owner, }), 'taker fee greater than 1' @@ -145,7 +152,7 @@ contract('FuturesMarketSettings', accounts => { it('should revert if maker fee next price is greater than 1', async () => { await assert.revert( - futuresMarketSettings.setMakerFeeNextPrice(baseAsset, toUnit('1').add(new BN(1)), { + futuresMarketSettings.setMakerFeeNextPrice(marketKey, toUnit('1').add(new BN(1)), { from: owner, }), 'maker fee greater than 1' @@ -154,7 +161,7 @@ contract('FuturesMarketSettings', accounts => { it('should revert if taker fee next price is greater than 1', async () => { await assert.revert( - futuresMarketSettings.setTakerFeeNextPrice(baseAsset, toUnit('1').add(new BN(1)), { + futuresMarketSettings.setTakerFeeNextPrice(marketKey, toUnit('1').add(new BN(1)), { from: owner, }), 'taker fee greater than 1' @@ -163,7 +170,7 @@ contract('FuturesMarketSettings', accounts => { it('should revert if setSkewScaleUSD is 0', async () => { await assert.revert( - futuresMarketSettings.setSkewScaleUSD(baseAsset, 0, { + futuresMarketSettings.setSkewScaleUSD(marketKey, 0, { from: owner, }), 'cannot set skew scale 0' @@ -181,7 +188,7 @@ contract('FuturesMarketSettings', accounts => { // Only settable by the owner await onlyGivenAddressCanInvoke({ fnc: setter, - args: [baseAsset, value], + args: [marketKey, value], address: owner, accounts, }); @@ -198,7 +205,7 @@ contract('FuturesMarketSettings', accounts => { const setter = p[2]; const getter = p[3]; - const tx = await setter(baseAsset, value, { from: owner }); + const tx = await setter(marketKey, value, { from: owner }); const decodedLogs = await getDecodedLogs({ hash: tx.tx, @@ -208,12 +215,12 @@ contract('FuturesMarketSettings', accounts => { decodedEventEqual({ event: 'ParameterUpdated', emittedFrom: futuresMarketSettings.address, - args: [baseAsset, param, value], + args: [marketKey, param, value], log: decodedLogs[1], }); // And the parameter was actually set properly - assert.bnEqual(await getter(baseAsset), value.toString()); + assert.bnEqual(await getter(marketKey), value.toString()); } }); }); @@ -383,4 +390,56 @@ contract('FuturesMarketSettings', accounts => { }); }); }); + + describe('migration scenario: different parameters for two markets for same asset', () => { + const firstMarketKey = toBytes32('sBTC'); + const secondMarketKey = toBytes32('SomethingElse'); + + let secondBTCMarket; + + before(async () => { + // add a second BTC market + secondBTCMarket = await artifacts.require('GenericMock').new(); + + await mockGenericContractFnc({ + instance: secondBTCMarket, + mock: 'FuturesMarket', + fncName: 'recomputeFunding', + returns: ['0'], + }); + + await mockGenericContractFnc({ + instance: secondBTCMarket, + mock: 'FuturesMarket', + fncName: 'marketSize', + returns: ['1'], + }); + + await mockGenericContractFnc({ + instance: secondBTCMarket, + mock: 'FuturesMarket', + fncName: 'baseAsset', + returns: [toBytes32('sBTC')], + }); + + await mockGenericContractFnc({ + instance: secondBTCMarket, + mock: 'FuturesMarket', + fncName: 'marketKey', + returns: [secondMarketKey], + }); + + // add the market + await futuresMarketManager.addMarkets([secondBTCMarket.address], { from: owner }); + }); + + it('should be able to change parameters for both markets independently', async () => { + const val1 = toUnit(0.1); + const val2 = toUnit(0.5); + await futuresMarketSettings.setMaxFundingRate(firstMarketKey, val1, { from: owner }); + await futuresMarketSettings.setMaxFundingRate(secondMarketKey, val2, { from: owner }); + assert.bnEqual(await futuresMarketSettings.maxFundingRate(firstMarketKey), val1); + assert.bnEqual(await futuresMarketSettings.maxFundingRate(secondMarketKey), val2); + }); + }); }); diff --git a/test/contracts/setup.js b/test/contracts/setup.js index 579e1cf656..2e57384092 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -160,6 +160,8 @@ const setupContract = async ({ } }; + const perpSuffix = tryGetProperty({ property: 'perpSuffix', otherwise: '' }); + const defaultArgs = { GenericMock: [], TradingRewards: [owner, owner, tryGetAddressOf('AddressResolver')], @@ -281,10 +283,12 @@ const setupContract = async ({ FuturesMarketBTC: [ tryGetAddressOf('AddressResolver'), toBytes32('sBTC'), // base asset + toBytes32('sBTC' + perpSuffix), // market key ], FuturesMarketETH: [ tryGetAddressOf('AddressResolver'), toBytes32('sETH'), // base asset + toBytes32('sETH' + perpSuffix), // market key ], FuturesMarketData: [tryGetAddressOf('AddressResolver')], }; @@ -1227,13 +1231,14 @@ const setupAllContracts = async ({ ]; // TODO: fetch settings per-market programmatically - const setupFuturesMarket = async asset => { - const assetKey = toBytes32(asset); + const setupFuturesMarket = async market => { + const assetKey = await market.baseAsset(); + const marketKey = await market.marketKey(); await setupPriceAggregators(returnObj['ExchangeRates'], owner, [assetKey]); await updateAggregatorRates(returnObj['ExchangeRates'], [assetKey], [toUnit('1')]); await Promise.all([ returnObj['FuturesMarketSettings'].setParameters( - assetKey, + marketKey, toWei('0.003'), // 0.3% taker fee toWei('0.001'), // 0.1% maker fee toWei('0.0005'), // 0.05% taker fee next price @@ -1249,10 +1254,10 @@ const setupAllContracts = async ({ }; if (returnObj['FuturesMarketBTC']) { - promises.push(setupFuturesMarket('sBTC')); + promises.push(setupFuturesMarket(returnObj['FuturesMarketBTC'])); } if (returnObj['FuturesMarketETH']) { - promises.push(setupFuturesMarket('sETH')); + promises.push(setupFuturesMarket(returnObj['FuturesMarketETH'])); } await Promise.all(promises);