diff --git a/contracts/FuturesMarket.sol b/contracts/FuturesMarket.sol index 3d6ca25d3a..5223f03d2d 100644 --- a/contracts/FuturesMarket.sol +++ b/contracts/FuturesMarket.sol @@ -228,13 +228,6 @@ contract FuturesMarket is Owned, Proxyable, MixinFuturesMarketSettings, IFutures return _marketSizes(); } - /* - * The max number of base units per market. - */ - function maxMarketSize() external view returns (uint) { - return uint(_maxMarketValue(baseAsset)); - } - /* * The remaining units on each side of the market left to be filled before hitting the cap. */ @@ -281,13 +274,17 @@ contract FuturesMarket is Owned, Proxyable, MixinFuturesMarketSettings, IFutures /* * The size of the skew relative to the size of the market OI cap. This value ranges between 0 and 1. + * Scaler used for skew is at least minSkewScale to prevent extreme funding rates for small markets. */ function _proportionalSkew() internal view returns (int) { - int signedSize = int(_maxMarketValue(baseAsset)); - if (signedSize == 0) { + uint minScale = _minSkewScale(baseAsset); + // don't allow small market sizes to cause huge funding rates + uint skewScale = marketSize > minScale ? marketSize : minScale; + if (skewScale == 0) { + // parameters may not be set, don't divide by zero return 0; } - return marketSkew.divideDecimalRound(signedSize); + return marketSkew.divideDecimalRound(int(skewScale)); } /* @@ -303,7 +300,7 @@ contract FuturesMarket is Owned, Proxyable, MixinFuturesMarketSettings, IFutures uint maxLeverage, uint maxMarketValue, uint maxFundingRate, - uint maxFundingRateSkew, + uint minSkewScale, uint maxFundingRateDelta ) { @@ -312,14 +309,8 @@ contract FuturesMarket is Owned, Proxyable, MixinFuturesMarketSettings, IFutures function _currentFundingRate() internal view returns (int) { int maxFundingRate = int(_maxFundingRate(baseAsset)); - int maxFundingRateSkew = int(_maxFundingRateSkew(baseAsset)); - if (maxFundingRateSkew == 0) { - return maxFundingRate; - } - - int functionFraction = _proportionalSkew().divideDecimalRound(maxFundingRateSkew); // Note the minus sign: funding flows in the opposite direction to the skew. - return _min(_max(-_UNIT, -functionFraction), _UNIT).multiplyDecimalRound(maxFundingRate); + return _min(_max(-_UNIT, -_proportionalSkew()), _UNIT).multiplyDecimalRound(maxFundingRate); } /* diff --git a/contracts/FuturesMarketData.sol b/contracts/FuturesMarketData.sol index b2fad19993..91fc66e74b 100644 --- a/contracts/FuturesMarketData.sol +++ b/contracts/FuturesMarketData.sol @@ -53,7 +53,7 @@ contract FuturesMarketData { struct FundingParameters { uint maxFundingRate; - uint maxFundingRateSkew; + uint minSkewScale; uint maxFundingRateDelta; } @@ -132,7 +132,7 @@ contract FuturesMarketData { uint maxLeverage, uint maxMarketValue, uint maxFundingRate, - uint maxFundingRateSkew, + uint minSkewScale, uint maxFundingRateDelta ) = _futuresMarketSettings().parameters(baseAsset); return @@ -143,7 +143,7 @@ contract FuturesMarketData { maxLeverage, maxMarketValue, maxFundingRate, - maxFundingRateSkew, + minSkewScale, maxFundingRateDelta ); } @@ -192,7 +192,7 @@ contract FuturesMarketData { pure returns (FundingParameters memory) { - return FundingParameters(params.maxFundingRate, params.maxFundingRateSkew, params.maxFundingRateDelta); + return FundingParameters(params.maxFundingRate, params.minSkewScale, params.maxFundingRateDelta); } function _marketSizes(IFuturesMarket market) internal view returns (Sides memory) { diff --git a/contracts/FuturesMarketSettings.sol b/contracts/FuturesMarketSettings.sol index 2e1f6ce672..e7f80ad718 100644 --- a/contracts/FuturesMarketSettings.sol +++ b/contracts/FuturesMarketSettings.sol @@ -81,8 +81,8 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar /* * The skew level at which the max funding rate will be charged. */ - function maxFundingRateSkew(bytes32 _baseAsset) public view returns (uint) { - return _maxFundingRateSkew(_baseAsset); + function minSkewScale(bytes32 _baseAsset) public view returns (uint) { + return _minSkewScale(_baseAsset); } /* @@ -102,7 +102,7 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar uint _maxLeverage, uint _maxMarketValue, uint _maxFundingRate, - uint _maxFundingRateSkew, + uint _minSkewScale, uint _maxFundingRateDelta ) { @@ -173,9 +173,9 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar _setParameter(_baseAsset, PARAMETER_MAX_FUNDING_RATE, _maxFundingRate); } - function setMaxFundingRateSkew(bytes32 _baseAsset, uint _maxFundingRateSkew) public onlyOwner { + function setMinSkewScale(bytes32 _baseAsset, uint _minSkewScale) public onlyOwner { _recomputeFunding(_baseAsset); - _setParameter(_baseAsset, PARAMETER_MAX_FUNDING_RATE_SKEW, _maxFundingRateSkew); + _setParameter(_baseAsset, PARAMETER_MIN_SKEW_SCALE, _minSkewScale); } function setMaxFundingRateDelta(bytes32 _baseAsset, uint _maxFundingRateDelta) public onlyOwner { @@ -191,7 +191,7 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar uint _maxLeverage, uint _maxMarketValue, uint _maxFundingRate, - uint _maxFundingRateSkew, + uint _minSkewScale, uint _maxFundingRateDelta ) external onlyOwner { _recomputeFunding(_baseAsset); @@ -201,7 +201,7 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar setMaxLeverage(_baseAsset, _maxLeverage); setMaxMarketValue(_baseAsset, _maxMarketValue); setMaxFundingRate(_baseAsset, _maxFundingRate); - setMaxFundingRateSkew(_baseAsset, _maxFundingRateSkew); + setMinSkewScale(_baseAsset, _minSkewScale); setMaxFundingRateDelta(_baseAsset, _maxFundingRateDelta); } diff --git a/contracts/MixinFuturesMarketSettings.sol b/contracts/MixinFuturesMarketSettings.sol index bdd4b7017c..9cbf14e3ed 100644 --- a/contracts/MixinFuturesMarketSettings.sol +++ b/contracts/MixinFuturesMarketSettings.sol @@ -20,7 +20,7 @@ contract MixinFuturesMarketSettings is MixinResolver { bytes32 internal constant PARAMETER_MAX_LEVERAGE = "maxLeverage"; bytes32 internal constant PARAMETER_MAX_MARKET_VALUE = "maxMarketValue"; bytes32 internal constant PARAMETER_MAX_FUNDING_RATE = "maxFundingRate"; - bytes32 internal constant PARAMETER_MAX_FUNDING_RATE_SKEW = "maxFundingRateSkew"; + bytes32 internal constant PARAMETER_MIN_SKEW_SCALE = "minSkewScale"; bytes32 internal constant PARAMETER_MAX_FUNDING_RATE_DELTA = "maxFundingRateDelta"; // Global settings @@ -72,12 +72,12 @@ contract MixinFuturesMarketSettings is MixinResolver { return _parameter(_baseAsset, PARAMETER_MAX_MARKET_VALUE); } - function _maxFundingRate(bytes32 _baseAsset) internal view returns (uint) { - return _parameter(_baseAsset, PARAMETER_MAX_FUNDING_RATE); + function _minSkewScale(bytes32 _baseAsset) internal view returns (uint) { + return _parameter(_baseAsset, PARAMETER_MIN_SKEW_SCALE); } - function _maxFundingRateSkew(bytes32 _baseAsset) internal view returns (uint) { - return _parameter(_baseAsset, PARAMETER_MAX_FUNDING_RATE_SKEW); + function _maxFundingRate(bytes32 _baseAsset) internal view returns (uint) { + return _parameter(_baseAsset, PARAMETER_MAX_FUNDING_RATE); } function _maxFundingRateDelta(bytes32 _baseAsset) internal view returns (uint) { @@ -94,7 +94,7 @@ contract MixinFuturesMarketSettings is MixinResolver { uint maxLeverage, uint maxMarketValue, uint maxFundingRate, - uint maxFundingRateSkew, + uint minSkewScale, uint maxFundingRateDelta ) { @@ -104,7 +104,7 @@ contract MixinFuturesMarketSettings is MixinResolver { maxLeverage = _maxLeverage(_baseAsset); maxMarketValue = _maxMarketValue(_baseAsset); maxFundingRate = _maxFundingRate(_baseAsset); - maxFundingRateSkew = _maxFundingRateSkew(_baseAsset); + minSkewScale = _minSkewScale(_baseAsset); maxFundingRateDelta = _maxFundingRateDelta(_baseAsset); } diff --git a/contracts/interfaces/IFuturesMarket.sol b/contracts/interfaces/IFuturesMarket.sol index a1378312de..8c91835647 100644 --- a/contracts/interfaces/IFuturesMarket.sol +++ b/contracts/interfaces/IFuturesMarket.sol @@ -55,8 +55,6 @@ interface IFuturesMarket { function marketSizes() external view returns (uint long, uint short); - function maxMarketSize() external view returns (uint); - function maxOrderSizes() external view @@ -78,7 +76,7 @@ interface IFuturesMarket { uint maxLeverage, uint maxMarketValue, uint maxFundingRate, - uint maxFundingRateSkew, + uint minSkewScale, uint maxFundingRateDelta ); diff --git a/contracts/interfaces/IFuturesMarketSettings.sol b/contracts/interfaces/IFuturesMarketSettings.sol index 9ccd491321..3032819f35 100644 --- a/contracts/interfaces/IFuturesMarketSettings.sol +++ b/contracts/interfaces/IFuturesMarketSettings.sol @@ -8,7 +8,7 @@ interface IFuturesMarketSettings { uint maxLeverage; uint maxMarketValue; uint maxFundingRate; - uint maxFundingRateSkew; + uint minSkewScale; uint maxFundingRateDelta; } @@ -24,7 +24,7 @@ interface IFuturesMarketSettings { function maxFundingRate(bytes32 _baseAsset) external view returns (uint); - function maxFundingRateSkew(bytes32 _baseAsset) external view returns (uint); + function minSkewScale(bytes32 _baseAsset) external view returns (uint); function maxFundingRateDelta(bytes32 _baseAsset) external view returns (uint); @@ -38,7 +38,7 @@ interface IFuturesMarketSettings { uint _maxLeverage, uint _maxMarketValue, uint _maxFundingRate, - uint _maxFundingRateSkew, + uint _minSkewScale, uint _maxFundingRateDelta ); diff --git a/publish/deployed/kovan-ovm-futures/deployment.json b/publish/deployed/kovan-ovm-futures/deployment.json index 3522f9ee3b..374dcf7ca2 100644 --- a/publish/deployed/kovan-ovm-futures/deployment.json +++ b/publish/deployed/kovan-ovm-futures/deployment.json @@ -24133,7 +24133,7 @@ }, { "internalType": "uint256", - "name": "maxFundingRateSkew", + "name": "minSkewScale", "type": "uint256" }, { @@ -24279,7 +24279,7 @@ }, { "internalType": "uint256", - "name": "maxFundingRateSkew", + "name": "minSkewScale", "type": "uint256" }, { @@ -24566,7 +24566,7 @@ }, { "internalType": "uint256", - "name": "maxFundingRateSkew", + "name": "minSkewScale", "type": "uint256" }, { @@ -25098,7 +25098,7 @@ "type": "bytes32" } ], - "name": "maxFundingRateSkew", + "name": "minSkewScale", "outputs": [ { "internalType": "uint256", @@ -25262,7 +25262,7 @@ }, { "internalType": "uint256", - "name": "_maxFundingRateSkew", + "name": "_minSkewScale", "type": "uint256" }, { @@ -25428,11 +25428,11 @@ }, { "internalType": "uint256", - "name": "_maxFundingRateSkew", + "name": "_minSkewScale", "type": "uint256" } ], - "name": "setMaxFundingRateSkew", + "name": "setMinSkewScale", "outputs": [], "payable": false, "stateMutability": "nonpayable", @@ -25537,7 +25537,7 @@ }, { "internalType": "uint256", - "name": "_maxFundingRateSkew", + "name": "_minSkewScale", "type": "uint256" }, { @@ -26531,7 +26531,7 @@ }, { "internalType": "uint256", - "name": "maxFundingRateSkew", + "name": "minSkewScale", "type": "uint256" }, { diff --git a/publish/deployed/kovan-ovm-futures/futures-markets.json b/publish/deployed/kovan-ovm-futures/futures-markets.json index 1e57db2831..e55b717132 100644 --- a/publish/deployed/kovan-ovm-futures/futures-markets.json +++ b/publish/deployed/kovan-ovm-futures/futures-markets.json @@ -7,7 +7,7 @@ "maxLeverage": "10", "maxMarketValue": "2000", "maxFundingRate": "0.1", - "maxFundingRateSkew": "1", + "minSkewScale": "300", "maxFundingRateDelta": "0.0125" }, { @@ -18,7 +18,7 @@ "maxLeverage": "10", "maxMarketValue": "20000", "maxFundingRate": "0.1", - "maxFundingRateSkew": "1", + "minSkewScale": "6000", "maxFundingRateDelta": "0.0125" }, { @@ -29,7 +29,7 @@ "maxLeverage": "10", "maxMarketValue": "1000000", "maxFundingRate": "0.1", - "maxFundingRateSkew": "1", + "minSkewScale": "750000", "maxFundingRateDelta": "0.0125" } ] diff --git a/publish/deployed/local-ovm/futures-markets.json b/publish/deployed/local-ovm/futures-markets.json index 3e0c89b1d7..2a1a6ec8cf 100644 --- a/publish/deployed/local-ovm/futures-markets.json +++ b/publish/deployed/local-ovm/futures-markets.json @@ -7,7 +7,7 @@ "maxLeverage": "10", "maxMarketValue": "1000000000", "maxFundingRate": "0.1", - "maxFundingRateSkew": "1", + "minSkewScale": "300", "maxFundingRateDelta": "0.0125" }, { @@ -18,7 +18,7 @@ "maxLeverage": "10", "maxMarketValue": "1000000000", "maxFundingRate": "0.1", - "maxFundingRateSkew": "1", + "minSkewScale": "6000", "maxFundingRateDelta": "0.0125" }, { @@ -29,7 +29,7 @@ "maxLeverage": "10", "maxMarketValue": "1000000000", "maxFundingRate": "0.1", - "maxFundingRateSkew": "1", + "minSkewScale": "750000", "maxFundingRateDelta": "0.0125" } ] diff --git a/publish/src/commands/deploy/configure-futures.js b/publish/src/commands/deploy/configure-futures.js index a5aad8de79..b99a504dc3 100644 --- a/publish/src/commands/deploy/configure-futures.js +++ b/publish/src/commands/deploy/configure-futures.js @@ -85,7 +85,7 @@ module.exports = async ({ maxLeverage, maxMarketValue, maxFundingRate, - maxFundingRateSkew, + minSkewScale, maxFundingRateDelta, } = market; @@ -100,7 +100,7 @@ module.exports = async ({ maxLeverage: w3utils.toWei(maxLeverage), maxMarketValue: w3utils.toWei(maxMarketValue), maxFundingRate: w3utils.toWei(maxFundingRate), - maxFundingRateSkew: w3utils.toWei(maxFundingRateSkew), + minSkewScale: w3utils.toWei(minSkewScale), maxFundingRateDelta: w3utils.toWei(maxFundingRateDelta), }; diff --git a/test/contracts/FuturesMarket.js b/test/contracts/FuturesMarket.js index 48f03fff88..f797fc0d7a 100644 --- a/test/contracts/FuturesMarket.js +++ b/test/contracts/FuturesMarket.js @@ -59,7 +59,7 @@ contract('FuturesMarket', accounts => { const maxLeverage = toUnit('10'); const maxMarketValue = toUnit('100000'); const maxFundingRate = toUnit('0.1'); - const maxFundingRateSkew = toUnit('1'); + const minSkewScale = toUnit('1000'); const maxFundingRateDelta = toUnit('0.0125'); const initialPrice = toUnit('100'); const liquidationFee = toUnit('20'); @@ -162,7 +162,7 @@ contract('FuturesMarket', accounts => { assert.bnEqual(parameters.maxLeverage, maxLeverage); assert.bnEqual(parameters.maxMarketValue, maxMarketValue); assert.bnEqual(parameters.maxFundingRate, maxFundingRate); - assert.bnEqual(parameters.maxFundingRateSkew, maxFundingRateSkew); + assert.bnEqual(parameters.minSkewScale, minSkewScale); assert.bnEqual(parameters.maxFundingRateDelta, maxFundingRateDelta); }); @@ -176,7 +176,7 @@ contract('FuturesMarket', accounts => { }); it('market size and skew', async () => { - const maxSize = await futuresMarket.maxMarketSize(); + const minScale = (await futuresMarket.parameters()).minSkewScale; let sizes = await futuresMarket.marketSizes(); let marketSkew = await futuresMarket.marketSkew(); @@ -203,7 +203,7 @@ contract('FuturesMarket', accounts => { assert.bnEqual(await futuresMarket.marketSkew(), toUnit('50')); assert.bnEqual( await futuresMarket.proportionalSkew(), - divideDecimalRound(marketSkew, maxSize) + divideDecimalRound(marketSkew, minScale) ); await transferMarginAndModifyPosition({ @@ -222,7 +222,7 @@ contract('FuturesMarket', accounts => { assert.bnEqual(await futuresMarket.marketSkew(), toUnit('15')); assert.bnClose( await futuresMarket.proportionalSkew(), - divideDecimalRound(marketSkew, maxSize) + divideDecimalRound(marketSkew, minScale) ); await closePositionAndWithdrawMargin({ @@ -239,7 +239,7 @@ contract('FuturesMarket', accounts => { assert.bnEqual(await futuresMarket.marketSkew(), toUnit('-35')); assert.bnClose( await futuresMarket.proportionalSkew(), - divideDecimalRound(marketSkew, maxSize) + divideDecimalRound(marketSkew, minScale) ); await closePositionAndWithdrawMargin({ @@ -2175,7 +2175,7 @@ contract('FuturesMarket', accounts => { await withdrawAccessibleAndValidate(trader2); }); - it.skip('Larger position', async () => { + it('Larger position', async () => { await transferMarginAndModifyPosition({ market: futuresMarket, account: trader, @@ -2448,14 +2448,14 @@ contract('FuturesMarket', accounts => { assert.bnEqual(await futuresMarket.currentFundingRate(), toUnit(0)); - const maxSize = await futuresMarket.maxMarketSize(); + const minScale = (await futuresMarket.parameters()).minSkewScale; const maxFundingRate = await futuresMarket.maxFundingRate(); // Market is 24 units long skewed (24 / 100000) await futuresMarket.modifyPosition(toUnit('24'), { from: trader }); let marketSkew = await futuresMarket.marketSkew(); assert.bnEqual( await futuresMarket.currentFundingRate(), - multiplyDecimalRound(divideDecimalRound(marketSkew, maxSize), maxFundingRate.neg()) + multiplyDecimalRound(divideDecimalRound(marketSkew, minScale), maxFundingRate.neg()) ); // 50% the other way () @@ -2463,7 +2463,7 @@ contract('FuturesMarket', accounts => { marketSkew = await futuresMarket.marketSkew(); assert.bnClose( await futuresMarket.currentFundingRate(), - multiplyDecimalRound(divideDecimalRound(marketSkew, maxSize), maxFundingRate.neg()) + multiplyDecimalRound(divideDecimalRound(marketSkew, minScale), maxFundingRate.neg()) ); // Market is 100% skewed @@ -2471,7 +2471,7 @@ contract('FuturesMarket', accounts => { marketSkew = await futuresMarket.marketSkew(); assert.bnClose( await futuresMarket.currentFundingRate(), - multiplyDecimalRound(divideDecimalRound(marketSkew, maxSize), maxFundingRate.neg()) + multiplyDecimalRound(divideDecimalRound(marketSkew, minScale), maxFundingRate.neg()) ); // 100% the other way @@ -2480,7 +2480,7 @@ contract('FuturesMarket', accounts => { marketSkew = await futuresMarket.marketSkew(); assert.bnClose( await futuresMarket.currentFundingRate(), - multiplyDecimalRound(divideDecimalRound(marketSkew, maxSize), maxFundingRate.neg()) + multiplyDecimalRound(divideDecimalRound(marketSkew, minScale), maxFundingRate.neg()) ); }); @@ -2504,7 +2504,7 @@ contract('FuturesMarket', accounts => { sizeDelta: toUnit('-4'), }); - const expectedFunding = toUnit('-0.000008'); // 8/100,000 skew * 0.1 max funding rate + const expectedFunding = toUnit('-0.0008'); // 8/1000 skew * 0.1 max funding rate assert.bnEqual(await futuresMarket.currentFundingRate(), expectedFunding); await futuresMarketSettings.setMaxFundingRate(baseAsset, toUnit('0.2'), { from: owner }); @@ -2516,7 +2516,7 @@ contract('FuturesMarket', accounts => { assert.bnEqual(await futuresMarket.currentFundingRate(), toUnit('0')); }); - it('Altering the max funding rate skew has a proportional effect', async () => { + it('Altering the minSkewScale has a proportional effect when above market size', async () => { await transferMarginAndModifyPosition({ market: futuresMarket, account: trader, @@ -2533,29 +2533,42 @@ contract('FuturesMarket', accounts => { sizeDelta: toUnit('4'), }); - const expectedFunding = toUnit('0.000008'); // 8/100,000 skew * 0.1 max funding rate + const expectedFunding = toUnit('0.0008'); // 8/1000 skew * 0.1 max funding rate assert.bnEqual(await futuresMarket.currentFundingRate(), expectedFunding); - await futuresMarketSettings.setMaxFundingRateSkew(baseAsset, toUnit('0.5'), { from: owner }); + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('500'), { from: owner }); assert.bnEqual( await futuresMarket.currentFundingRate(), multiplyDecimalRound(expectedFunding, toUnit('2')) ); - await futuresMarketSettings.setMaxFundingRateSkew(baseAsset, toUnit('0.25'), { from: owner }); + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('250'), { from: owner }); assert.bnEqual( await futuresMarket.currentFundingRate(), multiplyDecimalRound(expectedFunding, toUnit('4')) ); - await futuresMarketSettings.setMaxFundingRateSkew(baseAsset, toUnit('2'), { from: owner }); + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('2000'), { from: owner }); assert.bnEqual( await futuresMarket.currentFundingRate(), multiplyDecimalRound(expectedFunding, toUnit('0.5')) ); - await futuresMarketSettings.setMaxFundingRateSkew(baseAsset, toUnit('0'), { from: owner }); - assert.bnEqual(await futuresMarket.currentFundingRate(), toUnit('0.1')); + // disable minSkewScale + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('0'), { from: owner }); + assert.bnEqual(await futuresMarket.currentFundingRate(), toUnit('0.05')); // 0.1 * 8 / (4 + 12) + + // minSkewScale is below market size (so has no effect) + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('4'), { from: owner }); + assert.bnEqual(await futuresMarket.currentFundingRate(), toUnit('0.05')); // 0.1 * 8 / (4 + 12) + + // minSkewScale is equal to market size (so has no effect) + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('16'), { from: owner }); + assert.bnEqual(await futuresMarket.currentFundingRate(), toUnit('0.05')); // 0.1 * 8 / (4 + 12) + + // minSkewScale is double the size + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('32'), { from: owner }); + assert.bnEqual(await futuresMarket.currentFundingRate(), toUnit('0.025')); // 0.1 * 8 / (32) }); for (const leverage of ['1', '-1'].map(toUnit)) { @@ -2584,7 +2597,10 @@ contract('FuturesMarket', accounts => { assert.bnEqual(await futuresMarket.currentFundingRate(), expected); }); - it.skip('Different skew rates induce proportional funding levels', async () => { + it('Different skew rates induce proportional funding levels', async () => { + // no minSkewScale + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('0'), { from: owner }); + await transferMarginAndModifyPosition({ market: futuresMarket, account: trader, @@ -2596,73 +2612,43 @@ contract('FuturesMarket', accounts => { const points = 5; - for (const maxFRSkew of ['1', '0.5', '0.3'].map(toUnit)) { - await futuresMarketSettings.setMaxFundingRateSkew(baseAsset, maxFRSkew, { - from: owner, - }); - // We will sample points linearly from proportionalSkew = 0 down to proportionalSkew = maxFRSkew, - // So that the funding rate will go from 0 to maxFR. - // 0 skew is achieved when oppLev = -leverage. - // But when does proportionalSkew = maxFRSkew? - // Choose oppLev = k*lev, - // maxFRSkew is achieved when - // (lev - k*lev)/(lev + k*lev) = maxFRSkew - // => k = (1-maxFRSkew)/(1+maxFRSkew) - // E.g. if maxFRSkew = 0.5, then k = 0.5/1.5 = 1/3 - // So we sample oppLev from leverage to 1/3*leverage - - const k = toUnit(1) - .sub(maxFRSkew) - .mul(toUnit(1)) - .div(toUnit(1).add(maxFRSkew)); - - setPrice(baseAsset, toUnit('100')); - - for (const maxFR of ['0.1', '0.2', '0.05'].map(toUnit)) { - await futuresMarketSettings.setMaxFundingRate(baseAsset, maxFR, { from: owner }); - - const lowLev = leverage.mul(k).div(toUnit(1)); - - for (let i = points; i >= 0; i--) { - // now lerp from leverage*k to leverage - const frac = leverage - .sub(lowLev) - .mul(toBN(i)) - .div(toBN(points)); - const oppLev = lowLev.add(frac).neg(); - const size = oppLev.mul(toBN('10')); - if (size.abs().gt(toBN('0'))) { - await futuresMarket.modifyPosition(size, { from: trader2 }); - } - - // oppLev = lev*k + lev*(1 - k)*i/points - // The skew is (lev - lev*k - lev*(1-k)*i/points)/(lev + lev*k + lev*(1-k)*i/points) - // = (1 - k - (1-k)*i/points)/(1 + k + (1-k)*i/points) - // = (1 - i/points)/(1 + i/points + 2k/(1-k)) - // = (points - i)/(points + i + points*(1/maxFRSkew - 1)) - - const maxFRSkewCorrection = toUnit(1) - .mul(toUnit(1)) - .div(maxFRSkew) - .sub(toUnit(1)) - .mul(toBN(points)); - let expected = maxFR - .mul(toUnit(1)) - .div(maxFRSkew) - .mul(toUnit(points - i)) - .div(toUnit(points + i).add(maxFRSkewCorrection)) - .mul(leverage.div(leverage.abs())) - .neg(); - - if (expected.gt(maxFR)) { - expected = maxFR; - } - - assert.bnClose(await futuresMarket.currentFundingRate(), expected, toUnit('0.01')); - - if (size.abs().gt(toBN(0))) { - await futuresMarket.closePosition({ from: trader2 }); - } + setPrice(baseAsset, toUnit('100')); + + for (const maxFR of ['0.1', '0.2', '0.05'].map(toUnit)) { + await futuresMarketSettings.setMaxFundingRate(baseAsset, maxFR, { from: owner }); + + for (let i = points; i >= 0; i--) { + // now lerp from leverage*k to leverage + const frac = leverage.mul(toBN(i)).div(toBN(points)); + const oppLev = frac.neg(); + const size = oppLev.mul(toBN('10')); + if (size.abs().gt(toBN('0'))) { + await futuresMarket.modifyPosition(size, { from: trader2 }); + } + + // oppLev = lev*k + lev*(1 - k)*i/points + // The skew is (lev - lev*k - lev*(1-k)*i/points)/(lev + lev*k + lev*(1-k)*i/points) + // = (1 - k - (1-k)*i/points)/(1 + k + (1-k)*i/points) + // = (1 - i/points)/(1 + i/points + 2k/(1-k)) + // = (points - i)/(points + i + points*(1/maxFRSkew - 1)) + + const maxFRSkewCorrection = toUnit(1) + .sub(toUnit(1)) + .mul(toBN(points)); + let expected = maxFR + .mul(toUnit(points - i)) + .div(toUnit(points + i).add(maxFRSkewCorrection)) + .mul(leverage.div(leverage.abs())) + .neg(); + + if (expected.gt(maxFR)) { + expected = maxFR; + } + + assert.bnClose(await futuresMarket.currentFundingRate(), expected, toUnit('0.01')); + + if (size.abs().gt(toBN(0))) { + await futuresMarket.closePosition({ from: trader2 }); } } } @@ -2716,10 +2702,13 @@ contract('FuturesMarket', accounts => { assert.isTrue(false); }); - it.skip('Funding sequence is recomputed by setting funding rate parameters', async () => { + it('Funding sequence is recomputed by setting funding rate parameters', async () => { + // no minSkewScale + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('0'), { from: owner }); + assert.bnEqual( await futuresMarket.fundingSequenceLength(), - initialFundingIndex.add(toBN(5)) + initialFundingIndex.add(toBN(6)) ); await fastForward(24 * 60 * 60); await setPrice(baseAsset, toUnit('100')); @@ -2730,11 +2719,11 @@ contract('FuturesMarket', accounts => { assert.bnEqual( await futuresMarket.fundingSequenceLength(), - initialFundingIndex.add(toBN(6)) + initialFundingIndex.add(toBN(7)) ); assert.bnEqual(await futuresMarket.fundingLastRecomputed(), time); assert.bnClose( - await futuresMarket.fundingSequence(initialFundingIndex.add(toBN(5))), + await futuresMarket.fundingSequence(initialFundingIndex.add(toBN(6))), toUnit('-5'), toUnit('0.01') ); @@ -2748,25 +2737,25 @@ contract('FuturesMarket', accounts => { toUnit('0.001') ); - await futuresMarketSettings.setMaxFundingRateSkew(baseAsset, toUnit('0.5'), { + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('0'), { from: owner, }); time = await currentTime(); assert.bnEqual( await futuresMarket.fundingSequenceLength(), - initialFundingIndex.add(toBN(7)) + initialFundingIndex.add(toBN(8)) ); assert.bnEqual(await futuresMarket.fundingLastRecomputed(), time); assert.bnClose( - await futuresMarket.fundingSequence(initialFundingIndex.add(toBN(6))), + await futuresMarket.fundingSequence(initialFundingIndex.add(toBN(7))), toUnit('-25'), toUnit('0.01') ); await fastForward(24 * 60 * 60); await setPrice(baseAsset, toUnit('300')); - assert.bnClose((await futuresMarket.unrecordedFunding())[0], toUnit('-60'), toUnit('0.01')); + assert.bnClose((await futuresMarket.unrecordedFunding())[0], toUnit('-30'), toUnit('0.01')); await futuresMarketSettings.setMaxFundingRateDelta(baseAsset, toUnit('0.05'), { from: owner, @@ -2775,12 +2764,12 @@ contract('FuturesMarket', accounts => { assert.bnEqual( await futuresMarket.fundingSequenceLength(), - initialFundingIndex.add(toBN(8)) + initialFundingIndex.add(toBN(9)) ); assert.bnEqual(await futuresMarket.fundingLastRecomputed(), time); assert.bnClose( - await futuresMarket.fundingSequence(initialFundingIndex.add(toBN(7))), - toUnit('-85'), + await futuresMarket.fundingSequence(initialFundingIndex.add(toBN(8))), + toUnit('-55'), toUnit('0.01') ); }); @@ -2996,7 +2985,10 @@ contract('FuturesMarket', accounts => { ); }); - it.skip('Liquidation price is accurate with funding', async () => { + it('Liquidation price is accurate with funding', async () => { + // no minSkewScale + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('0'), { from: owner }); + await setPrice(baseAsset, toUnit('250')); // Submit orders that induce -0.05 funding rate await futuresMarket.transferMargin(toUnit('1500'), { from: trader }); @@ -3025,7 +3017,10 @@ contract('FuturesMarket', accounts => { assert.bnClose(lPrice[0], preLPrice2, toUnit(0.001)); }); - it.skip('Liquidation price reports invalidity properly', async () => { + it('Liquidation price reports invalidity properly', async () => { + // no minSkewScale + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('0'), { from: owner }); + await setPrice(baseAsset, toUnit('250')); await futuresMarket.transferMargin(toUnit('1500'), { from: trader }); await futuresMarket.modifyPosition(toUnit('30'), { from: trader }); @@ -3114,7 +3109,10 @@ contract('FuturesMarket', accounts => { ); }); - it.skip('Liquidation properly affects the overall market parameters (long case)', async () => { + it('Liquidation properly affects the overall market parameters (long case)', async () => { + // no minSkewScale + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('0'), { from: owner }); + await fastForward(24 * 60 * 60); // wait one day to accrue a bit of funding const size = await futuresMarket.marketSize(); @@ -3170,7 +3168,10 @@ contract('FuturesMarket', accounts => { ); }); - it.skip('Liquidation properly affects the overall market parameters (short case)', async () => { + it('Liquidation properly affects the overall market parameters (short case)', async () => { + // no minSkewScale + await futuresMarketSettings.setMinSkewScale(baseAsset, toUnit('0'), { from: owner }); + await fastForward(24 * 60 * 60); // wait one day to accrue a bit of funding const size = await futuresMarket.marketSize(); diff --git a/test/contracts/FuturesMarketData.js b/test/contracts/FuturesMarketData.js index b08f7e50b9..8cae50c730 100644 --- a/test/contracts/FuturesMarketData.js +++ b/test/contracts/FuturesMarketData.js @@ -93,7 +93,7 @@ contract('FuturesMarketData', accounts => { toWei('5'), // 5x max leverage toWei('1000000'), // 1000000 max total margin toWei('0.2'), // 20% max funding rate - toWei('0.5'), // 50% max funding rate skew + toWei('1000'), // 1000 units minSkewScale toWei('0.025'), // 2.5% per hour max funding rate of change { from: owner } ); @@ -162,7 +162,7 @@ contract('FuturesMarketData', accounts => { assert.bnEqual(details.limits.maxMarketValue, params.maxMarketValue); assert.bnEqual(details.fundingParameters.maxFundingRate, params.maxFundingRate); - assert.bnEqual(details.fundingParameters.maxFundingRateSkew, params.maxFundingRateSkew); + assert.bnEqual(details.fundingParameters.minSkewScale, params.minSkewScale); assert.bnEqual(details.fundingParameters.maxFundingRateDelta, params.maxFundingRateDelta); assert.bnEqual(details.marketSizeDetails.marketSize, await futuresMarket.marketSize()); diff --git a/test/contracts/FuturesMarketSettings.js b/test/contracts/FuturesMarketSettings.js index 03039c65d0..cda281668e 100644 --- a/test/contracts/FuturesMarketSettings.js +++ b/test/contracts/FuturesMarketSettings.js @@ -29,7 +29,7 @@ contract('FuturesMarketSettings', accounts => { const maxMarketValue = toUnit('100000'); const maxFundingRate = toUnit('0.1'); - const maxFundingRateSkew = toUnit('1'); + const minSkewScale = toUnit('100'); const maxFundingRateDelta = toUnit('0.0125'); before(async () => { @@ -85,7 +85,7 @@ contract('FuturesMarketSettings', accounts => { 'setMaxLeverage', 'setMaxMarketValue', 'setMaxFundingRate', - 'setMaxFundingRateSkew', + 'setMinSkewScale', 'setMaxFundingRateDelta', 'setParameters', 'setLiquidationFee', @@ -105,7 +105,7 @@ contract('FuturesMarketSettings', accounts => { maxLeverage, maxMarketValue, maxFundingRate, - maxFundingRateSkew, + minSkewScale, maxFundingRateDelta, }).map(([key, val]) => { const capKey = key.charAt(0).toUpperCase() + key.slice(1); diff --git a/test/contracts/setup.js b/test/contracts/setup.js index b9e0782eee..3db4878f88 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -1141,7 +1141,7 @@ const setupAllContracts = async ({ toWei('10'), // 10x max leverage toWei('100000'), // 100000 max market debt toWei('0.1'), // 10% max funding rate - toWei('1'), // 100% max funding rate skew + toWei('1000'), // 1000 units minSkewScale toWei('0.0125'), // 1.25% per hour max funding rate of change { from: owner } ),