Skip to content

Commit

Permalink
send oracle prices to evaluate availableMargin (#159)
Browse files Browse the repository at this point in the history
* send oracle prices to evaluate availableMargin

* deepcopy hState
  • Loading branch information
atvanguard authored Feb 26, 2024
1 parent 182fd14 commit 4685599
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 91 deletions.
2 changes: 1 addition & 1 deletion plugin/evm/limit_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func NewLimitOrderProcesser(ctx *snow.Context, txPool *txpool.TxPool, shutdownCh
MinAllowableMargin: configService.GetMinAllowableMargin(),
MaintenanceMargin: configService.GetMaintenanceMargin(),
TakerFee: configService.GetTakerFee(),
UpgradeVersion: configService.GetUpgradeVersion(),
UpgradeVersion: hu.V2,
}
hu.SetHubbleState(hState)
hu.SetChainIdAndVerifyingSignedOrdersContract(backend.ChainConfig().ChainID.Int64(), signedObAddy.String())
Expand Down
9 changes: 0 additions & 9 deletions plugin/evm/orderbook/config_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ type IConfigService interface {
GetSignedOrderStatus(orderHash common.Hash) int64
IsTradingAuthority(trader, signer common.Address) bool
GetSignedOrderbookContract() common.Address
GetUpgradeVersion() hu.UpgradeVersion

GetMarketAddressFromMarketID(marketId int64) common.Address
GetImpactMarginNotional(ammAddress common.Address) *big.Int
Expand Down Expand Up @@ -132,14 +131,6 @@ func (cs *ConfigService) GetSignedOrderbookContract() common.Address {
return bibliophile.GetSignedOrderBookAddress(cs.getStateAtCurrentBlock())
}

func (cs *ConfigService) GetUpgradeVersion() hu.UpgradeVersion {
jurorAddy := bibliophile.JurorAddress(cs.getStateAtCurrentBlock())
if jurorAddy == common.HexToAddress("0x03000000000000000000000000000000000000a2") {
return hu.V2
}
return hu.V1
}

func (cs *ConfigService) GetMarketAddressFromMarketID(marketId int64) common.Address {
return bibliophile.GetMarketAddressFromMarketID(marketId, cs.getStateAtCurrentBlock())
}
Expand Down
25 changes: 22 additions & 3 deletions plugin/evm/orderbook/hubbleutils/config.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
package hubbleutils

import "math/big"

var (
ChainId int64
VerifyingContract string
HState *HubbleState
hState *HubbleState
)

func SetChainIdAndVerifyingSignedOrdersContract(chainId int64, verifyingContract string) {
ChainId = chainId
VerifyingContract = verifyingContract
}

func SetHubbleState(hState *HubbleState) {
HState = hState
func SetHubbleState(_hState *HubbleState) {
hState = _hState
}

func GetHubbleState() *HubbleState {
assets := make([]Collateral, len(hState.Assets))
copy(assets, hState.Assets)

activeMarkets := make([]Market, len(hState.ActiveMarkets))
copy(activeMarkets, hState.ActiveMarkets)

return &HubbleState{
Assets: assets,
ActiveMarkets: activeMarkets,
MinAllowableMargin: new(big.Int).Set(hState.MinAllowableMargin),
MaintenanceMargin: new(big.Int).Set(hState.MaintenanceMargin),
TakerFee: new(big.Int).Set(hState.TakerFee),
UpgradeVersion: hState.UpgradeVersion,
}
}
8 changes: 8 additions & 0 deletions plugin/evm/orderbook/hubbleutils/margin_math.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,11 @@ func GetRequiredMargin(price, fillAmount, minAllowableMargin, takerFee *big.Int)
quoteAsset := Div1e18(Mul(fillAmount, price))
return Add(Div1e6(Mul(quoteAsset, minAllowableMargin)), Div1e6(Mul(quoteAsset, takerFee)))
}

func ArrayToMap(prices []*big.Int) map[Market]*big.Int {
underlyingPrices := make(map[Market]*big.Int)
for market, price := range prices {
underlyingPrices[Market(market)] = price
}
return underlyingPrices
}
50 changes: 25 additions & 25 deletions plugin/evm/orderbook/hubbleutils/margin_math_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/assert"
)

var hState = &HubbleState{
var _hState = &HubbleState{
Assets: []Collateral{
{
Price: big.NewInt(1.01 * 1e6), // 1.01
Expand Down Expand Up @@ -57,7 +57,7 @@ var userState = &UserState{
}

func TestWeightedAndSpotCollateral(t *testing.T) {
assets := hState.Assets
assets := _hState.Assets
margins := userState.Margins
expectedWeighted := Unscale(Mul(Mul(margins[0], assets[0].Price), assets[0].Weight), assets[0].Decimals+6)
expectedWeighted.Add(expectedWeighted, Unscale(Mul(Mul(margins[1], assets[1].Price), assets[1].Weight), assets[1].Decimals+6))
Expand Down Expand Up @@ -122,10 +122,10 @@ func TestGetOptimalPnlV2(t *testing.T) {
position := userState.Positions[market]
marginMode := Maintenance_Margin

notionalPosition, uPnL := getOptimalPnl(hState, position, margin, market, marginMode)
notionalPosition, uPnL := getOptimalPnl(_hState, position, margin, market, marginMode)

// mid price pnl is more than oracle price pnl
expectedNotionalPosition := Unscale(Mul(position.Size, hState.MidPrices[market]), 18)
expectedNotionalPosition := Unscale(Mul(position.Size, _hState.MidPrices[market]), 18)
expectedUPnL := Sub(expectedNotionalPosition, position.OpenNotional)
fmt.Println("Maintenace_Margin_Mode", "notionalPosition", notionalPosition, "uPnL", uPnL)

Expand All @@ -135,9 +135,9 @@ func TestGetOptimalPnlV2(t *testing.T) {
// ------ when marginMode is Min_Allowable_Margin ------

marginMode = Min_Allowable_Margin
notionalPosition, uPnL = getOptimalPnl(hState, position, margin, market, marginMode)
notionalPosition, uPnL = getOptimalPnl(_hState, position, margin, market, marginMode)

expectedNotionalPosition = Unscale(Mul(position.Size, hState.OraclePrices[market]), 18)
expectedNotionalPosition = Unscale(Mul(position.Size, _hState.OraclePrices[market]), 18)
expectedUPnL = Sub(expectedNotionalPosition, position.OpenNotional)
fmt.Println("Min_Allowable_Margin_Mode", "notionalPosition", notionalPosition, "uPnL", uPnL)

Expand All @@ -151,10 +151,10 @@ func TestGetOptimalPnlV1(t *testing.T) {
position := userState.Positions[market]
marginMode := Maintenance_Margin

notionalPosition, uPnL := getOptimalPnl(hState, position, margin, market, marginMode)
notionalPosition, uPnL := getOptimalPnl(_hState, position, margin, market, marginMode)

// mid price pnl is more than oracle price pnl
expectedNotionalPosition := Unscale(Mul(position.Size, hState.MidPrices[market]), 18)
expectedNotionalPosition := Unscale(Mul(position.Size, _hState.MidPrices[market]), 18)
expectedUPnL := Sub(expectedNotionalPosition, position.OpenNotional)
fmt.Println("Maintenace_Margin_Mode", "notionalPosition", notionalPosition, "uPnL", uPnL)

Expand All @@ -164,9 +164,9 @@ func TestGetOptimalPnlV1(t *testing.T) {
// ------ when marginMode is Min_Allowable_Margin ------

marginMode = Min_Allowable_Margin
notionalPosition, uPnL = getOptimalPnl(hState, position, margin, market, marginMode)
notionalPosition, uPnL = getOptimalPnl(_hState, position, margin, market, marginMode)

expectedNotionalPosition = Unscale(Mul(position.Size, hState.OraclePrices[market]), 18)
expectedNotionalPosition = Unscale(Mul(position.Size, _hState.OraclePrices[market]), 18)
expectedUPnL = Sub(expectedNotionalPosition, position.OpenNotional)
fmt.Println("Min_Allowable_Margin_Mode", "notionalPosition", notionalPosition, "uPnL", uPnL)

Expand All @@ -175,15 +175,15 @@ func TestGetOptimalPnlV1(t *testing.T) {
}

func TestGetTotalNotionalPositionAndUnrealizedPnlV2(t *testing.T) {
margin := GetNormalizedMargin(hState.Assets, userState.Margins)
margin := GetNormalizedMargin(_hState.Assets, userState.Margins)
marginMode := Maintenance_Margin
notionalPosition, uPnL := GetTotalNotionalPositionAndUnrealizedPnl(hState, userState, margin, marginMode)
notionalPosition, uPnL := GetTotalNotionalPositionAndUnrealizedPnl(_hState, userState, margin, marginMode)

// mid price pnl is more than oracle price pnl for long position
expectedNotionalPosition := Unscale(Mul(userState.Positions[0].Size, hState.MidPrices[0]), 18)
expectedNotionalPosition := Unscale(Mul(userState.Positions[0].Size, _hState.MidPrices[0]), 18)
expectedUPnL := Sub(expectedNotionalPosition, userState.Positions[0].OpenNotional)
// oracle price pnl is more than mid price pnl for short position
expectedNotional2 := Abs(Unscale(Mul(userState.Positions[1].Size, hState.OraclePrices[1]), 18))
expectedNotional2 := Abs(Unscale(Mul(userState.Positions[1].Size, _hState.OraclePrices[1]), 18))
expectedNotionalPosition.Add(expectedNotionalPosition, expectedNotional2)
expectedUPnL.Add(expectedUPnL, Sub(userState.Positions[1].OpenNotional, expectedNotional2))

Expand All @@ -193,12 +193,12 @@ func TestGetTotalNotionalPositionAndUnrealizedPnlV2(t *testing.T) {
// ------ when marginMode is Min_Allowable_Margin ------

marginMode = Min_Allowable_Margin
notionalPosition, uPnL = GetTotalNotionalPositionAndUnrealizedPnl(hState, userState, margin, marginMode)
notionalPosition, uPnL = GetTotalNotionalPositionAndUnrealizedPnl(_hState, userState, margin, marginMode)
fmt.Println("Min_Allowable_Margin_Mode ", "notionalPosition = ", notionalPosition, "uPnL = ", uPnL)

expectedNotionalPosition = Unscale(Mul(userState.Positions[0].Size, hState.OraclePrices[0]), 18)
expectedNotionalPosition = Unscale(Mul(userState.Positions[0].Size, _hState.OraclePrices[0]), 18)
expectedUPnL = Sub(expectedNotionalPosition, userState.Positions[0].OpenNotional)
expectedNotional2 = Abs(Unscale(Mul(userState.Positions[1].Size, hState.MidPrices[1]), 18))
expectedNotional2 = Abs(Unscale(Mul(userState.Positions[1].Size, _hState.MidPrices[1]), 18))
expectedNotionalPosition.Add(expectedNotionalPosition, expectedNotional2)
expectedUPnL.Add(expectedUPnL, Sub(userState.Positions[1].OpenNotional, expectedNotional2))

Expand All @@ -207,16 +207,16 @@ func TestGetTotalNotionalPositionAndUnrealizedPnlV2(t *testing.T) {
}

func TestGetTotalNotionalPositionAndUnrealizedPnl(t *testing.T) {
margin := GetNormalizedMargin(hState.Assets, userState.Margins)
margin := GetNormalizedMargin(_hState.Assets, userState.Margins)
marginMode := Maintenance_Margin
hState.UpgradeVersion = V2
notionalPosition, uPnL := GetTotalNotionalPositionAndUnrealizedPnl(hState, userState, margin, marginMode)
_hState.UpgradeVersion = V2
notionalPosition, uPnL := GetTotalNotionalPositionAndUnrealizedPnl(_hState, userState, margin, marginMode)

// mid price pnl is more than oracle price pnl for long position
expectedNotionalPosition := Unscale(Mul(userState.Positions[0].Size, hState.OraclePrices[0]), 18)
expectedNotionalPosition := Unscale(Mul(userState.Positions[0].Size, _hState.OraclePrices[0]), 18)
expectedUPnL := Sub(expectedNotionalPosition, userState.Positions[0].OpenNotional)
// oracle price pnl is more than mid price pnl for short position
expectedNotional2 := Abs(Unscale(Mul(userState.Positions[1].Size, hState.OraclePrices[1]), 18))
expectedNotional2 := Abs(Unscale(Mul(userState.Positions[1].Size, _hState.OraclePrices[1]), 18))
expectedNotionalPosition.Add(expectedNotionalPosition, expectedNotional2)
expectedUPnL.Add(expectedUPnL, Sub(userState.Positions[1].OpenNotional, expectedNotional2))

Expand All @@ -226,12 +226,12 @@ func TestGetTotalNotionalPositionAndUnrealizedPnl(t *testing.T) {
// ------ when marginMode is Min_Allowable_Margin ------

marginMode = Min_Allowable_Margin
notionalPosition, uPnL = GetTotalNotionalPositionAndUnrealizedPnl(hState, userState, margin, marginMode)
notionalPosition, uPnL = GetTotalNotionalPositionAndUnrealizedPnl(_hState, userState, margin, marginMode)
fmt.Println("Min_Allowable_Margin_Mode ", "notionalPosition = ", notionalPosition, "uPnL = ", uPnL)

expectedNotionalPosition = Unscale(Mul(userState.Positions[0].Size, hState.OraclePrices[0]), 18)
expectedNotionalPosition = Unscale(Mul(userState.Positions[0].Size, _hState.OraclePrices[0]), 18)
expectedUPnL = Sub(expectedNotionalPosition, userState.Positions[0].OpenNotional)
expectedNotional2 = Abs(Unscale(Mul(userState.Positions[1].Size, hState.OraclePrices[1]), 18))
expectedNotional2 = Abs(Unscale(Mul(userState.Positions[1].Size, _hState.OraclePrices[1]), 18))
expectedNotionalPosition.Add(expectedNotionalPosition, expectedNotional2)
expectedUPnL.Add(expectedUPnL, Sub(userState.Positions[1].OpenNotional, expectedNotional2))

Expand Down
27 changes: 3 additions & 24 deletions plugin/evm/orderbook/matching_pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,9 @@ func (pipeline *MatchingPipeline) Run(blockNumber *big.Int) bool {
}
}

// fetch the underlying price and run the matching engine
hState := hu.HState
hState.OraclePrices = pipeline.GetUnderlyingPrices()
hState.MidPrices = pipeline.GetMidPrices()
// fetch various hubble market params and run the matching engine
hState := hu.GetHubbleState()
hState.OraclePrices = hu.ArrayToMap(pipeline.configService.GetUnderlyingPrices())

// build trader map
liquidablePositions, ordersToCancel, marginMap := pipeline.db.GetNaughtyTraders(hState)
Expand Down Expand Up @@ -120,26 +119,6 @@ func (pipeline *MatchingPipeline) GetActiveMarkets() []Market {
return markets
}

func (pipeline *MatchingPipeline) GetUnderlyingPrices() map[Market]*big.Int {
prices := pipeline.configService.GetUnderlyingPrices()
// log.Info("GetUnderlyingPrices", "prices", prices)
underlyingPrices := make(map[Market]*big.Int)
for market, price := range prices {
underlyingPrices[Market(market)] = price
}
return underlyingPrices
}

func (pipeline *MatchingPipeline) GetMidPrices() map[Market]*big.Int {
prices := pipeline.configService.GetMidPrices()
// log.Info("GetMidPrices", "prices", prices)
midPrices := make(map[Market]*big.Int)
for market, price := range prices {
midPrices[Market(market)] = price
}
return midPrices
}

func (pipeline *MatchingPipeline) GetCollaterals() []hu.Collateral {
return pipeline.configService.GetCollaterals()
}
Expand Down
55 changes: 31 additions & 24 deletions plugin/evm/orderbook/memory_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ type LimitOrderDatabase interface {
GetOrderValidationFields(orderId common.Hash, order *hu.SignedOrder) OrderValidationFields
SampleImpactPrice() (impactBids, impactAsks, midPrices []*big.Int)
RemoveExpiredSignedOrders()
GetMarginAvailableForMakerbook(trader common.Address, prices map[int]*big.Int) *big.Int
}

type Snapshot struct {
Expand Down Expand Up @@ -1290,11 +1291,10 @@ func getOrderIdx(orders []*Order, orderId common.Hash) int {
}

type OrderValidationFields struct {
Exists bool
PosSize *big.Int
AvailableMargin *big.Int
AsksHead *big.Int
BidsHead *big.Int
Exists bool
PosSize *big.Int
AsksHead *big.Int
BidsHead *big.Int
}

func (db *InMemoryDatabase) GetOrderValidationFields(orderId common.Hash, order *hu.SignedOrder) OrderValidationFields {
Expand Down Expand Up @@ -1323,28 +1323,35 @@ func (db *InMemoryDatabase) GetOrderValidationFields(orderId common.Hash, order
bidsHead = db.LongOrders[marketId][0].Price
}

availableMargin := big.NewInt(0)
return OrderValidationFields{
Exists: false,
PosSize: posSize,
AsksHead: asksHead,
BidsHead: bidsHead,
}
}

func (db *InMemoryDatabase) GetMarginAvailableForMakerbook(trader common.Address, prices map[int]*big.Int) *big.Int {
db.mu.RLock()
defer db.mu.RUnlock()

_trader := db.TraderMap[trader]
if _trader != nil {
hState := hu.HState
userState := &hu.UserState{
Positions: translatePositions(_trader.Positions),
Margins: getMargins(_trader, len(hState.Assets)),
PendingFunding: getTotalFunding(_trader, hState.ActiveMarkets),
ReservedMargin: new(big.Int).Set(_trader.Margin.Reserved),
}
if _trader.Margin.VirtualReserved == nil {
_trader.Margin.VirtualReserved = big.NewInt(0)
}
availableMargin = hu.Sub(hu.GetAvailableMargin(hState, userState), _trader.Margin.VirtualReserved)
if _trader == nil {
return big.NewInt(0)
}
return OrderValidationFields{
Exists: false,
PosSize: posSize,
AvailableMargin: availableMargin,
AsksHead: asksHead,
BidsHead: bidsHead,

hState := hu.GetHubbleState()
hState.OraclePrices = prices
userState := &hu.UserState{
Positions: translatePositions(_trader.Positions),
Margins: getMargins(_trader, len(hState.Assets)),
PendingFunding: getTotalFunding(_trader, hState.ActiveMarkets),
ReservedMargin: new(big.Int).Set(_trader.Margin.Reserved),
}
if _trader.Margin.VirtualReserved == nil {
_trader.Margin.VirtualReserved = big.NewInt(0)
}
return hu.Sub(hu.GetAvailableMargin(hState, userState), _trader.Margin.VirtualReserved)
}

func (db *InMemoryDatabase) SampleImpactPrice() (impactBids, impactAsks, midPrices []*big.Int) {
Expand Down
8 changes: 4 additions & 4 deletions plugin/evm/orderbook/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ func (db *MockLimitOrderDatabase) SampleImpactPrice() (impactBids, impactAsks, m

func (db *MockLimitOrderDatabase) RemoveExpiredSignedOrders() {}

func (db *MockLimitOrderDatabase) GetMarginAvailableForMakerbook(trader common.Address, prices map[int]*big.Int) *big.Int {
return big.NewInt(0)
}

type MockLimitOrderTxProcessor struct {
mock.Mock
}
Expand Down Expand Up @@ -328,10 +332,6 @@ func (cs *MockConfigService) GetSignedOrderbookContract() common.Address {
return common.Address{}
}

func (cs *MockConfigService) GetUpgradeVersion() hu.UpgradeVersion {
return hu.V2
}

func (cs *MockConfigService) GetMarketAddressFromMarketID(marketId int64) common.Address {
return common.Address{}
}
Expand Down
3 changes: 2 additions & 1 deletion plugin/evm/orderbook/trading_apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@ func (api *TradingAPI) PlaceOrder(order *hu.SignedOrder) (common.Hash, error) {
minAllowableMargin := api.configService.GetMinAllowableMargin()
// even tho order might be matched at a different price, we reserve margin at the price the order was placed at to keep it simple
requiredMargin = hu.GetRequiredMargin(order.Price, hu.Abs(order.BaseAssetQuantity), minAllowableMargin, big.NewInt(0))
if fields.AvailableMargin.Cmp(requiredMargin) == -1 {
availableMargin := api.db.GetMarginAvailableForMakerbook(trader, hu.ArrayToMap(api.configService.GetUnderlyingPrices()))
if availableMargin.Cmp(requiredMargin) == -1 {
return orderId, hu.ErrInsufficientMargin
}
} else {
Expand Down

0 comments on commit 4685599

Please sign in to comment.