Skip to content

Commit

Permalink
Market debt (#1700)
Browse files Browse the repository at this point in the history
* checkpoint - conceptual

* template test

* some comments

* checkpoint: use requestedMarketId

* linting

* compute reported debt

* doc fix (spotted)

* market debt

* checkpoint

* wip: checkpoint

* tests passing (and makes sense)

* lint fix

* rogue validation

* Update markets/perps-market/contracts/modules/PerpsMarketFactoryModule.sol

Co-authored-by: Sunny Vempati <[email protected]>

* wip test

* wip test

* test reported debt with funding and partial liquidations

* remove console log

* small cleanup

* fix interface

---------

Co-authored-by: Sunny Vempati <[email protected]>
  • Loading branch information
leomassazza and sunnyvempati authored Jul 31, 2023
1 parent 08ea86d commit 3d52128
Show file tree
Hide file tree
Showing 17 changed files with 784 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,10 @@ interface IGlobalPerpsMarketModule {
external
view
returns (uint256 minLiquidationRewardUsd, uint256 maxLiquidationRewardUsd);

/**
* @notice Gets the total collateral value of all deposited collateral from all traders.
* @return totalCollateralValue value of all collateral
*/
function totalGlobalCollateralValue() external view returns (uint256 totalCollateralValue);
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,14 @@ interface IPerpsAccountModule {
* @notice Gets the details of an open position.
* @param accountId Id of the account.
* @param marketId Id of the position market.
* @return pnl pnl of the position.
* @return totalPnl pnl of the entire position including funding.
* @return accruedFunding accrued funding of the position.
* @return size size of the position.
* @return positionSize size of the position.
*/
function getOpenPosition(
uint128 accountId,
uint128 marketId
) external view returns (int pnl, int accruedFunding, int size);
) external view returns (int256 totalPnl, int256 accruedFunding, int128 positionSize);

/**
* @notice Gets the available margin of an account. It can be negative due to pnl.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ contract AsyncOrderSettlementModule is IAsyncOrderSettlementModule, IMarketEvent
PerpsAccount.Data storage perpsAccount = PerpsAccount.load(runtime.accountId);

// use fill price to calculate realized pnl
(runtime.pnl, , , ) = oldPosition.getPnl(fillPrice);
(runtime.pnl, , , , ) = oldPosition.getPnl(fillPrice);
runtime.pnlUint = MathUtil.abs(runtime.pnl);

if (runtime.pnl > 0) {
Expand Down
14 changes: 14 additions & 0 deletions markets/perps-market/contracts/modules/GlobalPerpsMarketModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity >=0.8.11 <0.9.0;

import {GlobalPerpsMarketConfiguration} from "../storage/GlobalPerpsMarketConfiguration.sol";
import {GlobalPerpsMarket} from "../storage/GlobalPerpsMarket.sol";
import {IGlobalPerpsMarketModule} from "../interfaces/IGlobalPerpsMarketModule.sol";
import {OwnableStorage} from "@synthetixio/core-contracts/contracts/ownership/OwnableStorage.sol";

Expand All @@ -11,6 +12,7 @@ import {OwnableStorage} from "@synthetixio/core-contracts/contracts/ownership/Ow
*/
contract GlobalPerpsMarketModule is IGlobalPerpsMarketModule {
using GlobalPerpsMarketConfiguration for GlobalPerpsMarketConfiguration.Data;
using GlobalPerpsMarket for GlobalPerpsMarket.Data;

/**
* @inheritdoc IGlobalPerpsMarketModule
Expand Down Expand Up @@ -81,4 +83,16 @@ contract GlobalPerpsMarketModule is IGlobalPerpsMarketModule {
minLiquidationRewardUsd = store.minLiquidationRewardUsd;
maxLiquidationRewardUsd = store.maxLiquidationRewardUsd;
}

/**
* @inheritdoc IGlobalPerpsMarketModule
*/
function totalGlobalCollateralValue()
external
view
override
returns (uint256 totalCollateralValue)
{
return GlobalPerpsMarket.load().totalCollateralValue();
}
}
6 changes: 3 additions & 3 deletions markets/perps-market/contracts/modules/PerpsAccountModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ contract PerpsAccountModule is IPerpsAccountModule {
function getOpenPosition(
uint128 accountId,
uint128 marketId
) external view override returns (int, int, int) {
) external view override returns (int256 totalPnl, int256 accruedFunding, int128 positionSize) {
PerpsMarket.Data storage perpsMarket = PerpsMarket.loadValid(marketId);

Position.Data storage position = perpsMarket.positions[accountId];

(, int pnl, int accruedFunding, , ) = position.getPositionData(
(, totalPnl, , accruedFunding, , ) = position.getPositionData(
PerpsPrice.getCurrentPrice(marketId)
);
return (pnl, accruedFunding, position.size);
return (totalPnl, accruedFunding, position.size);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {ParameterError} from "@synthetixio/core-contracts/contracts/errors/Param
import {MathUtil} from "../utils/MathUtil.sol";
import {PerpsMarketConfiguration} from "../storage/PerpsMarketConfiguration.sol";
import {IMarket} from "@synthetixio/main/contracts/interfaces/external/IMarket.sol";
import {SetUtil} from "@synthetixio/core-contracts/contracts/utils/SetUtil.sol";
import {SafeCastU256, SafeCastI256, SafeCastU128} from "@synthetixio/core-contracts/contracts/utils/SafeCast.sol";

/**
* @title Module for registering perpetual futures markets. The factory tracks all markets in the system and consolidates implementation.
Expand All @@ -34,6 +36,11 @@ contract PerpsMarketFactoryModule is IPerpsMarketFactoryModule {
using GlobalPerpsMarket for GlobalPerpsMarket.Data;
using PerpsPrice for PerpsPrice.Data;
using DecimalMath for uint256;
using SafeCastU256 for uint256;
using SafeCastU128 for uint128;
using SafeCastI256 for int256;
using SetUtil for SetUtil.UintSet;
using PerpsMarket for PerpsMarket.Data;

bytes32 private constant _CREATE_MARKET_FEATURE_FLAG = "createMarket";

Expand Down Expand Up @@ -101,11 +108,33 @@ contract PerpsMarketFactoryModule is IPerpsMarketFactoryModule {

function name(uint128 perpsMarketId) external view override returns (string memory) {
// todo: set name on initialize?
perpsMarketId; // silence unused variable warning
return "Perps Market";
}

function reportedDebt(uint128 perpsMarketId) external view override returns (uint256) {
// TODO
PerpsMarketFactory.Data storage factory = PerpsMarketFactory.load();

if (factory.perpsMarketId == perpsMarketId) {
// debt is the total debt of all markets
// can be computed as total collateral value - sum_each_market( debt )
uint totalCollateralValue = GlobalPerpsMarket.load().totalCollateralValue();
int totalMarketDebt;

SetUtil.UintSet storage activeMarkets = GlobalPerpsMarket.load().activeMarkets;
uint256 activeMarketsLength = activeMarkets.length();
for (uint i = 1; i <= activeMarketsLength; i++) {
uint128 marketId = activeMarkets.valueAt(i).to128();
totalMarketDebt += PerpsMarket.load(marketId).marketDebt(
PerpsPrice.getCurrentPrice(marketId)
);
}

int totalDebt = totalCollateralValue.toInt() + totalMarketDebt;
return totalDebt < 0 ? 0 : totalDebt.toUint();
}

// TODO Should revert if perpsMarketId is not correct???
return 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ library GlobalPerpsMarket {
ISpotMarketSystem spotMarket = PerpsMarketFactory.load().spotMarket;
SetUtil.UintSet storage activeCollateralTypes = self.activeCollateralTypes;
uint256 activeCollateralLength = activeCollateralTypes.length();
for (uint i = 1; i < activeCollateralLength; i++) {
for (uint i = 1; i <= activeCollateralLength; i++) {
uint128 synthMarketId = activeCollateralTypes.valueAt(i).to128();

if (synthMarketId == 0) {
Expand Down
4 changes: 2 additions & 2 deletions markets/perps-market/contracts/storage/PerpsAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ library PerpsAccount {
for (uint i = 1; i <= self.openPositionMarketIds.length(); i++) {
uint128 marketId = self.openPositionMarketIds.valueAt(i).to128();
Position.Data storage position = PerpsMarket.load(marketId).positions[self.id];
(int pnl, , , ) = position.getPnl(PerpsPrice.getCurrentPrice(marketId));
(int pnl, , , , ) = position.getPnl(PerpsPrice.getCurrentPrice(marketId));
totalPnl += pnl;
}
}
Expand All @@ -207,7 +207,7 @@ library PerpsAccount {
uint128 marketId = self.openPositionMarketIds.valueAt(i).to128();

Position.Data storage position = PerpsMarket.load(marketId).positions[self.id];
(uint openInterest, , , , ) = position.getPositionData(
(uint openInterest, , , , , ) = position.getPositionData(
PerpsPrice.getCurrentPrice(marketId)
);
totalAccountOpenInterest += openInterest;
Expand Down
39 changes: 36 additions & 3 deletions markets/perps-market/contracts/storage/PerpsMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,18 @@ library PerpsMarket {
uint128 id;
int256 skew;
uint256 size;
int lastFundingRate;
int lastFundingValue;
// TODO: move to new data structure?
int256 lastFundingRate;
int256 lastFundingValue;
uint256 lastFundingTime;
// liquidation data
uint128 lastTimeLiquidationCapacityUpdated;
uint128 lastUtilizedLiquidationCapacity;
// debt calculation
// accumulates total notional size of the market including accrued funding until the last time any position changed
int256 debtCorrectionAccumulator;
// accountId => asyncOrder
mapping(uint => AsyncOrder.Data) asyncOrders;
// accountId => position
mapping(uint => Position.Data) positions;
}
Expand Down Expand Up @@ -139,20 +145,34 @@ library PerpsMarket {
}

/**
* @dev If you call this method, please ensure you emit an event so offchain solution can index market state history properly
* @dev Use this function to update both market/position size/skew.
* @dev Size and skew should not be updated directly.
* @dev The return value is used to emit a MarketUpdated event.
*/
function updatePositionData(
Data storage self,
uint128 accountId,
Position.Data memory newPosition
) internal returns (MarketUpdateData memory) {
Position.Data storage oldPosition = self.positions[accountId];

int128 oldPositionSize = oldPosition.size;
int128 newPositionSize = newPosition.size;

self.size = (self.size + MathUtil.abs(newPositionSize)) - MathUtil.abs(oldPositionSize);
self.skew += newPositionSize - oldPositionSize;

uint currentPrice = newPosition.latestInteractionPrice;
(int totalPositionPnl, , , , ) = oldPosition.getPnl(currentPrice);

int sizeDelta = newPositionSize - oldPositionSize;
int fundingDelta = calculateNextFunding(self, currentPrice).mulDecimal(sizeDelta);
int notionalDelta = currentPrice.toInt().mulDecimal(sizeDelta);

// update the market debt correction accumulator before losing oldPosition details
// by adding the new updated notional (old - new size) plus old position pnl
self.debtCorrectionAccumulator += fundingDelta + notionalDelta + totalPositionPnl;

oldPosition.update(newPosition);

return
Expand Down Expand Up @@ -280,4 +300,17 @@ library PerpsMarket {
}
}
}

/**
* @dev Returns the market debt incurred by all positions
* @notice Market debt is the sum of all position sizes multiplied by the price, and old positions pnl that is included in the debt correction accumulator.
*/
function marketDebt(Data storage self, uint price) internal view returns (int) {
// all positions sizes multiplied by the price is equivalent to skew times price
// and the debt correction accumulator is the sum of all positions pnl
int traderUnrealizedPnl = self.skew.mulDecimal(price.toInt());
int unrealizedFunding = self.skew.mulDecimal(calculateNextFunding(self, price));

return traderUnrealizedPnl + unrealizedFunding - self.debtCorrectionAccumulator;
}
}
20 changes: 16 additions & 4 deletions markets/perps-market/contracts/storage/Position.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,38 @@ library Position {
view
returns (
uint256 notionalValue,
int pnl,
int totalPnl,
int pricePnl,
int accruedFunding,
int netFundingPerUnit,
int nextFunding
)
{
(pnl, accruedFunding, netFundingPerUnit, nextFunding) = getPnl(self, price);
(totalPnl, pricePnl, accruedFunding, netFundingPerUnit, nextFunding) = getPnl(self, price);
notionalValue = getNotionalValue(self, price);
}

function getPnl(
Data storage self,
uint price
) internal view returns (int pnl, int accruedFunding, int netFundingPerUnit, int nextFunding) {
)
internal
view
returns (
int totalPnl,
int pricePnl,
int accruedFunding,
int netFundingPerUnit,
int nextFunding
)
{
nextFunding = PerpsMarket.load(self.marketId).calculateNextFunding(price);
netFundingPerUnit = nextFunding - self.latestInteractionFunding;
accruedFunding = self.size.mulDecimal(netFundingPerUnit);

int priceShift = price.toInt() - self.latestInteractionPrice.toInt();
pnl = self.size.mulDecimal(priceShift) + accruedFunding;
pricePnl = self.size.mulDecimal(priceShift);
totalPnl = pricePnl + accruedFunding;
}

function getNotionalValue(Data storage self, uint256 price) internal view returns (uint256) {
Expand Down
6 changes: 4 additions & 2 deletions markets/perps-market/storage.dump.sol
Original file line number Diff line number Diff line change
Expand Up @@ -575,11 +575,13 @@ library PerpsMarket {
uint128 id;
int256 skew;
uint256 size;
int lastFundingRate;
int lastFundingValue;
int256 lastFundingRate;
int256 lastFundingValue;
uint256 lastFundingTime;
uint128 lastTimeLiquidationCapacityUpdated;
uint128 lastUtilizedLiquidationCapacity;
int256 debtCorrectionAccumulator;
mapping(uint => AsyncOrder.Data) asyncOrders;
mapping(uint => Position.Data) positions;
}
struct MarketUpdateData {
Expand Down
Loading

0 comments on commit 3d52128

Please sign in to comment.