@@ -15,6 +15,7 @@ import "./SafeDecimalMath.sol";
15
15
// Internal references
16
16
import "./interfaces/IExchangeCircuitBreaker.sol " ;
17
17
import "./interfaces/IExchangeRates.sol " ;
18
+ import "./interfaces/IExchanger.sol " ;
18
19
import "./interfaces/ISystemStatus.sol " ;
19
20
import "./interfaces/IERC20.sol " ;
20
21
@@ -92,6 +93,9 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
92
93
// This is the same unit as used inside `SignedSafeDecimalMath`.
93
94
int private constant _UNIT = int (10 ** uint (18 ));
94
95
96
+ //slither-disable-next-line naming-convention
97
+ bytes32 internal constant sUSD = "sUSD " ;
98
+
95
99
/* ========== STATE VARIABLES ========== */
96
100
97
101
// The asset being traded in this market. This should be a valid key into the ExchangeRates contract.
@@ -140,6 +144,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
140
144
/* ---------- Address Resolver Configuration ---------- */
141
145
142
146
bytes32 internal constant CONTRACT_CIRCUIT_BREAKER = "ExchangeCircuitBreaker " ;
147
+ bytes32 internal constant CONTRACT_EXCHANGER = "Exchanger " ;
143
148
bytes32 internal constant CONTRACT_FUTURESMARKETMANAGER = "FuturesMarketManager " ;
144
149
bytes32 internal constant CONTRACT_FUTURESMARKETSETTINGS = "FuturesMarketSettings " ;
145
150
bytes32 internal constant CONTRACT_SYSTEMSTATUS = "SystemStatus " ;
@@ -177,6 +182,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
177
182
_errorMessages[uint8 (Status.NotPermitted)] = "Not permitted by this address " ;
178
183
_errorMessages[uint8 (Status.NilOrder)] = "Cannot submit empty order " ;
179
184
_errorMessages[uint8 (Status.NoPositionOpen)] = "No position open " ;
185
+ _errorMessages[uint8 (Status.PriceTooVolatile)] = "Price too volatile " ;
180
186
}
181
187
182
188
/* ========== VIEWS ========== */
@@ -185,18 +191,23 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
185
191
186
192
function resolverAddressesRequired () public view returns (bytes32 [] memory addresses ) {
187
193
bytes32 [] memory existingAddresses = MixinFuturesMarketSettings.resolverAddressesRequired ();
188
- bytes32 [] memory newAddresses = new bytes32 [](4 );
189
- newAddresses[0 ] = CONTRACT_CIRCUIT_BREAKER;
190
- newAddresses[1 ] = CONTRACT_FUTURESMARKETMANAGER;
191
- newAddresses[2 ] = CONTRACT_FUTURESMARKETSETTINGS;
192
- newAddresses[3 ] = CONTRACT_SYSTEMSTATUS;
194
+ bytes32 [] memory newAddresses = new bytes32 [](5 );
195
+ newAddresses[0 ] = CONTRACT_EXCHANGER;
196
+ newAddresses[1 ] = CONTRACT_CIRCUIT_BREAKER;
197
+ newAddresses[2 ] = CONTRACT_FUTURESMARKETMANAGER;
198
+ newAddresses[3 ] = CONTRACT_FUTURESMARKETSETTINGS;
199
+ newAddresses[4 ] = CONTRACT_SYSTEMSTATUS;
193
200
addresses = combineArrays (existingAddresses, newAddresses);
194
201
}
195
202
196
203
function _exchangeCircuitBreaker () internal view returns (IExchangeCircuitBreaker) {
197
204
return IExchangeCircuitBreaker (requireAndGetAddress (CONTRACT_CIRCUIT_BREAKER));
198
205
}
199
206
207
+ function _exchanger () internal view returns (IExchanger) {
208
+ return IExchanger (requireAndGetAddress (CONTRACT_EXCHANGER));
209
+ }
210
+
200
211
function _systemStatus () internal view returns (ISystemStatus) {
201
212
return ISystemStatus (requireAndGetAddress (CONTRACT_SYSTEMSTATUS));
202
213
}
@@ -542,22 +553,25 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
542
553
return _notionalValue (position.size, price).divideDecimal (int (remainingMargin_));
543
554
}
544
555
545
- function _orderFee (TradeParams memory params ) internal view returns (uint fee ) {
556
+ function _orderFee (TradeParams memory params , uint dynamicFeeRate ) internal view returns (uint fee ) {
557
+ // usd value of the difference in position
546
558
int notionalDiff = params.sizeDelta.multiplyDecimal (int (params.price));
547
559
548
- if (_sameSide (notionalDiff, marketSkew)) {
549
- // If the order is submitted on the same side as the skew, increasing it.
550
- // The taker fee is charged on the increase.
551
- fee = _abs (notionalDiff.multiplyDecimal (int (params.takerFee)));
552
- } else {
553
- // Otherwise if the order is opposite to the skew,
554
- // the maker fee is charged on new notional value up to the size of the existing skew,
555
- // and the taker fee is charged on any new skew they induce on the order's side of the market.
556
- fee = _abs (notionalDiff.multiplyDecimal (int (params.makerFee)));
557
- }
558
-
560
+ // If the order is submitted on the same side as the skew, increasing it. The taker fee is charged.
561
+ // Otherwise if the order is opposite to the skew, the maker fee is charged.
559
562
// the case where the order flips the skew is ignored for simplicity due to being negligible
560
563
// in both size of effect and frequency of occurrence
564
+ uint staticRate = _sameSide (notionalDiff, marketSkew) ? params.takerFee : params.makerFee;
565
+ uint feeRate = staticRate.add (dynamicFeeRate);
566
+ return _abs (notionalDiff.multiplyDecimal (int (feeRate)));
567
+ }
568
+
569
+ /// Uses the exchanger to get the dynamic fee (SIP-184) for trading from sUSD to baseAsset
570
+ /// this assumes dynamic fee is symmetric in direction of trade.
571
+ /// @dev this is a pretty expensive action in terms of execution gas as it queries a lot
572
+ /// of past rates from oracle. Shoudn't be much of an issue on a rollup though.
573
+ function _dynamicFeeRate () internal view returns (uint feeRate , bool tooVolatile ) {
574
+ return _exchanger ().dynamicFeeRateForExchange (sUSD, baseAsset);
561
575
}
562
576
563
577
function _postTradeDetails (Position memory oldPos , TradeParams memory params )
@@ -581,9 +595,17 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
581
595
582
596
int newSize = int (oldPos.size).add (params.sizeDelta);
583
597
598
+ // get the dynamic fee rate SIP-184
599
+ (uint dynamicFeeRate , bool tooVolatile ) = _dynamicFeeRate ();
600
+ if (tooVolatile) {
601
+ return (oldPos, 0 , Status.PriceTooVolatile);
602
+ }
603
+
604
+ // calculate the total fee for exchange
605
+ fee = _orderFee (params, dynamicFeeRate);
606
+
584
607
// Deduct the fee.
585
608
// It is an error if the realised margin minus the fee is negative or subject to liquidation.
586
- fee = _orderFee (params);
587
609
(uint newMargin , Status status ) = _realisedMargin (oldPos, params.fundingIndex, params.price, - int (fee));
588
610
if (_isError (status)) {
589
611
return (oldPos, 0 , status);
@@ -702,8 +724,15 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
702
724
// check that synth is active, and wasn't suspended, revert with appropriate message
703
725
_systemStatus ().requireSynthActive (baseAsset);
704
726
// check if circuit breaker if price is within deviation tolerance and system & synth is active
727
+ // note: rateWithBreakCircuit (mutative) is used here instead of rateWithInvalid (view). This is
728
+ // despite reverting immediately after if circuit is broken, which may seem silly.
729
+ // This is in order to persist last-rate in exchangeCircuitBreaker in the happy case
730
+ // because last-rate is what used for measuring the deviation for subsequent trades.
705
731
(uint price , bool circuitBroken ) = _exchangeCircuitBreaker ().rateWithBreakCircuit (baseAsset);
706
732
// revert if price is invalid or circuit was broken
733
+ // note: we revert here, which means that circuit is not really broken (is not persisted), this is
734
+ // because the futures methods and interface are designed for reverts, and do not support no-op
735
+ // return values.
707
736
_revertIfError (circuitBroken, Status.InvalidPrice);
708
737
return price;
709
738
}
0 commit comments