Skip to content

Futures remove closure fee and rounding #1610

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 31 additions & 64 deletions contracts/FuturesMarketBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
*/
function _maxOrderSizes(uint price) internal view returns (uint, uint) {
(uint long, uint short) = _marketSizes();
int sizeLimit = int(_maxMarketValueUSD(baseAsset)).divideDecimalRound(int(price));
int sizeLimit = int(_maxMarketValueUSD(baseAsset)).divideDecimal(int(price));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would reduce the sizeLimit capacity slightly (doesn't round up).

return (uint(sizeLimit.sub(_min(int(long), sizeLimit))), uint(sizeLimit.sub(_min(int(short), sizeLimit))));
}

Expand All @@ -271,7 +271,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
function _marketDebt(uint price) internal view returns (uint) {
// see comment explaining this calculation in _positionDebtCorrection()
int totalDebt =
marketSkew.multiplyDecimalRound(int(price).add(_nextFundingEntry(fundingSequence.length, price))).add(
marketSkew.multiplyDecimal(int(price).add(_nextFundingEntry(fundingSequence.length, price))).add(
_entryDebtCorrection
);

Expand All @@ -295,14 +295,14 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
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).divideDecimalRound(price);
uint skewScaleBaseAsset = _skewScaleUSD(baseAsset).divideDecimal(price);

// parameters may not be set, don't divide by zero
if (skewScaleBaseAsset == 0) {
return 0;
}

return marketSkew.divideDecimalRound(int(skewScaleBaseAsset));
return marketSkew.divideDecimal(int(skewScaleBaseAsset));
}

/*
Expand All @@ -316,7 +316,6 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
uint makerFee,
uint takerFeeNextPrice,
uint makerFeeNextPrice,
uint closureFee,
uint nextPriceConfirmWindow,
uint maxLeverage,
uint maxMarketValueUSD,
Expand All @@ -331,7 +330,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
function _currentFundingRate(uint price) internal view returns (int) {
int maxFundingRate = int(_maxFundingRate(baseAsset));
// Note the minus sign: funding flows in the opposite direction to the skew.
return _min(_max(-_UNIT, -_proportionalSkew(price)), _UNIT).multiplyDecimalRound(maxFundingRate);
return _min(_max(-_UNIT, -_proportionalSkew(price)), _UNIT).multiplyDecimal(maxFundingRate);
}

/*
Expand All @@ -352,7 +351,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut

function _unrecordedFunding(uint price) internal view returns (int funding) {
int elapsed = int(block.timestamp.sub(fundingLastRecomputed));
return _currentFundingRatePerSecond(price).multiplyDecimalRound(int(price)).mul(elapsed);
return _currentFundingRatePerSecond(price).multiplyDecimal(int(price)).mul(elapsed);
}

/*
Expand Down Expand Up @@ -454,7 +453,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
}

function _notionalValue(Position memory position, uint price) internal pure returns (int value) {
return position.size.multiplyDecimalRound(int(price));
return position.size.multiplyDecimal(int(price));
}

/*
Expand All @@ -467,7 +466,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut

function _profitLoss(Position memory position, uint price) internal pure returns (int pnl) {
int priceShift = int(price).sub(int(position.lastPrice));
return position.size.multiplyDecimalRound(priceShift);
return position.size.multiplyDecimal(priceShift);
}

/*
Expand All @@ -488,7 +487,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
return 0; // The position does not exist -- no funding.
}
int net = _netFundingPerUnit(lastModifiedIndex, endFundingIndex, fundingSequence.length, price);
return position.size.multiplyDecimalRound(net);
return position.size.multiplyDecimal(net);
}

/*
Expand Down Expand Up @@ -567,7 +566,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
// 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));
uint inaccessible = _abs(_notionalValue(position, price).divideDecimalRound(maxLeverage));
uint inaccessible = _abs(_notionalValue(position, price).divideDecimal(maxLeverage));

// If the user has a position open, we'll enforce a min initial margin requirement.
if (0 < inaccessible) {
Expand Down Expand Up @@ -630,7 +629,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
// profitLoss = (price - last-price) * positionSize
// price = lastPrice + (liquidationMargin - margin) / positionSize - netFundingPerUnit
int result =
int(position.lastPrice).add(int(liqMargin).sub(int(position.margin)).divideDecimalRound(positionSize)).sub(
int(position.lastPrice).add(int(liqMargin).sub(int(position.margin)).divideDecimal(positionSize)).sub(
fundingPerUnit
);

Expand All @@ -648,7 +647,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
*/
function _liquidationFee(int positionSize, uint price) internal view returns (uint lFee) {
// size * price * fee-ratio
uint proportionalFee = _abs(positionSize).multiplyDecimalRound(price).multiplyDecimalRound(_liquidationFeeRatio());
uint proportionalFee = _abs(positionSize).multiplyDecimal(price).multiplyDecimal(_liquidationFeeRatio());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here it'll truncate instead of rounding the multiply / division

uint minFee = _minKeeperFee();
// max(proportionalFee, minFee) - to prevent not incentivising liquidations enough
return proportionalFee > minFee ? proportionalFee : minFee; // not using _max() helper because it's for signed ints
Expand All @@ -665,7 +664,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
*/
function _liquidationBuffer(int positionSize, uint price) internal view returns (uint lBuffer) {
// size * price * buffer-ratio
return _abs(positionSize).multiplyDecimalRound(price).multiplyDecimalRound(_liquidationBufferRatio());
return _abs(positionSize).multiplyDecimal(price).multiplyDecimal(_liquidationBufferRatio());
}

/**
Expand Down Expand Up @@ -754,7 +753,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
return 0;
}

return _notionalValue(position, price).divideDecimalRound(int(remainingMargin_));
return _notionalValue(position, price).divideDecimal(int(remainingMargin_));
}

/*
Expand All @@ -767,62 +766,30 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
return (_currentLeverage(position, price, remainingMargin_), isInvalid);
}

function _orderFee(int existingSize, TradeParams memory params) internal view returns (uint) {
int newSize = existingSize.add(params.sizeDelta);
int existingNotional = existingSize.multiplyDecimalRound(int(params.price));
function _orderFee(TradeParams memory params) internal view returns (uint fee) {
int notionalDiff = params.sizeDelta.multiplyDecimal(int(params.price));

// Charge the closure fee if closing a position entirely.
if (newSize == 0) {
return _abs(existingNotional.multiplyDecimalRound(int(_closureFee(baseAsset))));
}

int newNotional = newSize.multiplyDecimalRound(int(params.price));

int notionalDiff = newNotional;
if (_sameSide(newNotional, existingNotional)) {
// If decreasing a position, charge the closure fee.
if (_abs(newNotional) <= _abs(existingNotional)) {
return _abs(existingNotional.sub(newNotional).multiplyDecimalRound(int(_closureFee(baseAsset))));
}

// We now know |existingNotional| < |newNotional|, provided the new order is on the same side as an existing position,
// The existing position's notional may be larger if it is on the other side, but we neglect this,
// and take the delta in the notional to be the entire new notional size, as the existing position is closing.
notionalDiff = notionalDiff.sub(existingNotional);
}

int skew = marketSkew;
if (_sameSide(newNotional, skew)) {
if (_sameSide(notionalDiff, marketSkew)) {
// If the order is submitted on the same side as the skew, increasing it.
// The taker fee is charged on the increase.
return _abs(notionalDiff.multiplyDecimalRound(int(params.takerFee)));
}

// Otherwise if the order is opposite to the skew,
// the maker fee is charged on new notional value up to the size of the existing skew,
// and the taker fee is charged on any new skew they induce on the order's side of the market.

int fee = notionalDiff.multiplyDecimalRound(int(params.makerFee));

// The notional value of the skew after the order is filled
int postSkewNotional = skew.multiplyDecimalRound(int(params.price)).sub(existingNotional).add(newNotional);

// The order is sufficient to flip the skew, charge/rebate the difference in fees
// between maker and taker on the new skew value.
if (_sameSide(newNotional, postSkewNotional)) {
fee = fee.add(postSkewNotional.multiplyDecimalRound(int(params.takerFee).sub(int(params.makerFee))));
fee = _abs(notionalDiff.multiplyDecimal(int(params.takerFee)));
} else {
// Otherwise if the order is opposite to the skew,
// the maker fee is charged on new notional value up to the size of the existing skew,
// and the taker fee is charged on any new skew they induce on the order's side of the market.
fee = _abs(notionalDiff.multiplyDecimal(int(params.makerFee)));
}

return _abs(fee);
// the case where the order flips the skew is ignored for simplicity due to being negligible
// in both size of effect and frequency of occurrence
}

/*
* Reports the fee for submitting an order of a given size. Orders that increase the skew will be more
* expensive than ones that decrease it; closing positions implies a different fee rate.
*/
function orderFee(address account, int sizeDelta) external view returns (uint fee, bool invalid) {
function orderFee(int sizeDelta) external view returns (uint fee, bool invalid) {
(uint price, bool isInvalid) = _assetPrice();
int positionSize = positions[account].size;
TradeParams memory params =
TradeParams({
sizeDelta: sizeDelta,
Expand All @@ -831,7 +798,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
takerFee: _takerFee(baseAsset),
makerFee: _makerFee(baseAsset)
});
return (_orderFee(positionSize, params), isInvalid);
return (_orderFee(params), isInvalid);
}

function _postTradeDetails(Position memory oldPos, TradeParams memory params)
Expand All @@ -857,7 +824,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut

// Deduct the fee.
// It is an error if the realised margin minus the fee is negative or subject to liquidation.
fee = _orderFee(oldPos.size, params);
fee = _orderFee(params);
(uint newMargin, Status status) = _realisedMargin(oldPos, params.fundingIndex, params.price, -int(fee));
if (_isError(status)) {
return (oldPos, 0, status);
Expand All @@ -879,7 +846,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
// We'll allow a little extra headroom for rounding errors.
{
// stack too deep
int leverage = newSize.multiplyDecimalRound(int(params.price)).divideDecimalRound(int(newMargin.add(fee)));
int leverage = newSize.multiplyDecimal(int(params.price)).divideDecimal(int(newMargin.add(fee)));
if (_maxLeverage(baseAsset).add(uint(_UNIT) / 100) < _abs(leverage)) {
return (oldPos, 0, Status.MaxLeverageExceeded);
}
Expand All @@ -889,7 +856,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
// Allow a bit of extra value in case of rounding errors.
if (
_orderSizeTooLarge(
uint(int(_maxMarketValueUSD(baseAsset).add(100 * uint(_UNIT))).divideDecimalRound(int(params.price))),
uint(int(_maxMarketValueUSD(baseAsset).add(100 * uint(_UNIT))).divideDecimal(int(params.price))),
oldPos.size,
newPos.size
)
Expand Down Expand Up @@ -1053,7 +1020,7 @@ contract FuturesMarketBase is Owned, Proxyable, MixinFuturesMarketSettings, IFut
*/
return
int(position.margin).sub(
position.size.multiplyDecimalRound(int(position.lastPrice).add(fundingSequence[position.fundingIndex]))
position.size.multiplyDecimal(int(position.lastPrice).add(fundingSequence[position.fundingIndex]))
);
}

Expand Down
2 changes: 0 additions & 2 deletions contracts/FuturesMarketData.sol
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ contract FuturesMarketData {
uint makerFee,
uint takerFeeNextPrice,
uint makerFeeNextPrice,
uint closureFee,
uint nextPriceConfirmWindow,
uint maxLeverage,
uint maxMarketValueUSD,
Expand All @@ -155,7 +154,6 @@ contract FuturesMarketData {
makerFee,
takerFeeNextPrice,
makerFeeNextPrice,
closureFee,
nextPriceConfirmWindow,
maxLeverage,
maxMarketValueUSD,
Expand Down
15 changes: 0 additions & 15 deletions contracts/FuturesMarketSettings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,6 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar
return _makerFeeNextPrice(_baseAsset);
}

/*
* The fee charged when reducing the size of a position.
*/
function closureFee(bytes32 _baseAsset) public view returns (uint) {
return _closureFee(_baseAsset);
}

/*
* The number of price update rounds during which confirming next-price is allowed
*/
Expand Down Expand Up @@ -121,7 +114,6 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar
uint _makerFee,
uint _takerFeeNextPrice,
uint _makerFeeNextPrice,
uint _closureFee,
uint _nextPriceConfirmWindow,
uint _maxLeverage,
uint _maxMarketValueUSD,
Expand Down Expand Up @@ -197,11 +189,6 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar
_setParameter(_baseAsset, PARAMETER_MAKER_FEE_NEXT_PRICE, _makerFeeNextPrice);
}

function setClosureFee(bytes32 _baseAsset, uint _closureFee) public onlyOwner {
require(_closureFee <= 1e18, "closure fee greater than 1");
_setParameter(_baseAsset, PARAMETER_CLOSURE_FEE, _closureFee);
}

function setNextPriceConfirmWindow(bytes32 _baseAsset, uint _nextPriceConfirmWindow) public onlyOwner {
_setParameter(_baseAsset, PARAMETER_NEXT_PRICE_CONFIRM_WINDOW, _nextPriceConfirmWindow);
}
Expand Down Expand Up @@ -243,7 +230,6 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar
uint _makerFee,
uint _takerFeeNextPrice,
uint _makerFeeNextPrice,
uint _closureFee,
uint _nextPriceConfirmWindow,
uint _maxLeverage,
uint _maxMarketValueUSD,
Expand All @@ -256,7 +242,6 @@ contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMar
setMakerFee(_baseAsset, _makerFee);
setTakerFeeNextPrice(_baseAsset, _takerFeeNextPrice);
setMakerFeeNextPrice(_baseAsset, _makerFeeNextPrice);
setClosureFee(_baseAsset, _closureFee);
setNextPriceConfirmWindow(_baseAsset, _nextPriceConfirmWindow);
setMaxLeverage(_baseAsset, _maxLeverage);
setMaxMarketValueUSD(_baseAsset, _maxMarketValueUSD);
Expand Down
7 changes: 0 additions & 7 deletions contracts/MixinFuturesMarketSettings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ contract MixinFuturesMarketSettings is MixinResolver {
bytes32 internal constant PARAMETER_MAKER_FEE = "makerFee";
bytes32 internal constant PARAMETER_TAKER_FEE_NEXT_PRICE = "takerFeeNextPrice";
bytes32 internal constant PARAMETER_MAKER_FEE_NEXT_PRICE = "makerFeeNextPrice";
bytes32 internal constant PARAMETER_CLOSURE_FEE = "closureFee";
bytes32 internal constant PARAMETER_NEXT_PRICE_CONFIRM_WINDOW = "nextPriceConfirmWindow";
bytes32 internal constant PARAMETER_MAX_LEVERAGE = "maxLeverage";
bytes32 internal constant PARAMETER_MAX_MARKET_VALUE = "maxMarketValueUSD";
Expand Down Expand Up @@ -76,10 +75,6 @@ contract MixinFuturesMarketSettings is MixinResolver {
return _parameter(_baseAsset, PARAMETER_MAKER_FEE_NEXT_PRICE);
}

function _closureFee(bytes32 _baseAsset) internal view returns (uint) {
return _parameter(_baseAsset, PARAMETER_CLOSURE_FEE);
}

function _nextPriceConfirmWindow(bytes32 _baseAsset) internal view returns (uint) {
return _parameter(_baseAsset, PARAMETER_NEXT_PRICE_CONFIRM_WINDOW);
}
Expand Down Expand Up @@ -112,7 +107,6 @@ contract MixinFuturesMarketSettings is MixinResolver {
uint makerFee,
uint takerFeeNextPrice,
uint makerFeeNextPrice,
uint closureFee,
uint nextPriceConfirmWindow,
uint maxLeverage,
uint maxMarketValueUSD,
Expand All @@ -125,7 +119,6 @@ contract MixinFuturesMarketSettings is MixinResolver {
makerFee = _makerFee(_baseAsset);
takerFeeNextPrice = _takerFeeNextPrice(_baseAsset);
makerFeeNextPrice = _makerFeeNextPrice(_baseAsset);
closureFee = _closureFee(_baseAsset);
nextPriceConfirmWindow = _nextPriceConfirmWindow(_baseAsset);
maxLeverage = _maxLeverage(_baseAsset);
maxMarketValueUSD = _maxMarketValueUSD(_baseAsset);
Expand Down
6 changes: 3 additions & 3 deletions contracts/MixinFuturesNextPriceOrders.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ contract MixinFuturesNextPriceOrders is FuturesMarketBase {
_revertIfError(status);

// deduct fees from margin
uint commitDeposit = _nextPriceCommitDeposit(position.size, params);
uint commitDeposit = _nextPriceCommitDeposit(params);
uint keeperDeposit = _minKeeperFee();
_updatePositionMargin(position, fundingIndex, price, -int(commitDeposit + keeperDeposit));
// emit event for modidying the position (subtracting the fees from margin)
Expand Down Expand Up @@ -206,13 +206,13 @@ contract MixinFuturesNextPriceOrders is FuturesMarketBase {
}

// calculate the commitFee, which is the fee that would be charged on the order if it was spot
function _nextPriceCommitDeposit(int existingSize, TradeParams memory params) internal view returns (uint) {
function _nextPriceCommitDeposit(TradeParams memory params) internal view returns (uint) {
// modify params to spot fee
params.takerFee = _takerFee(baseAsset);
params.makerFee = _makerFee(baseAsset);
// 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)
return _orderFee(existingSize, params);
return _orderFee(params);
}

///// Events
Expand Down
Loading