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

trader view precompile #181

Open
wants to merge 14 commits into
base: isolated-margin
Choose a base branch
from
26 changes: 26 additions & 0 deletions contracts/contracts/hubble-v2/interfaces/ITraderViewer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IClearingHouse } from "./IClearingHouse.sol";
import { ILimitOrderBook } from "./IJuror.sol";
import { IOrderHandler } from "./IOrderHandler.sol";

interface ITraderViewer {
function getNotionalPositionAndMargin(address trader, bool includeFundingPayments, IClearingHouse.Mode mode) external view returns(uint256 notionalPosition, int256 margin, uint256 requiredMargin);

function getTraderDataForMarket(address trader, uint256 ammIndex, IClearingHouse.Mode mode) external view returns(
bool isIsolated, uint256 notionalPosition, int256 unrealizedPnl, uint256 requiredMargin, int256 pendingFunding
);

function getCrossMarginAccountData(address trader, IClearingHouse.Mode mode)
external
view
returns(uint256 notionalPosition, uint256 requiredMargin, int256 unrealizedPnl, int256 pendingFunding);

function getTotalFundingForCrossMarginPositions(address trader) external view returns(int256 totalFunding);

function validateCancelLimitOrderV2(ILimitOrderBook.Order memory order, address sender, bool assertLowMargin, bool assertOverPositionCap) external view returns (string memory err, bytes32 orderHash, IOrderHandler.CancelOrderRes memory res);

function getRequiredMargin(int256 baseAssetQuantity, uint256 price, uint ammIndex, address trader) external view returns(uint256 requiredMargin);
}
10 changes: 10 additions & 0 deletions plugin/evm/orderbook/hubbleutils/data_structures.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ const (
Min_Allowable_Margin
)

const (
Cross MarginMode = iota
Isolated
)

type AccountPreferences struct {
MarginMode MarginMode
MarginFraction *big.Int
}

type Collateral struct {
Price *big.Int // scaled by 1e6
Weight *big.Int // scaled by 1e6
Expand Down
51 changes: 46 additions & 5 deletions plugin/evm/orderbook/hubbleutils/margin_math.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ type HubbleState struct {
}

type UserState struct {
Positions map[Market]*Position
ReduceOnlyAmounts []*big.Int
Margins []*big.Int
PendingFunding *big.Int
ReservedMargin *big.Int
Positions map[Market]*Position
ReduceOnlyAmounts []*big.Int
Margins []*big.Int
PendingFunding *big.Int
ReservedMargin *big.Int
AccountPreferences map[Market]*AccountPreferences
}

func UpgradeVersionV0orV1(blockTimestamp uint64) UpgradeVersion {
Expand All @@ -40,6 +41,7 @@ func UpgradeVersionV0orV1(blockTimestamp uint64) UpgradeVersion {
return V0
}

// @todo update to newer version wherever it is used in the evm
func GetAvailableMargin(hState *HubbleState, userState *UserState) *big.Int {
notionalPosition, margin := GetNotionalPositionAndMargin(hState, userState, Min_Allowable_Margin)
return GetAvailableMargin_(notionalPosition, margin, userState.ReservedMargin, hState.MinAllowableMargin)
Expand Down Expand Up @@ -118,6 +120,45 @@ func getOptimalPnl(hState *HubbleState, position *Position, margin *big.Int, mar
return notionalPosition, unrealizedPnl
}

func GetNotionalPositionAndRequiredMargin(hState *HubbleState, userState *UserState) (*big.Int, *big.Int, *big.Int) {
margin := Sub(GetNormalizedMargin(hState.Assets, userState.Margins), userState.PendingFunding)
notionalPosition, requiredMargin, unrealizedPnl := GetCrossMarginAccountData(hState, userState)
return notionalPosition, Add(margin, unrealizedPnl), requiredMargin
}

func GetCrossMarginAccountData(hState *HubbleState, userState *UserState) (*big.Int, *big.Int, *big.Int) {
notionalPosition := big.NewInt(0)
unrealizedPnl := big.NewInt(0)
requiredMargin := big.NewInt(0)

for _, market := range hState.ActiveMarkets {
if userState.AccountPreferences[market].MarginMode == Cross {
_notionalPosition, _unrealizedPnl, _requiredMargin := GetTraderPositionDetails(userState.Positions[market], hState.OraclePrices[market], userState.AccountPreferences[market].MarginFraction)
notionalPosition.Add(notionalPosition, _notionalPosition)
unrealizedPnl.Add(unrealizedPnl, _unrealizedPnl)
requiredMargin.Add(requiredMargin, _requiredMargin)
}
}
return notionalPosition, requiredMargin, unrealizedPnl
}

func GetTraderPositionDetails(position *Position, oraclePrice *big.Int, marginFraction *big.Int) (notionalPosition *big.Int, uPnL *big.Int, requiredMargin *big.Int) {
if position == nil || position.Size.Sign() == 0 {
return big.NewInt(0), big.NewInt(0), big.NewInt(0)
}

// based on oracle price,
notionalPosition, unrealizedPnl, _ := GetPositionMetadata(
oraclePrice,
position.OpenNotional,
position.Size,
big.NewInt(0), // margin is not used here
)
requiredMargin = Div1e6(Mul(notionalPosition, marginFraction))

return notionalPosition, unrealizedPnl, requiredMargin
}

func GetPositionMetadata(price *big.Int, openNotional *big.Int, size *big.Int, margin *big.Int) (notionalPosition *big.Int, unrealisedPnl *big.Int, marginFraction *big.Int) {
notionalPosition = GetNotionalPosition(price, size)
uPnL := new(big.Int)
Expand Down
56 changes: 52 additions & 4 deletions plugin/evm/orderbook/hubbleutils/margin_math_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ var _hState = &HubbleState{
var userState = &UserState{
Positions: map[Market]*Position{
0: {
Size: big.NewInt(0.582 * 1e18), // 0.0582
OpenNotional: big.NewInt(875 * 1e6), // 87.5, openPrice = 1503.43
Size: big.NewInt(0.582 * 1e18), // 0.582
OpenNotional: big.NewInt(875 * 1e6), // 875, openPrice = 1503.43
},
1: {
Size: Scale(big.NewInt(-101), 18), // -101
Expand All @@ -52,8 +52,18 @@ var userState = &UserState{
big.NewInt(30.5 * 1e6), // 30.5
big.NewInt(14 * 1e6), // 14
},
PendingFunding: big.NewInt(0),
ReservedMargin: big.NewInt(0),
PendingFunding: big.NewInt(-50 * 1e6), // +50
ReservedMargin: big.NewInt(60 * 1e6), // 60
AccountPreferences: map[Market]*AccountPreferences{
0: {
MarginMode: Cross,
MarginFraction: big.NewInt(0.2 * 1e6), // 0.2
},
1: {
MarginMode: Isolated,
MarginFraction: big.NewInt(0.1 * 1e6), // 0.1
},
},
}

func TestWeightedAndSpotCollateral(t *testing.T) {
Expand Down Expand Up @@ -238,3 +248,41 @@ func TestGetTotalNotionalPositionAndUnrealizedPnl(t *testing.T) {
assert.Equal(t, expectedNotionalPosition, notionalPosition)
assert.Equal(t, expectedUPnL, uPnL)
}

func TestGetNotionalPositionAndRequiredMargin(t *testing.T) {
t.Run("one market in cross and other in isolated mode", func(t *testing.T) {
expectedMargin := GetNormalizedMargin(_hState.Assets, userState.Margins)
fmt.Println(expectedMargin)
notionalPosition, margin, requiredMargin := GetNotionalPositionAndRequiredMargin(_hState, userState)
expectedNotionalPosition := Unscale(Mul(userState.Positions[0].Size, _hState.OraclePrices[0]), 18)
expectedUPnL := Sub(expectedNotionalPosition, userState.Positions[0].OpenNotional)
expectedMargin = Sub(Add(expectedMargin, expectedUPnL), userState.PendingFunding)
expectedRequiredMargin := Unscale(Mul(expectedNotionalPosition, userState.AccountPreferences[0].MarginFraction), 6)
assert.Equal(t, expectedNotionalPosition, notionalPosition)
assert.Equal(t, expectedMargin, margin)
assert.Equal(t, expectedRequiredMargin, requiredMargin)
})

t.Run("both markets in cross mode", func(t *testing.T) {
userState.AccountPreferences[1].MarginMode = Cross
notionalPosition, margin, requiredMargin := GetNotionalPositionAndRequiredMargin(_hState, userState)
expectedNotionalPosition := big.NewInt(0)
expectedRequiredMargin := big.NewInt(0)
expectedMargin := GetNormalizedMargin(_hState.Assets, userState.Margins)
for _, market := range _hState.ActiveMarkets {
notional := Abs(Unscale(Mul(userState.Positions[market].Size, _hState.OraclePrices[market]), 18))
expectedNotionalPosition = Add(expectedNotionalPosition, notional)
multiplier := big.NewInt(1)
if userState.Positions[market].Size.Sign() == -1 {
multiplier = big.NewInt(-1)
}
expectedUPnL := Mul(Sub(notional, userState.Positions[market].OpenNotional), multiplier)
expectedMargin = Add(expectedMargin, expectedUPnL)
expectedRequiredMargin = Add(expectedRequiredMargin, Unscale(Mul(notional, userState.AccountPreferences[market].MarginFraction), 6))
}
expectedMargin = Sub(expectedMargin, userState.PendingFunding)
assert.Equal(t, expectedNotionalPosition, notionalPosition)
assert.Equal(t, expectedMargin, margin)
assert.Equal(t, expectedRequiredMargin, requiredMargin)
})
}
97 changes: 83 additions & 14 deletions precompile/contracts/bibliophile/amm.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,26 @@ import (
)

const (
VAR_POSITIONS_SLOT int64 = 1
VAR_CUMULATIVE_PREMIUM_FRACTION int64 = 2
MAX_ORACLE_SPREAD_RATIO_SLOT int64 = 3
MAX_LIQUIDATION_RATIO_SLOT int64 = 4
MIN_SIZE_REQUIREMENT_SLOT int64 = 5
UNDERLYING_ASSET_SLOT int64 = 6
MAX_LIQUIDATION_PRICE_SPREAD int64 = 11
MULTIPLIER_SLOT int64 = 12
IMPACT_MARGIN_NOTIONAL_SLOT int64 = 19
LAST_TRADE_PRICE_SLOT int64 = 20
BIDS_SLOT int64 = 21
ASKS_SLOT int64 = 22
BIDS_HEAD_SLOT int64 = 23
ASKS_HEAD_SLOT int64 = 24
VAR_POSITIONS_SLOT int64 = 1
VAR_CUMULATIVE_PREMIUM_FRACTION int64 = 2
MAX_ORACLE_SPREAD_RATIO_SLOT int64 = 3
MAX_LIQUIDATION_RATIO_SLOT int64 = 4
MIN_SIZE_REQUIREMENT_SLOT int64 = 5
UNDERLYING_ASSET_SLOT int64 = 6
MAX_LIQUIDATION_PRICE_SPREAD int64 = 11
MULTIPLIER_SLOT int64 = 12
IMPACT_MARGIN_NOTIONAL_SLOT int64 = 19
LAST_TRADE_PRICE_SLOT int64 = 20
BIDS_SLOT int64 = 21
ASKS_SLOT int64 = 22
BIDS_HEAD_SLOT int64 = 23
ASKS_HEAD_SLOT int64 = 24
TRADE_MARGIN_FRACTION_SLOT int64 = 28
LIQUIDATION_MARGIN_FRACTION_SLOT int64 = 29
ISOLATED_TRADE_MARGIN_FRACTION_SLOT int64 = 30
ISOLATED_LIQUIDATION_MARGIN_FRACTION_SLOT int64 = 31
ACCOUNT_PREFERENCES_SLOT int64 = 33
MAX_POSITION_CAP_SLOT int64 = 34
)

// AMM State
Expand Down Expand Up @@ -146,6 +152,69 @@ func getPosition(stateDB contract.StateDB, market common.Address, trader *common
}
}

func getTradeMarginFraction(stateDB contract.StateDB, market common.Address) *big.Int {
return stateDB.GetState(market, common.BigToHash(big.NewInt(TRADE_MARGIN_FRACTION_SLOT))).Big()
}

func getLiquidationMarginFraction(stateDB contract.StateDB, market common.Address) *big.Int {
return stateDB.GetState(market, common.BigToHash(big.NewInt(LIQUIDATION_MARGIN_FRACTION_SLOT))).Big()
}

func getIsolatedTradeMarginFraction(stateDB contract.StateDB, market common.Address) *big.Int {
return stateDB.GetState(market, common.BigToHash(big.NewInt(ISOLATED_TRADE_MARGIN_FRACTION_SLOT))).Big()
}

func getIsolatedLiquidationMarginFraction(stateDB contract.StateDB, market common.Address) *big.Int {
return stateDB.GetState(market, common.BigToHash(big.NewInt(ISOLATED_LIQUIDATION_MARGIN_FRACTION_SLOT))).Big()
}

func accountPreferencesSlot(trader *common.Address) *big.Int {
return new(big.Int).SetBytes(crypto.Keccak256(append(common.LeftPadBytes(trader.Bytes(), 32), common.LeftPadBytes(big.NewInt(ACCOUNT_PREFERENCES_SLOT).Bytes(), 32)...)))
}

func getMarginMode(stateDB contract.StateDB, market common.Address, trader *common.Address) uint8 {
return uint8(stateDB.GetState(market, common.BigToHash(accountPreferencesSlot(trader))).Big().Uint64())
}

func marginFraction(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int {
return stateDB.GetState(market, common.BigToHash(new(big.Int).Add(accountPreferencesSlot(trader), big.NewInt(1)))).Big()
}

func getMaxPositionCap(stateDB contract.StateDB, market common.Address) *big.Int {
return stateDB.GetState(market, common.BigToHash(big.NewInt(MAX_POSITION_CAP_SLOT))).Big()
}

func getMarginFractionByMode(stateDB contract.StateDB, market common.Address, trader *common.Address, mode uint8) *big.Int {
if mode == hu.Maintenance_Margin {
if getMarginMode(stateDB, market, trader) == hu.Isolated {
return getIsolatedLiquidationMarginFraction(stateDB, market)
} else {
return getLiquidationMarginFraction(stateDB, market)
}
}
// retuns trade margin fraction by default
// @todo check if can be reverted in case of invalid mode
return getTraderMarginFraction(stateDB, market, trader)
}

func getTraderMarginFraction(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int {
traderMarginFraction_ := marginFraction(stateDB, market, trader)
if traderMarginFraction_.Sign() != 0 {
return traderMarginFraction_
} else if getMarginMode(stateDB, market, trader) == hu.Isolated {
return getIsolatedTradeMarginFraction(stateDB, market)
} else {
return getTradeMarginFraction(stateDB, market)
}
}

func getPositionCap(stateDB contract.StateDB, market int64, trader *common.Address) *big.Int {
marketAddress := GetMarketAddressFromMarketID(market, stateDB)
maxPositionCap := getMaxPositionCap(stateDB, marketAddress)
traderMarginFraction := getTraderMarginFraction(stateDB, marketAddress, trader)
return hu.Div1e6(hu.Mul(maxPositionCap, traderMarginFraction))
}

// Utils

func getPendingFundingPayment(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int {
Expand Down
Loading
Loading