Skip to content
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

Market debt #1700

Merged
merged 20 commits into from
Jul 31, 2023
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
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???
Copy link
Contributor

Choose a reason for hiding this comment

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

0 is fine. we probably should never revert on reported debt since it could brick the core system

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
Loading