From bd2a39a48e6add5420f2be87e93ac12403b2fda7 Mon Sep 17 00:00:00 2001 From: atvanguard <3612498+atvanguard@users.noreply.github.com> Date: Fri, 29 Sep 2023 13:16:22 +0100 Subject: [PATCH] Support multi-collateral in getNormalizedMargin functions + refactor (#118) * getNormalisedMargin * refactor GetAvailableMargin * fix tests * update slot * rename vars * cleanup comments * baseSlot for collateral idx*3 * userState/hubbleState in margin_math * hu.Position * ticks.getQuote * misc changes --- plugin/evm/orderbook/config_service.go | 6 + .../contract_events_processor_test.go | 15 +- .../orderbook/hubbleutils/data_structures.go | 39 + .../evm/orderbook/hubbleutils/hubble_math.go | 8 + .../evm/orderbook/hubbleutils/margin_math.go | 113 + plugin/evm/orderbook/liquidations.go | 99 +- plugin/evm/orderbook/liquidations_test.go | 101 +- plugin/evm/orderbook/matching_pipeline.go | 8 +- plugin/evm/orderbook/memory_database.go | 58 +- plugin/evm/orderbook/memory_database_test.go | 17 +- plugin/evm/orderbook/mocks.go | 12 +- plugin/evm/orderbook/service.go | 12 +- plugin/evm/orderbook/trading_apis.go | 3 +- precompile/contracts/bibliophile/amm.go | 120 +- precompile/contracts/bibliophile/api.go | 6 +- .../contracts/bibliophile/clearing_house.go | 47 +- .../contracts/bibliophile/client_mock.go | 15 + .../contracts/bibliophile/margin_account.go | 71 +- precompile/contracts/bibliophile/oracle.go | 61 + precompile/contracts/bibliophile/redstone.go | 25 - precompile/contracts/ticks/logic.go | 67 +- precompile/contracts/ticks/logic_test.go | 167 +- tests/orderbook/abi/AMM.json | 2225 ++++++++--------- tests/orderbook/abi/MarginAccount.json | 2208 ++++++++-------- tests/orderbook/abi/Oracle.json | 284 +-- .../bibliophile/variablesReadFromSlotTests.js | 37 +- tests/orderbook/juror/JurorTests.js | 12 +- tests/orderbook/tests/test.js | 4 +- tests/orderbook/utils.js | 2 +- 29 files changed, 3086 insertions(+), 2756 deletions(-) create mode 100644 plugin/evm/orderbook/hubbleutils/data_structures.go create mode 100644 plugin/evm/orderbook/hubbleutils/margin_math.go create mode 100644 precompile/contracts/bibliophile/oracle.go delete mode 100644 precompile/contracts/bibliophile/redstone.go diff --git a/plugin/evm/orderbook/config_service.go b/plugin/evm/orderbook/config_service.go index cc0477ca8b..f5d0660c06 100644 --- a/plugin/evm/orderbook/config_service.go +++ b/plugin/evm/orderbook/config_service.go @@ -5,6 +5,7 @@ import ( "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/state" + hu "github.com/ava-labs/subnet-evm/plugin/evm/orderbook/hubbleutils" "github.com/ava-labs/subnet-evm/precompile/contracts/bibliophile" "github.com/ethereum/go-ethereum/common" ) @@ -17,6 +18,7 @@ type IConfigService interface { getMinSizeRequirement(market Market) *big.Int GetActiveMarketsCount() int64 GetUnderlyingPrices() []*big.Int + GetCollaterals() []hu.Collateral GetLastPremiumFraction(market Market, trader *common.Address) *big.Int GetCumulativePremiumFraction(market Market) *big.Int GetAcceptableBounds(market Market) (*big.Int, *big.Int) @@ -82,6 +84,10 @@ func (cs *ConfigService) GetUnderlyingPrices() []*big.Int { return bibliophile.GetUnderlyingPrices(cs.getStateAtCurrentBlock()) } +func (cs *ConfigService) GetCollaterals() []hu.Collateral { + return bibliophile.GetCollaterals(cs.getStateAtCurrentBlock()) +} + func (cs *ConfigService) GetLastPremiumFraction(market Market, trader *common.Address) *big.Int { markets := bibliophile.GetMarkets(cs.getStateAtCurrentBlock()) return bibliophile.GetLastPremiumFraction(cs.getStateAtCurrentBlock(), markets[market], trader) diff --git a/plugin/evm/orderbook/contract_events_processor_test.go b/plugin/evm/orderbook/contract_events_processor_test.go index ec8983d679..7b751bfcb4 100644 --- a/plugin/evm/orderbook/contract_events_processor_test.go +++ b/plugin/evm/orderbook/contract_events_processor_test.go @@ -111,8 +111,7 @@ func TestOrderBookMarginAccountClearingHouseEventInLog(t *testing.T) { unrealisedFunding := hu.Mul1e6(big.NewInt(1)) market := Market(0) position := &Position{ - OpenNotional: openNotional, - Size: size, + Position: hu.Position{OpenNotional: openNotional, Size: size}, UnrealisedFunding: unrealisedFunding, LastPremiumFraction: lastPremiumFraction, LiquidationThreshold: liquidationThreshold, @@ -455,8 +454,7 @@ func TestHandleClearingHouseEvent(t *testing.T) { db := getDatabase() cep := newcep(t, db) position := &Position{ - OpenNotional: openNotional, - Size: size, + Position: hu.Position{OpenNotional: openNotional, Size: size}, UnrealisedFunding: unrealisedFunding, LastPremiumFraction: lastPremiumFraction, LiquidationThreshold: liquidationThreshold, @@ -493,8 +491,7 @@ func TestHandleClearingHouseEvent(t *testing.T) { db := getDatabase() cep := newcep(t, db) position := &Position{ - OpenNotional: openNotional, - Size: size, + Position: hu.Position{OpenNotional: openNotional, Size: size}, UnrealisedFunding: unrealisedFunding, LastPremiumFraction: lastPremiumFraction, LiquidationThreshold: liquidationThreshold, @@ -529,8 +526,7 @@ func TestHandleClearingHouseEvent(t *testing.T) { db := getDatabase() cep := newcep(t, db) position := &Position{ - OpenNotional: openNotional, - Size: size, + Position: hu.Position{OpenNotional: openNotional, Size: size}, UnrealisedFunding: unrealisedFunding, LastPremiumFraction: lastPremiumFraction, LiquidationThreshold: liquidationThreshold, @@ -577,8 +573,7 @@ func TestHandleClearingHouseEvent(t *testing.T) { db := getDatabase() cep := newcep(t, db) position := &Position{ - OpenNotional: openNotional, - Size: size, + Position: hu.Position{OpenNotional: openNotional, Size: size}, UnrealisedFunding: unrealisedFunding, LastPremiumFraction: lastPremiumFraction, LiquidationThreshold: liquidationThreshold, diff --git a/plugin/evm/orderbook/hubbleutils/data_structures.go b/plugin/evm/orderbook/hubbleutils/data_structures.go new file mode 100644 index 0000000000..fbae81a41e --- /dev/null +++ b/plugin/evm/orderbook/hubbleutils/data_structures.go @@ -0,0 +1,39 @@ +package hubbleutils + +import ( + // "encoding/json" + "math/big" +) + +type MarginMode = uint8 + +const ( + Maintenance_Margin MarginMode = iota + Min_Allowable_Margin +) + +type Collateral struct { + Price *big.Int // scaled by 1e6 + Weight *big.Int // scaled by 1e6 + Decimals uint8 +} + +type Market = int + +type Position struct { + OpenNotional *big.Int `json:"open_notional"` + Size *big.Int `json:"size"` + // UnrealisedFunding *big.Int `json:"unrealised_funding"` + // LastPremiumFraction *big.Int `json:"last_premium_fraction"` + // LiquidationThreshold *big.Int `json:"liquidation_threshold"` +} + +type Trader struct { + Positions map[Market]*Position `json:"positions"` // position for every market + Margin Margin `json:"margin"` // available margin/balance for every market +} + +type Margin struct { + Reserved *big.Int `json:"reserved"` + Deposited map[Collateral]*big.Int `json:"deposited"` +} diff --git a/plugin/evm/orderbook/hubbleutils/hubble_math.go b/plugin/evm/orderbook/hubbleutils/hubble_math.go index 3960ed77f0..5bf10d7d7f 100644 --- a/plugin/evm/orderbook/hubbleutils/hubble_math.go +++ b/plugin/evm/orderbook/hubbleutils/hubble_math.go @@ -57,3 +57,11 @@ func RoundOff(a, b *big.Int) *big.Int { func Mod(a, b *big.Int) *big.Int { return new(big.Int).Mod(a, b) } + +func Scale(a *big.Int, decimals uint8) *big.Int { + return Mul(a, new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil)) +} + +func Unscale(a *big.Int, decimals uint8) *big.Int { + return Div(a, new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil)) +} diff --git a/plugin/evm/orderbook/hubbleutils/margin_math.go b/plugin/evm/orderbook/hubbleutils/margin_math.go new file mode 100644 index 0000000000..3bd8bdec53 --- /dev/null +++ b/plugin/evm/orderbook/hubbleutils/margin_math.go @@ -0,0 +1,113 @@ +package hubbleutils + +import ( + "math/big" +) + +type HubbleState struct { + Assets []Collateral + OraclePrices map[Market]*big.Int + LastPrices map[Market]*big.Int + ActiveMarkets []Market + MinAllowableMargin *big.Int +} + +type UserState struct { + Positions map[Market]*Position + Margins []*big.Int + PendingFunding *big.Int + ReservedMargin *big.Int +} + +func GetAvailableMargin(hState *HubbleState, userState *UserState) *big.Int { + notionalPosition, margin := GetNotionalPositionAndMargin(hState, userState, Min_Allowable_Margin) + return GetAvailableMargin_(notionalPosition, margin, userState.ReservedMargin, hState.MinAllowableMargin) +} + +func GetAvailableMargin_(notionalPosition, margin, reservedMargin, minAllowableMargin *big.Int) *big.Int { + utilisedMargin := Div1e6(Mul(notionalPosition, minAllowableMargin)) + return Sub(Sub(margin, utilisedMargin), reservedMargin) +} + +func GetNotionalPositionAndMargin(hState *HubbleState, userState *UserState, marginMode MarginMode) (*big.Int, *big.Int) { + margin := Sub(GetNormalizedMargin(hState.Assets, userState.Margins), userState.PendingFunding) + notionalPosition, unrealizedPnl := GetTotalNotionalPositionAndUnrealizedPnl(hState, userState, margin, marginMode) + return notionalPosition, Add(margin, unrealizedPnl) +} + +func GetTotalNotionalPositionAndUnrealizedPnl(hState *HubbleState, userState *UserState, margin *big.Int, marginMode MarginMode) (*big.Int, *big.Int) { + notionalPosition := big.NewInt(0) + unrealizedPnl := big.NewInt(0) + for _, market := range hState.ActiveMarkets { + _notionalPosition, _unrealizedPnl := GetOptimalPnl(hState, userState.Positions[market], margin, market, marginMode) + notionalPosition.Add(notionalPosition, _notionalPosition) + unrealizedPnl.Add(unrealizedPnl, _unrealizedPnl) + } + return notionalPosition, unrealizedPnl +} + +func GetOptimalPnl(hState *HubbleState, position *Position, margin *big.Int, market Market, marginMode MarginMode) (notionalPosition *big.Int, uPnL *big.Int) { + if position == nil || position.Size.Sign() == 0 { + return big.NewInt(0), big.NewInt(0) + } + + // based on last price + notionalPosition, unrealizedPnl, lastPriceBasedMF := GetPositionMetadata( + hState.LastPrices[market], + position.OpenNotional, + position.Size, + margin, + ) + + // based on oracle price + oracleBasedNotional, oracleBasedUnrealizedPnl, oracleBasedMF := GetPositionMetadata( + hState.OraclePrices[market], + position.OpenNotional, + position.Size, + margin, + ) + + if (marginMode == Maintenance_Margin && oracleBasedMF.Cmp(lastPriceBasedMF) == 1) || // for liquidations + (marginMode == Min_Allowable_Margin && oracleBasedMF.Cmp(lastPriceBasedMF) == -1) { // for increasing leverage + return oracleBasedNotional, oracleBasedUnrealizedPnl + } + return notionalPosition, unrealizedPnl +} + +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) + if notionalPosition.Sign() == 0 { + return big.NewInt(0), big.NewInt(0), big.NewInt(0) + } + if size.Cmp(big.NewInt(0)) > 0 { + uPnL = Sub(notionalPosition, openNotional) + } else { + uPnL = Sub(openNotional, notionalPosition) + } + mf := Div(Mul1e6(Add(margin, uPnL)), notionalPosition) + return notionalPosition, uPnL, mf +} + +func GetNotionalPosition(price *big.Int, size *big.Int) *big.Int { + return big.NewInt(0).Abs(Div1e18(Mul(size, price))) +} + +func GetNormalizedMargin(assets []Collateral, margins []*big.Int) *big.Int { + weighted, _ := WeightedAndSpotCollateral(assets, margins) + return weighted +} + +func WeightedAndSpotCollateral(assets []Collateral, margins []*big.Int) (weighted, spot *big.Int) { + weighted = big.NewInt(0) + spot = big.NewInt(0) + for i, asset := range assets { + if margins[i] == nil || margins[i].Sign() == 0 { + continue + } + numerator := Mul(margins[i], asset.Price) // margin[i] is scaled by asset.Decimal + spot.Add(spot, Unscale(numerator, asset.Decimals)) + weighted.Add(weighted, Unscale(Mul(numerator, asset.Weight), asset.Decimals+6)) + } + return weighted, spot +} diff --git a/plugin/evm/orderbook/liquidations.go b/plugin/evm/orderbook/liquidations.go index c59347e597..0dd97b903f 100644 --- a/plugin/evm/orderbook/liquidations.go +++ b/plugin/evm/orderbook/liquidations.go @@ -22,9 +22,9 @@ func (liq LiquidablePosition) GetUnfilledSize() *big.Int { return big.NewInt(0).Sub(liq.Size, liq.FilledSize) } -func calcMarginFraction(trader *Trader, pendingFunding *big.Int, oraclePrices map[Market]*big.Int, lastPrices map[Market]*big.Int, markets []Market) *big.Int { - margin := new(big.Int).Sub(getNormalisedMargin(trader), pendingFunding) - notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(trader, margin, Maintenance_Margin, oraclePrices, lastPrices, markets) +func calcMarginFraction(trader *Trader, pendingFunding *big.Int, assets []hu.Collateral, oraclePrices map[Market]*big.Int, lastPrices map[Market]*big.Int, markets []Market) *big.Int { + margin := new(big.Int).Sub(getNormalisedMargin(trader, assets), pendingFunding) + notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(trader, margin, hu.Maintenance_Margin, oraclePrices, lastPrices, markets) if notionalPosition.Sign() == 0 { return big.NewInt(math.MaxInt64) } @@ -39,9 +39,16 @@ func sortLiquidableSliceByMarginFraction(positions []LiquidablePosition) []Liqui return positions } -func getNormalisedMargin(trader *Trader) *big.Int { - return trader.Margin.Deposited[HUSD] - // @todo: Write for multi-collateral +func getNormalisedMargin(trader *Trader, assets []hu.Collateral) *big.Int { + return hu.GetNormalizedMargin(assets, getMargins(trader, len(assets))) +} + +func getMargins(trader *Trader, numAssets int) []*big.Int { + margin := make([]*big.Int, numAssets) + for i := 0; i < numAssets; i++ { + margin[i] = trader.Margin.Deposited[Collateral(i)] + } + return margin } func getTotalFunding(trader *Trader, markets []Market) *big.Int { @@ -54,75 +61,35 @@ func getTotalFunding(trader *Trader, markets []Market) *big.Int { return totalPendingFunding } -func getNotionalPosition(price *big.Int, size *big.Int) *big.Int { - return big.NewInt(0).Abs(hu.Div1e18(big.NewInt(0).Mul(size, price))) -} - -type MarginMode uint8 - -const ( - Maintenance_Margin MarginMode = iota - Min_Allowable_Margin -) +type MarginMode = hu.MarginMode func getTotalNotionalPositionAndUnrealizedPnl(trader *Trader, margin *big.Int, marginMode MarginMode, oraclePrices map[Market]*big.Int, lastPrices map[Market]*big.Int, markets []Market) (*big.Int, *big.Int) { - notionalPosition := big.NewInt(0) - unrealizedPnl := big.NewInt(0) - for _, market := range markets { - _notionalPosition, _unrealizedPnl := getOptimalPnl(market, oraclePrices[market], lastPrices[market], trader, margin, marginMode) - notionalPosition.Add(notionalPosition, _notionalPosition) - unrealizedPnl.Add(unrealizedPnl, _unrealizedPnl) - } - return notionalPosition, unrealizedPnl -} - -func getOptimalPnl(market Market, oraclePrice *big.Int, lastPrice *big.Int, trader *Trader, margin *big.Int, marginMode MarginMode) (notionalPosition *big.Int, uPnL *big.Int) { - position := trader.Positions[market] - if position == nil || position.Size.Sign() == 0 { - return big.NewInt(0), big.NewInt(0) - } - - // based on last price - notionalPosition, unrealizedPnl, lastPriceBasedMF := getPositionMetadata( - lastPrice, - position.OpenNotional, - position.Size, - margin, - ) - // log.Info("in getOptimalPnl", "notionalPosition", notionalPosition, "unrealizedPnl", unrealizedPnl, "lastPriceBasedMF", lastPriceBasedMF) - - // based on oracle price - oracleBasedNotional, oracleBasedUnrealizedPnl, oracleBasedMF := getPositionMetadata( - oraclePrice, - position.OpenNotional, - position.Size, + return hu.GetTotalNotionalPositionAndUnrealizedPnl( + &hu.HubbleState{ + OraclePrices: oraclePrices, + LastPrices: lastPrices, + ActiveMarkets: markets, + }, + &hu.UserState{ + Positions: translatePositions(trader.Positions), + }, margin, + marginMode, ) - // log.Info("in getOptimalPnl", "oracleBasedNotional", oracleBasedNotional, "oracleBasedUnrealizedPnl", oracleBasedUnrealizedPnl, "oracleBasedMF", oracleBasedMF) - - if (marginMode == Maintenance_Margin && oracleBasedMF.Cmp(lastPriceBasedMF) == 1) || // for liquidations - (marginMode == Min_Allowable_Margin && oracleBasedMF.Cmp(lastPriceBasedMF) == -1) { // for increasing leverage - return oracleBasedNotional, oracleBasedUnrealizedPnl - } - return notionalPosition, unrealizedPnl } func getPositionMetadata(price *big.Int, openNotional *big.Int, size *big.Int, margin *big.Int) (notionalPosition *big.Int, unrealisedPnl *big.Int, marginFraction *big.Int) { - // log.Info("in getPositionMetadata", "price", price, "openNotional", openNotional, "size", size, "margin", margin) - notionalPosition = getNotionalPosition(price, size) - uPnL := new(big.Int) - if notionalPosition.Cmp(big.NewInt(0)) == 0 { - return big.NewInt(0), big.NewInt(0), big.NewInt(0) - } - if size.Cmp(big.NewInt(0)) > 0 { - uPnL = new(big.Int).Sub(notionalPosition, openNotional) - } else { - uPnL = new(big.Int).Sub(openNotional, notionalPosition) - } - mf := new(big.Int).Div(hu.Mul1e6(new(big.Int).Add(margin, uPnL)), notionalPosition) - return notionalPosition, uPnL, mf + return hu.GetPositionMetadata(price, openNotional, size, margin) } func prettifyScaledBigInt(number *big.Int, precision int8) string { return new(big.Float).Quo(new(big.Float).SetInt(number), big.NewFloat(math.Pow10(int(precision)))).String() } + +func translatePositions(positions map[int]*Position) map[int]*hu.Position { + huPositions := make(map[int]*hu.Position) + for key, value := range positions { + huPositions[key] = &value.Position + } + return huPositions +} diff --git a/plugin/evm/orderbook/liquidations_test.go b/plugin/evm/orderbook/liquidations_test.go index aba124bb96..337f3d5efe 100644 --- a/plugin/evm/orderbook/liquidations_test.go +++ b/plugin/evm/orderbook/liquidations_test.go @@ -12,10 +12,11 @@ import ( func TestGetLiquidableTraders(t *testing.T) { var market Market = Market(0) collateral := HUSD + assets := []hu.Collateral{{Price: big.NewInt(1e6), Weight: big.NewInt(1e6), Decimals: 6}} t.Run("When no trader exist", func(t *testing.T) { db := getDatabase() oraclePrices := map[Market]*big.Int{market: hu.Mul1e6(big.NewInt(110))} - liquidablePositions, _ := db.GetNaughtyTraders(oraclePrices, []Market{market}) + liquidablePositions, _ := db.GetNaughtyTraders(oraclePrices, assets, []Market{market}) assert.Equal(t, 0, len(liquidablePositions)) }) @@ -34,7 +35,7 @@ func TestGetLiquidableTraders(t *testing.T) { } db.LastPrice = map[Market]*big.Int{market: hu.Mul1e6(big.NewInt(100))} oraclePrices := map[Market]*big.Int{market: hu.Mul1e6(big.NewInt(110))} - liquidablePositions, _ := db.GetNaughtyTraders(oraclePrices, []Market{market}) + liquidablePositions, _ := db.GetNaughtyTraders(oraclePrices, assets, []Market{market}) assert.Equal(t, 0, len(liquidablePositions)) }) @@ -66,31 +67,31 @@ func TestGetLiquidableTraders(t *testing.T) { // assertions begin // for long trader _trader := &longTrader - assert.Equal(t, marginLong, getNormalisedMargin(_trader)) + assert.Equal(t, marginLong, getNormalisedMargin(_trader, assets)) assert.Equal(t, pendingFundingLong, getTotalFunding(_trader, []Market{market})) // open notional = 90 * 10 = 900 // last price: notional = 50 * 10 = 500, pnl = 500-900 = -400, mf = (500-42-400)/500 = 0.116 // oracle price: notional = 49 * 10 = 490, pnl = 490-900 = -410, mf = (500-42-410)/490 = 0.097 - // for Min_Allowable_Margin we select the min of 2 hence orale_mf - notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginLong, pendingFundingLong), Min_Allowable_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) + // for hu.Min_Allowable_Margin we select the min of 2 hence orale_mf + notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginLong, pendingFundingLong), hu.Min_Allowable_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) assert.Equal(t, hu.Mul1e6(big.NewInt(490)), notionalPosition) assert.Equal(t, hu.Mul1e6(big.NewInt(-410)), unrealizePnL) - availableMargin := getAvailableMargin(_trader, pendingFundingLong, oraclePrices, db.GetLastPrices(), db.configService.getMinAllowableMargin(), []Market{market}) + availableMargin := getAvailableMargin(_trader, pendingFundingLong, assets, oraclePrices, db.GetLastPrices(), db.configService.getMinAllowableMargin(), []Market{market}) // availableMargin = 500 - 42 (pendingFundingLong) - 410 (uPnL) - 490/5 = -50 assert.Equal(t, hu.Mul1e6(big.NewInt(-50)), availableMargin) - // for Maintenance_Margin we select the max of 2 hence, last_mf - notionalPosition, unrealizePnL = getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginLong, pendingFundingLong), Maintenance_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) + // for hu.Maintenance_Margin we select the max of 2 hence, last_mf + notionalPosition, unrealizePnL = getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginLong, pendingFundingLong), hu.Maintenance_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) assert.Equal(t, hu.Mul1e6(big.NewInt(500)), notionalPosition) assert.Equal(t, hu.Mul1e6(big.NewInt(-400)), unrealizePnL) - marginFraction := calcMarginFraction(_trader, pendingFundingLong, oraclePrices, db.GetLastPrices(), []Market{market}) + marginFraction := calcMarginFraction(_trader, pendingFundingLong, assets, oraclePrices, db.GetLastPrices(), []Market{market}) assert.Equal(t, new(big.Int).Div(hu.Mul1e6(new(big.Int).Add(new(big.Int).Sub(marginLong, pendingFundingLong), unrealizePnL)), notionalPosition), marginFraction) - liquidablePositions, _ := db.GetNaughtyTraders(oraclePrices, []Market{market}) + liquidablePositions, _ := db.GetNaughtyTraders(oraclePrices, assets, []Market{market}) assert.Equal(t, 0, len(liquidablePositions)) }) @@ -115,31 +116,31 @@ func TestGetLiquidableTraders(t *testing.T) { // assertions begin // for long trader _trader := &longTrader - assert.Equal(t, marginLong, getNormalisedMargin(_trader)) + assert.Equal(t, marginLong, getNormalisedMargin(_trader, assets)) assert.Equal(t, pendingFundingLong, getTotalFunding(_trader, []Market{market})) // open notional = 90 * 10 = 900 // last price: notional = 49 * 10 = 490, pnl = 490-900 = -410, mf = (500-42-410)/490 = 0.097 // oracle price: notional = 50 * 10 = 500, pnl = 500-900 = -400, mf = (500-42-400)/500 = 0.116 - // for Min_Allowable_Margin we select the min of 2 hence last_mf - notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginLong, pendingFundingLong), Min_Allowable_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) + // for hu.Min_Allowable_Margin we select the min of 2 hence last_mf + notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginLong, pendingFundingLong), hu.Min_Allowable_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) assert.Equal(t, hu.Mul1e6(big.NewInt(490)), notionalPosition) assert.Equal(t, hu.Mul1e6(big.NewInt(-410)), unrealizePnL) - availableMargin := getAvailableMargin(_trader, pendingFundingLong, oraclePrices, db.GetLastPrices(), db.configService.getMinAllowableMargin(), []Market{market}) + availableMargin := getAvailableMargin(_trader, pendingFundingLong, assets, oraclePrices, db.GetLastPrices(), db.configService.getMinAllowableMargin(), []Market{market}) // availableMargin = 500 - 42 (pendingFundingLong) - 410 (uPnL) - 490/5 = -50 assert.Equal(t, hu.Mul1e6(big.NewInt(-50)), availableMargin) - // for Maintenance_Margin we select the max of 2 hence, oracle_mf - notionalPosition, unrealizePnL = getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginLong, pendingFundingLong), Maintenance_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) + // for hu.Maintenance_Margin we select the max of 2 hence, oracle_mf + notionalPosition, unrealizePnL = getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginLong, pendingFundingLong), hu.Maintenance_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) assert.Equal(t, hu.Mul1e6(big.NewInt(500)), notionalPosition) assert.Equal(t, hu.Mul1e6(big.NewInt(-400)), unrealizePnL) - marginFraction := calcMarginFraction(_trader, pendingFundingLong, oraclePrices, db.GetLastPrices(), []Market{market}) + marginFraction := calcMarginFraction(_trader, pendingFundingLong, assets, oraclePrices, db.GetLastPrices(), []Market{market}) assert.Equal(t, new(big.Int).Div(hu.Mul1e6(new(big.Int).Add(new(big.Int).Sub(marginLong, pendingFundingLong), unrealizePnL)), notionalPosition), marginFraction) - liquidablePositions, _ := db.GetNaughtyTraders(oraclePrices, []Market{market}) + liquidablePositions, _ := db.GetNaughtyTraders(oraclePrices, assets, []Market{market}) assert.Equal(t, 0, len(liquidablePositions)) }) }) @@ -171,31 +172,31 @@ func TestGetLiquidableTraders(t *testing.T) { // assertions begin _trader := &shortTrader - assert.Equal(t, marginShort, getNormalisedMargin(_trader)) + assert.Equal(t, marginShort, getNormalisedMargin(_trader, assets)) assert.Equal(t, pendingFundingShort, getTotalFunding(_trader, []Market{market})) // open notional = 105 * 20 = 2100 // last price: notional = 142 * 20 = 2840, pnl = 2100-2840 = -740, mf = (1000+37-740)/2840 = 0.104 // oracle price based notional = 143 * 20 = 2860, pnl = 2100-2860 = -760, mf = (1000+37-760)/2860 = 0.096 - // for Min_Allowable_Margin we select the min of 2 hence, oracle_mf - notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginShort, pendingFundingShort), Min_Allowable_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) + // for hu.Min_Allowable_Margin we select the min of 2 hence, oracle_mf + notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginShort, pendingFundingShort), hu.Min_Allowable_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) assert.Equal(t, hu.Mul1e6(big.NewInt(2860)), notionalPosition) assert.Equal(t, hu.Mul1e6(big.NewInt(-760)), unrealizePnL) - availableMargin := getAvailableMargin(_trader, pendingFundingShort, oraclePrices, db.GetLastPrices(), db.configService.getMinAllowableMargin(), []Market{market}) + availableMargin := getAvailableMargin(_trader, pendingFundingShort, assets, oraclePrices, db.GetLastPrices(), db.configService.getMinAllowableMargin(), []Market{market}) // availableMargin = 1000 + 37 (pendingFundingShort) -760 (uPnL) - 2860/5 = -295 assert.Equal(t, hu.Mul1e6(big.NewInt(-295)), availableMargin) - // for Maintenance_Margin we select the max of 2 hence, last_mf - notionalPosition, unrealizePnL = getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginShort, pendingFundingShort), Maintenance_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) + // for hu.Maintenance_Margin we select the max of 2 hence, last_mf + notionalPosition, unrealizePnL = getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginShort, pendingFundingShort), hu.Maintenance_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) assert.Equal(t, hu.Mul1e6(big.NewInt(2840)), notionalPosition) assert.Equal(t, hu.Mul1e6(big.NewInt(-740)), unrealizePnL) - marginFraction := calcMarginFraction(_trader, pendingFundingShort, oraclePrices, db.GetLastPrices(), []Market{market}) + marginFraction := calcMarginFraction(_trader, pendingFundingShort, assets, oraclePrices, db.GetLastPrices(), []Market{market}) assert.Equal(t, new(big.Int).Div(hu.Mul1e6(new(big.Int).Add(new(big.Int).Sub(marginShort, pendingFundingShort), unrealizePnL)), notionalPosition), marginFraction) - liquidablePositions, _ := db.GetNaughtyTraders(oraclePrices, []Market{market}) + liquidablePositions, _ := db.GetNaughtyTraders(oraclePrices, assets, []Market{market}) assert.Equal(t, 0, len(liquidablePositions)) }) @@ -219,40 +220,41 @@ func TestGetLiquidableTraders(t *testing.T) { // assertions begin _trader := &shortTrader - assert.Equal(t, marginShort, getNormalisedMargin(_trader)) + assert.Equal(t, marginShort, getNormalisedMargin(_trader, assets)) assert.Equal(t, pendingFundingShort, getTotalFunding(_trader, []Market{market})) // open notional = 105 * 20 = 2100 // last price: = 143 * 20 = 2860, pnl = 2100-2860 = -760, mf = (1000+37-760)/2860 = 0.096 // oracle price: notional = 142 * 20 = 2840, pnl = 2100-2840 = -740, mf = (1000+37-740)/2840 = 0.104 - // for Min_Allowable_Margin we select the min of 2 hence, last_mf - notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginShort, pendingFundingShort), Min_Allowable_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) + // for hu.Min_Allowable_Margin we select the min of 2 hence, last_mf + notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginShort, pendingFundingShort), hu.Min_Allowable_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) assert.Equal(t, hu.Mul1e6(big.NewInt(2860)), notionalPosition) assert.Equal(t, hu.Mul1e6(big.NewInt(-760)), unrealizePnL) - availableMargin := getAvailableMargin(_trader, pendingFundingShort, oraclePrices, db.GetLastPrices(), db.configService.getMinAllowableMargin(), []Market{market}) + availableMargin := getAvailableMargin(_trader, pendingFundingShort, assets, oraclePrices, db.GetLastPrices(), db.configService.getMinAllowableMargin(), []Market{market}) // availableMargin = 1000 + 37 (pendingFundingShort) - 760 (uPnL) - 2860/5 = -295 assert.Equal(t, hu.Mul1e6(big.NewInt(-295)), availableMargin) - // for Maintenance_Margin we select the max of 2 hence, oracle_mf - notionalPosition, unrealizePnL = getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginShort, pendingFundingShort), Maintenance_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) + // for hu.Maintenance_Margin we select the max of 2 hence, oracle_mf + notionalPosition, unrealizePnL = getTotalNotionalPositionAndUnrealizedPnl(_trader, new(big.Int).Add(marginShort, pendingFundingShort), hu.Maintenance_Margin, oraclePrices, db.GetLastPrices(), []Market{market}) assert.Equal(t, hu.Mul1e6(big.NewInt(2840)), notionalPosition) assert.Equal(t, hu.Mul1e6(big.NewInt(-740)), unrealizePnL) - marginFraction := calcMarginFraction(_trader, pendingFundingShort, oraclePrices, db.GetLastPrices(), []Market{market}) + marginFraction := calcMarginFraction(_trader, pendingFundingShort, assets, oraclePrices, db.GetLastPrices(), []Market{market}) assert.Equal(t, new(big.Int).Div(hu.Mul1e6(new(big.Int).Add(new(big.Int).Sub(marginShort, pendingFundingShort), unrealizePnL)), notionalPosition), marginFraction) - liquidablePositions, _ := db.GetNaughtyTraders(oraclePrices, []Market{market}) + liquidablePositions, _ := db.GetNaughtyTraders(oraclePrices, assets, []Market{market}) assert.Equal(t, 0, len(liquidablePositions)) }) }) } func TestGetNormalisedMargin(t *testing.T) { + assets := []hu.Collateral{{Price: big.NewInt(1e6), Weight: big.NewInt(1e6), Decimals: 6}} t.Run("When trader has no margin", func(t *testing.T) { trader := Trader{} - assert.Equal(t, trader.Margin.Deposited[HUSD], getNormalisedMargin(&trader)) + assert.Equal(t, big.NewInt(0), getNormalisedMargin(&trader, assets)) }) t.Run("When trader has margin in HUSD", func(t *testing.T) { margin := hu.Mul1e6(big.NewInt(10)) @@ -261,7 +263,7 @@ func TestGetNormalisedMargin(t *testing.T) { HUSD: margin, }}, } - assert.Equal(t, margin, getNormalisedMargin(&trader)) + assert.Equal(t, margin, getNormalisedMargin(&trader, assets)) }) } @@ -270,13 +272,13 @@ func TestGetNotionalPosition(t *testing.T) { price := hu.Mul1e6(big.NewInt(10)) size := hu.Mul1e18(big.NewInt(20)) expectedNotionalPosition := hu.Div1e18(big.NewInt(0).Mul(price, size)) - assert.Equal(t, expectedNotionalPosition, getNotionalPosition(price, size)) + assert.Equal(t, expectedNotionalPosition, hu.GetNotionalPosition(price, size)) }) t.Run("When size is negative, it return abs value", func(t *testing.T) { price := hu.Mul1e6(big.NewInt(10)) size := hu.Mul1e18(big.NewInt(-20)) expectedNotionalPosition := hu.Div1e18(big.NewInt(0).Abs(big.NewInt(0).Mul(price, size))) - assert.Equal(t, expectedNotionalPosition, getNotionalPosition(price, size)) + assert.Equal(t, expectedNotionalPosition, hu.GetNotionalPosition(price, size)) }) } @@ -287,13 +289,12 @@ func TestGetPositionMetadata(t *testing.T) { entryPrice := hu.Mul1e6(big.NewInt(10)) newPrice := hu.Mul1e6(big.NewInt(15)) position := &Position{ - Size: size, - OpenNotional: getNotionalPosition(entryPrice, size), + Position: hu.Position{OpenNotional: hu.GetNotionalPosition(entryPrice, size), Size: size}, } arbitaryMarginValue := hu.Mul1e6(big.NewInt(69)) notionalPosition, uPnL, mf := getPositionMetadata(newPrice, position.OpenNotional, position.Size, arbitaryMarginValue) - assert.Equal(t, getNotionalPosition(newPrice, size), notionalPosition) + assert.Equal(t, hu.GetNotionalPosition(newPrice, size), notionalPosition) expectedPnl := hu.Div1e18(big.NewInt(0).Mul(big.NewInt(0).Sub(newPrice, entryPrice), size)) assert.Equal(t, expectedPnl, uPnL) assert.Equal(t, new(big.Int).Div(hu.Mul1e6(new(big.Int).Add(arbitaryMarginValue, uPnL)), notionalPosition), mf) @@ -303,12 +304,11 @@ func TestGetPositionMetadata(t *testing.T) { entryPrice := hu.Mul1e6(big.NewInt(10)) newPrice := hu.Mul1e6(big.NewInt(15)) position := &Position{ - Size: size, - OpenNotional: getNotionalPosition(entryPrice, size), + Position: hu.Position{OpenNotional: hu.GetNotionalPosition(entryPrice, size), Size: size}, } notionalPosition, uPnL, _ := getPositionMetadata(newPrice, position.OpenNotional, position.Size, big.NewInt(0)) - assert.Equal(t, getNotionalPosition(newPrice, size), notionalPosition) + assert.Equal(t, hu.GetNotionalPosition(newPrice, size), notionalPosition) expectedPnl := hu.Div1e18(big.NewInt(0).Mul(big.NewInt(0).Sub(newPrice, entryPrice), size)) assert.Equal(t, expectedPnl, uPnL) }) @@ -319,12 +319,11 @@ func TestGetPositionMetadata(t *testing.T) { entryPrice := hu.Mul1e6(big.NewInt(10)) newPrice := hu.Mul1e6(big.NewInt(5)) position := &Position{ - Size: size, - OpenNotional: getNotionalPosition(entryPrice, size), + Position: hu.Position{OpenNotional: hu.GetNotionalPosition(entryPrice, size), Size: size}, } notionalPosition, uPnL, _ := getPositionMetadata(newPrice, position.OpenNotional, position.Size, big.NewInt(0)) - assert.Equal(t, getNotionalPosition(newPrice, size), notionalPosition) + assert.Equal(t, hu.GetNotionalPosition(newPrice, size), notionalPosition) expectedPnl := hu.Div1e18(big.NewInt(0).Mul(big.NewInt(0).Sub(newPrice, entryPrice), size)) assert.Equal(t, expectedPnl, uPnL) }) @@ -333,11 +332,10 @@ func TestGetPositionMetadata(t *testing.T) { entryPrice := hu.Mul1e6(big.NewInt(10)) newPrice := hu.Mul1e6(big.NewInt(5)) position := &Position{ - Size: size, - OpenNotional: getNotionalPosition(entryPrice, size), + Position: hu.Position{OpenNotional: hu.GetNotionalPosition(entryPrice, size), Size: size}, } notionalPosition, uPnL, _ := getPositionMetadata(newPrice, position.OpenNotional, position.Size, big.NewInt(0)) - assert.Equal(t, getNotionalPosition(newPrice, size), notionalPosition) + assert.Equal(t, hu.GetNotionalPosition(newPrice, size), notionalPosition) expectedPnl := hu.Div1e18(big.NewInt(0).Mul(big.NewInt(0).Sub(newPrice, entryPrice), size)) assert.Equal(t, expectedPnl, uPnL) }) @@ -349,8 +347,7 @@ func getPosition(market Market, openNotional *big.Int, size *big.Int, unrealized liquidationThreshold = getLiquidationThreshold(maxLiquidationRatio, minSizeRequirement, size) } return &Position{ - OpenNotional: openNotional, - Size: size, + Position: hu.Position{OpenNotional: openNotional, Size: size}, UnrealisedFunding: unrealizedFunding, LastPremiumFraction: lastPremiumFraction, LiquidationThreshold: liquidationThreshold, diff --git a/plugin/evm/orderbook/matching_pipeline.go b/plugin/evm/orderbook/matching_pipeline.go index 29ca32eb96..818d63a549 100644 --- a/plugin/evm/orderbook/matching_pipeline.go +++ b/plugin/evm/orderbook/matching_pipeline.go @@ -6,6 +6,7 @@ import ( "sync" "time" + hu "github.com/ava-labs/subnet-evm/plugin/evm/orderbook/hubbleutils" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -71,9 +72,10 @@ func (pipeline *MatchingPipeline) Run(blockNumber *big.Int) bool { // fetch the underlying price and run the matching engine underlyingPrices := pipeline.GetUnderlyingPrices() + assets := pipeline.GetCollaterals() // build trader map - liquidablePositions, ordersToCancel := pipeline.db.GetNaughtyTraders(underlyingPrices, markets) + liquidablePositions, ordersToCancel := pipeline.db.GetNaughtyTraders(underlyingPrices, assets, markets) cancellableOrderIds := pipeline.cancelLimitOrders(ordersToCancel) orderMap := make(map[Market]*Orders) for _, market := range markets { @@ -117,6 +119,10 @@ func (pipeline *MatchingPipeline) GetUnderlyingPrices() map[Market]*big.Int { return underlyingPrices } +func (pipeline *MatchingPipeline) GetCollaterals() []hu.Collateral { + return pipeline.configService.GetCollaterals() +} + func (pipeline *MatchingPipeline) cancelLimitOrders(cancellableOrders map[common.Address][]Order) map[common.Hash]struct{} { cancellableOrderIds := map[common.Hash]struct{}{} // @todo: if there are too many cancellable orders, they might not fit in a single block. Need to adjust for that. diff --git a/plugin/evm/orderbook/memory_database.go b/plugin/evm/orderbook/memory_database.go index cb2d7062c9..fab59b370e 100644 --- a/plugin/evm/orderbook/memory_database.go +++ b/plugin/evm/orderbook/memory_database.go @@ -51,9 +51,9 @@ const ( RETRY_AFTER_BLOCKS = 10 ) -type Market int64 +type Market = hu.Market -type Collateral int +type Collateral = int const ( HUSD Collateral = iota @@ -180,8 +180,7 @@ func (order Order) ToOrderMin() OrderMin { } type Position struct { - OpenNotional *big.Int `json:"open_notional"` - Size *big.Int `json:"size"` + hu.Position UnrealisedFunding *big.Int `json:"unrealised_funding"` LastPremiumFraction *big.Int `json:"last_premium_fraction"` LiquidationThreshold *big.Int `json:"liquidation_threshold"` @@ -240,7 +239,7 @@ type LimitOrderDatabase interface { Accept(acceptedBlockNumber uint64, blockTimestamp uint64) SetOrderStatus(orderId common.Hash, status Status, info string, blockNumber uint64) error RevertLastStatus(orderId common.Hash) error - GetNaughtyTraders(oraclePrices map[Market]*big.Int, markets []Market) ([]LiquidablePosition, map[common.Address][]Order) + GetNaughtyTraders(oraclePrices map[Market]*big.Int, assets []hu.Collateral, markets []Market) ([]LiquidablePosition, map[common.Address][]Order) GetAllOpenOrdersForTrader(trader common.Address) []Order GetOpenOrdersForTraderByType(trader common.Address, orderType OrderType) []Order UpdateLastPremiumFraction(market Market, trader common.Address, lastPremiumFraction *big.Int, cumlastPremiumFraction *big.Int) @@ -913,7 +912,7 @@ func determinePositionToLiquidate(trader *Trader, addr common.Address, marginFra return liquidable } -func (db *InMemoryDatabase) GetNaughtyTraders(oraclePrices map[Market]*big.Int, markets []Market) ([]LiquidablePosition, map[common.Address][]Order) { +func (db *InMemoryDatabase) GetNaughtyTraders(oraclePrices map[Market]*big.Int, assets []hu.Collateral, markets []Market) ([]LiquidablePosition, map[common.Address][]Order) { db.mu.RLock() defer db.mu.RUnlock() @@ -926,7 +925,7 @@ func (db *InMemoryDatabase) GetNaughtyTraders(oraclePrices map[Market]*big.Int, for addr, trader := range db.TraderMap { pendingFunding := getTotalFunding(trader, markets) - marginFraction := calcMarginFraction(trader, pendingFunding, oraclePrices, db.LastPrice, markets) + marginFraction := calcMarginFraction(trader, pendingFunding, assets, oraclePrices, db.LastPrice, markets) if marginFraction.Cmp(db.configService.getMaintenanceMargin()) == -1 { log.Info("below maintenanceMargin", "trader", addr.String(), "marginFraction", prettifyScaledBigInt(marginFraction, 6)) if len(minSizes) == 0 { @@ -941,7 +940,7 @@ func (db *InMemoryDatabase) GetNaughtyTraders(oraclePrices map[Market]*big.Int, continue } // has orders that might be cancellable - availableMargin := getAvailableMargin(trader, pendingFunding, oraclePrices, db.LastPrice, db.configService.getMinAllowableMargin(), markets) + availableMargin := getAvailableMargin(trader, pendingFunding, assets, oraclePrices, db.LastPrice, db.configService.getMinAllowableMargin(), markets) // availableMargin := getAvailableMarginWithDebugInfo(addr, trader, pendingFunding, oraclePrices, db.LastPrice, db.configService.getMinAllowableMargin(), markets) if availableMargin.Sign() == -1 { foundCancellableOrders := db.determineOrdersToCancel(addr, trader, availableMargin, oraclePrices, ordersToCancel) @@ -1086,29 +1085,22 @@ func getBlankTrader() *Trader { } } -func getAvailableMargin(trader *Trader, pendingFunding *big.Int, oraclePrices map[Market]*big.Int, lastPrices map[Market]*big.Int, minAllowableMargin *big.Int, markets []Market) *big.Int { - margin := new(big.Int).Sub(getNormalisedMargin(trader), pendingFunding) - notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(trader, margin, Min_Allowable_Margin, oraclePrices, lastPrices, markets) - utilisedMargin := hu.Div1e6(new(big.Int).Mul(notionalPosition, minAllowableMargin)) - return new(big.Int).Sub( - new(big.Int).Add(margin, unrealizePnL), - new(big.Int).Add(utilisedMargin, trader.Margin.Reserved), - ) -} - -func getAvailableMarginWithDebugInfo(addr common.Address, trader *Trader, pendingFunding *big.Int, oraclePrices map[Market]*big.Int, lastPrices map[Market]*big.Int, minAllowableMargin *big.Int, markets []Market) *big.Int { - margin := new(big.Int).Sub(getNormalisedMargin(trader), pendingFunding) - notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(trader, margin, Min_Allowable_Margin, oraclePrices, lastPrices, markets) - utilisedMargin := hu.Div1e6(new(big.Int).Mul(notionalPosition, minAllowableMargin)) - availableMargin := new(big.Int).Sub( - new(big.Int).Add(margin, unrealizePnL), - new(big.Int).Add(utilisedMargin, trader.Margin.Reserved), +func getAvailableMargin(trader *Trader, pendingFunding *big.Int, assets []hu.Collateral, oraclePrices map[Market]*big.Int, lastPrices map[Market]*big.Int, minAllowableMargin *big.Int, markets []Market) *big.Int { + return hu.GetAvailableMargin( + &hu.HubbleState{ + Assets: assets, + OraclePrices: oraclePrices, + LastPrices: lastPrices, + ActiveMarkets: markets, + MinAllowableMargin: minAllowableMargin, + }, + &hu.UserState{ + Positions: translatePositions(trader.Positions), + Margins: getMargins(trader, len(assets)), + PendingFunding: pendingFunding, + ReservedMargin: trader.Margin.Reserved, + }, ) - if availableMargin.Sign() == -1 { - log.Info("availableMargin < 0", "addr", addr.String(), "pendingFunding", pendingFunding, "margin", margin, "notionalPosition", notionalPosition, "unrealizePnL", unrealizePnL, "utilisedMargin", utilisedMargin, "Reserved", trader.Margin.Reserved) - log.Info("prices", "oraclePrices", oraclePrices, "lastPrices", lastPrices) - } - return availableMargin } // deepCopyOrder deep copies the LimitOrder struct @@ -1134,8 +1126,10 @@ func deepCopyTrader(order *Trader) *Trader { positions := map[Market]*Position{} for market, position := range order.Positions { positions[market] = &Position{ - OpenNotional: big.NewInt(0).Set(position.OpenNotional), - Size: big.NewInt(0).Set(position.Size), + Position: hu.Position{ + OpenNotional: big.NewInt(0).Set(position.OpenNotional), + Size: big.NewInt(0).Set(position.Size), + }, UnrealisedFunding: big.NewInt(0).Set(position.UnrealisedFunding), LastPremiumFraction: big.NewInt(0).Set(position.LastPremiumFraction), LiquidationThreshold: big.NewInt(0).Set(position.LiquidationThreshold), diff --git a/plugin/evm/orderbook/memory_database_test.go b/plugin/evm/orderbook/memory_database_test.go index bafd3b2f2c..ba0377520f 100644 --- a/plugin/evm/orderbook/memory_database_test.go +++ b/plugin/evm/orderbook/memory_database_test.go @@ -20,6 +20,7 @@ var status Status = Placed var blockNumber = big.NewInt(2) var market = Market(0) +var assets = []hu.Collateral{{Price: big.NewInt(1e6), Weight: big.NewInt(1e6), Decimals: 6}} func TestgetDatabase(t *testing.T) { inMemoryDatabase := getDatabase() @@ -405,27 +406,27 @@ func TestGetCancellableOrders(t *testing.T) { // Setup completed, assertions start here _trader := inMemoryDatabase.TraderMap[trader] assert.Equal(t, big.NewInt(0), getTotalFunding(_trader, []Market{market})) - assert.Equal(t, depositMargin, getNormalisedMargin(_trader)) + assert.Equal(t, depositMargin, getNormalisedMargin(_trader, assets)) // last price based notional = 9 * 10 = 90, pnl = 0, mf = (40-0)/90 = 0.44 // oracle price based notional = 9 * 11 = 99, pnl = -9, mf = (40-9)/99 = 0.31 - // for Min_Allowable_Margin we select the min of 2 hence, oracle based mf - notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(_trader, depositMargin, Min_Allowable_Margin, priceMap, inMemoryDatabase.GetLastPrices(), []Market{market}) + // for hu.Min_Allowable_Margin we select the min of 2 hence, oracle based mf + notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(_trader, depositMargin, hu.Min_Allowable_Margin, priceMap, inMemoryDatabase.GetLastPrices(), []Market{market}) assert.Equal(t, hu.Mul1e6(big.NewInt(99)), notionalPosition) assert.Equal(t, hu.Mul1e6(big.NewInt(-9)), unrealizePnL) - // for Maintenance_Margin we select the max of 2 hence, last price based mf - notionalPosition, unrealizePnL = getTotalNotionalPositionAndUnrealizedPnl(_trader, depositMargin, Maintenance_Margin, priceMap, inMemoryDatabase.GetLastPrices(), []Market{market}) + // for hu.Maintenance_Margin we select the max of 2 hence, last price based mf + notionalPosition, unrealizePnL = getTotalNotionalPositionAndUnrealizedPnl(_trader, depositMargin, hu.Maintenance_Margin, priceMap, inMemoryDatabase.GetLastPrices(), []Market{market}) assert.Equal(t, hu.Mul1e6(big.NewInt(90)), notionalPosition) assert.Equal(t, big.NewInt(0), unrealizePnL) - marginFraction := calcMarginFraction(_trader, big.NewInt(0), priceMap, inMemoryDatabase.GetLastPrices(), []Market{market}) + marginFraction := calcMarginFraction(_trader, big.NewInt(0), assets, priceMap, inMemoryDatabase.GetLastPrices(), []Market{market}) assert.Equal(t, new(big.Int).Div(hu.Mul1e6(depositMargin /* uPnL = 0 */), notionalPosition), marginFraction) - availableMargin := getAvailableMargin(_trader, big.NewInt(0), priceMap, inMemoryDatabase.GetLastPrices(), inMemoryDatabase.configService.getMinAllowableMargin(), []Market{market}) + availableMargin := getAvailableMargin(_trader, big.NewInt(0), assets, priceMap, inMemoryDatabase.GetLastPrices(), inMemoryDatabase.configService.getMinAllowableMargin(), []Market{market}) // availableMargin = 40 - 9 - (99 + (10+9+8) * 3)/5 = -5 assert.Equal(t, hu.Mul1e6(big.NewInt(-5)), availableMargin) - _, ordersToCancel := inMemoryDatabase.GetNaughtyTraders(priceMap, []Market{market}) + _, ordersToCancel := inMemoryDatabase.GetNaughtyTraders(priceMap, assets, []Market{market}) // t.Log("####", "ordersToCancel", ordersToCancel) assert.Equal(t, 1, len(ordersToCancel)) // only one trader diff --git a/plugin/evm/orderbook/mocks.go b/plugin/evm/orderbook/mocks.go index 3dc444d538..8a6c754ad0 100644 --- a/plugin/evm/orderbook/mocks.go +++ b/plugin/evm/orderbook/mocks.go @@ -4,6 +4,7 @@ import ( "math/big" "github.com/ava-labs/subnet-evm/core/types" + hu "github.com/ava-labs/subnet-evm/plugin/evm/orderbook/hubbleutils" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/mock" ) @@ -115,7 +116,7 @@ func (db *MockLimitOrderDatabase) GetLastPrices() map[Market]*big.Int { return map[Market]*big.Int{} } -func (db *MockLimitOrderDatabase) GetNaughtyTraders(oraclePrices map[Market]*big.Int, markets []Market) ([]LiquidablePosition, map[common.Address][]Order) { +func (db *MockLimitOrderDatabase) GetNaughtyTraders(oraclePrices map[Market]*big.Int, assets []hu.Collateral, markets []Market) ([]LiquidablePosition, map[common.Address][]Order) { return []LiquidablePosition{}, map[common.Address][]Order{} } @@ -225,11 +226,6 @@ func (mcs *MockConfigService) GetAcceptableBoundsForLiquidation(market Market) ( return args.Get(0).(*big.Int), args.Get(1).(*big.Int) } -// func (mcs *MockConfigService) getOracleSpreadThreshold(market Market) *big.Int { -// args := mcs.Called() -// return args.Get(0).(*big.Int) -// } - func (mcs *MockConfigService) getLiquidationSpreadThreshold(market Market) *big.Int { return big.NewInt(1e4) } @@ -277,6 +273,10 @@ func (cs *MockConfigService) GetCumulativePremiumFractionAtBlock(market Market, return big.NewInt(0) } +func (cs *MockConfigService) GetCollaterals() []hu.Collateral { + return []hu.Collateral{{Price: big.NewInt(1e6), Weight: big.NewInt(1e6), Decimals: 6}} +} + func NewMockConfigService() *MockConfigService { return &MockConfigService{} } diff --git a/plugin/evm/orderbook/service.go b/plugin/evm/orderbook/service.go index 05921293d7..fcecda3637 100644 --- a/plugin/evm/orderbook/service.go +++ b/plugin/evm/orderbook/service.go @@ -107,19 +107,19 @@ func (api *OrderBookAPI) GetDebugData(ctx context.Context, trader string) GetDeb markets[i] = Market(i) oraclePrices[Market(i)] = prices[Market(i)] } - + assets := api.configService.GetCollaterals() for addr, trader := range traderMap { pendingFunding := getTotalFunding(&trader, markets) - margin := new(big.Int).Sub(getNormalisedMargin(&trader), pendingFunding) - notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(&trader, margin, Min_Allowable_Margin, oraclePrices, lastPrices, markets) - marginFraction := calcMarginFraction(&trader, pendingFunding, oraclePrices, lastPrices, markets) - availableMargin := getAvailableMargin(&trader, pendingFunding, oraclePrices, lastPrices, api.configService.getMinAllowableMargin(), markets) + margin := new(big.Int).Sub(getNormalisedMargin(&trader, assets), pendingFunding) + notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(&trader, margin, hu.Min_Allowable_Margin, oraclePrices, lastPrices, markets) + marginFraction := calcMarginFraction(&trader, pendingFunding, assets, oraclePrices, lastPrices, markets) + availableMargin := getAvailableMargin(&trader, pendingFunding, assets, oraclePrices, lastPrices, api.configService.getMinAllowableMargin(), markets) utilisedMargin := hu.Div1e6(new(big.Int).Mul(notionalPosition, minAllowableMargin)) response.MarginFraction[addr] = marginFraction response.AvailableMargin[addr] = availableMargin response.PendingFunding[addr] = pendingFunding - response.Margin[addr] = getNormalisedMargin(&trader) + response.Margin[addr] = getNormalisedMargin(&trader, assets) response.UtilisedMargin[addr] = utilisedMargin response.NotionalPosition[addr] = notionalPosition response.UnrealizePnL[addr] = unrealizePnL diff --git a/plugin/evm/orderbook/trading_apis.go b/plugin/evm/orderbook/trading_apis.go index a3c3c95781..af6edd89b2 100644 --- a/plugin/evm/orderbook/trading_apis.go +++ b/plugin/evm/orderbook/trading_apis.go @@ -177,8 +177,9 @@ func (api *TradingAPI) GetMarginAndPositions(ctx context.Context, trader string) markets[i] = Market(i) } + assets := api.configService.GetCollaterals() pendingFunding := getTotalFunding(traderInfo, markets) - margin := new(big.Int).Sub(getNormalisedMargin(traderInfo), pendingFunding) + margin := new(big.Int).Sub(getNormalisedMargin(traderInfo, assets), pendingFunding) response.Margin = utils.BigIntToDecimal(margin, 6, 8) response.ReservedMargin = utils.BigIntToDecimal(traderInfo.Margin.Reserved, 6, 8) diff --git a/precompile/contracts/bibliophile/amm.go b/precompile/contracts/bibliophile/amm.go index 263cd38137..86cebaa6f6 100644 --- a/precompile/contracts/bibliophile/amm.go +++ b/precompile/contracts/bibliophile/amm.go @@ -15,23 +15,15 @@ const ( MAX_ORACLE_SPREAD_RATIO_SLOT int64 = 3 MAX_LIQUIDATION_RATIO_SLOT int64 = 4 MIN_SIZE_REQUIREMENT_SLOT int64 = 5 - ORACLE_SLOT int64 = 6 - UNDERLYING_ASSET_SLOT int64 = 7 - MAX_LIQUIDATION_PRICE_SPREAD int64 = 12 - MULTIPLIER_SLOT int64 = 13 - RED_STONE_ADAPTER_SLOT int64 = 16 - RED_STONE_FEED_ID_SLOT int64 = 17 - IMPACT_MARGIN_NOTIONAL_SLOT int64 = 22 - LAST_TRADE_PRICE_SLOT int64 = 23 - BIDS_SLOT int64 = 24 - ASKS_SLOT int64 = 25 - BIDS_HEAD_SLOT int64 = 26 - ASKS_HEAD_SLOT int64 = 27 -) - -const ( - // this slot is from TestOracle.sol - TEST_ORACLE_PRICES_MAPPING_SLOT int64 = 3 + 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 ) // AMM State @@ -88,10 +80,6 @@ func getMultiplier(stateDB contract.StateDB, market common.Address) *big.Int { return stateDB.GetState(market, common.BigToHash(big.NewInt(MULTIPLIER_SLOT))).Big() } -func getOracleAddress(stateDB contract.StateDB, market common.Address) common.Address { - return common.BytesToAddress(stateDB.GetState(market, common.BigToHash(big.NewInt(ORACLE_SLOT))).Bytes()) -} - func getUnderlyingAssetAddress(stateDB contract.StateDB, market common.Address) common.Address { return common.BytesToAddress(stateDB.GetState(market, common.BigToHash(big.NewInt(UNDERLYING_ASSET_SLOT))).Bytes()) } @@ -101,45 +89,6 @@ func getUnderlyingPriceForMarket(stateDB contract.StateDB, marketID int64) *big. return getUnderlyingPrice(stateDB, market) } -func getRedStoneAdapterAddress(stateDB contract.StateDB, market common.Address) common.Address { - return common.BytesToAddress(stateDB.GetState(market, common.BigToHash(big.NewInt(RED_STONE_ADAPTER_SLOT))).Bytes()) -} - -func getRedStoneFeedId(stateDB contract.StateDB, market common.Address) common.Hash { - return stateDB.GetState(market, common.BigToHash(big.NewInt(RED_STONE_FEED_ID_SLOT))) -} - -func getUnderlyingPrice(stateDB contract.StateDB, market common.Address) *big.Int { - redStoneAdapter := getRedStoneAdapterAddress(stateDB, market) - if redStoneAdapter.Hash().Big().Sign() != 0 { - feedId := getRedStoneFeedId(stateDB, market) - // first we check the feedId, if it is set, it should imply we are using a redstone oracle - // log.Info("red-stone-feed-id", "feedId", feedId.String()) - if feedId.Big().Sign() != 0 { - // redstone oracle is configured for this market - redstonePrice := getRedStonePrice(stateDB, redStoneAdapter, feedId) - // log.Info("redstone-price", "amm", market, "price", redstonePrice) - return redstonePrice - } - /* else { - // just log the red stone price, for testing before deployment - var feedId common.Hash - if strings.EqualFold(market.String(), "0xa72b463C21dA61cCc86069cFab82e9e8491152a0") { // eth amm - feedId = common.HexToHash("0x4554480000000000000000000000000000000000000000000000000000000000") - } else if strings.EqualFold(market.String(), "0xd80e57dB448b0692C396B890eE9c791D7386dAdC") { // avax amm - feedId = common.HexToHash("0x4156415800000000000000000000000000000000000000000000000000000000") - } - // redstonePrice := getRedStonePrice(stateDB, redStoneAdapter, feedId) - // log.Info("log-only-redstone-price", "amm", market, "price", redstonePrice) - } */ - } - // red stone oracle is not enabled for this market, we use the default TestOracle - oracle := getOracleAddress(stateDB, market) - underlying := getUnderlyingAssetAddress(stateDB, market) - slot := crypto.Keccak256(append(common.LeftPadBytes(underlying.Bytes(), 32), common.LeftPadBytes(big.NewInt(TEST_ORACLE_PRICES_MAPPING_SLOT).Bytes(), 32)...)) - return fromTwosComplement(stateDB.GetState(oracle, common.BytesToHash(slot)).Bytes()) -} - // Trader State func positionsStorageSlot(trader *common.Address) *big.Int { @@ -186,6 +135,13 @@ func getImpactMarginNotional(stateDB contract.StateDB, market common.Address) *b return stateDB.GetState(market, common.BigToHash(big.NewInt(IMPACT_MARGIN_NOTIONAL_SLOT))).Big() } +func getPosition(stateDB contract.StateDB, market common.Address, trader *common.Address) *hu.Position { + return &hu.Position{ + Size: getSize(stateDB, market, trader), + OpenNotional: getOpenNotional(stateDB, market, trader), + } +} + // Utils func getPendingFundingPayment(stateDB contract.StateDB, market common.Address, trader *common.Address) *big.Int { @@ -193,50 +149,6 @@ func getPendingFundingPayment(stateDB contract.StateDB, market common.Address, t return hu.Div1e18(new(big.Int).Mul(new(big.Int).Sub(cumulativePremiumFraction, GetLastPremiumFraction(stateDB, market, trader)), getSize(stateDB, market, trader))) } -func getOptimalPnl(stateDB contract.StateDB, market common.Address, oraclePrice *big.Int, lastPrice *big.Int, trader *common.Address, margin *big.Int, marginMode MarginMode) (notionalPosition *big.Int, uPnL *big.Int) { - size := getSize(stateDB, market, trader) - if size.Sign() == 0 { - return big.NewInt(0), big.NewInt(0) - } - - openNotional := getOpenNotional(stateDB, market, trader) - // based on last price - notionalPosition, unrealizedPnl, lastPriceBasedMF := getPositionMetadata( - lastPrice, - openNotional, - size, - margin, - ) - - // based on oracle price - oracleBasedNotional, oracleBasedUnrealizedPnl, oracleBasedMF := getPositionMetadata( - oraclePrice, - openNotional, - size, - margin, - ) - - if (marginMode == Maintenance_Margin && oracleBasedMF.Cmp(lastPriceBasedMF) == 1) || // for liquidations - (marginMode == Min_Allowable_Margin && oracleBasedMF.Cmp(lastPriceBasedMF) == -1) { // for increasing leverage - return oracleBasedNotional, oracleBasedUnrealizedPnl - } - return notionalPosition, unrealizedPnl -} - -func getPositionMetadata(price *big.Int, openNotional *big.Int, size *big.Int, margin *big.Int) (notionalPos *big.Int, uPnl *big.Int, marginFraction *big.Int) { - notionalPos = hu.Div1e18(new(big.Int).Mul(price, new(big.Int).Abs(size))) - if notionalPos.Sign() == 0 { - return big.NewInt(0), big.NewInt(0), big.NewInt(0) - } - if size.Sign() == 1 { - uPnl = new(big.Int).Sub(notionalPos, openNotional) - } else { - uPnl = new(big.Int).Sub(openNotional, notionalPos) - } - marginFraction = new(big.Int).Div(hu.Mul1e6(new(big.Int).Add(margin, uPnl)), notionalPos) - return notionalPos, uPnl, marginFraction -} - // Common Utils func fromTwosComplement(b []byte) *big.Int { t := new(big.Int).SetBytes(b) diff --git a/precompile/contracts/bibliophile/api.go b/precompile/contracts/bibliophile/api.go index 359e70f943..5858056c3b 100644 --- a/precompile/contracts/bibliophile/api.go +++ b/precompile/contracts/bibliophile/api.go @@ -117,12 +117,12 @@ func GetAMMVariables(stateDB contract.StateDB, ammAddress common.Address, ammInd maxLiquidationRatio := GetMaxLiquidationRatio(stateDB, ammIndex) maxLiquidationPriceSpread := GetMaxLiquidationPriceSpread(stateDB, ammIndex) minSizeRequirement := GetMinSizeRequirement(stateDB, ammIndex) - oracleAddress := getOracleAddress(stateDB, ammAddress) + oracleAddress := getOracleAddress(stateDB) underlyingAssetAddress := getUnderlyingAssetAddress(stateDB, ammAddress) underlyingPriceForMarket := getUnderlyingPriceForMarket(stateDB, ammIndex) underlyingPrice := getUnderlyingPrice(stateDB, ammAddress) - redStoneAdapterAddress := getRedStoneAdapterAddress(stateDB, ammAddress) - redStoneFeedId := getRedStoneFeedId(stateDB, ammAddress) + redStoneAdapterAddress := getRedStoneAdapterAddress(stateDB, oracleAddress) + redStoneFeedId := getRedStoneFeedId(stateDB, oracleAddress, underlyingAssetAddress) bidsHead := getBidsHead(stateDB, ammAddress) bidsHeadSize := getBidSize(stateDB, ammAddress, bidsHead) asksHead := getAsksHead(stateDB, ammAddress) diff --git a/precompile/contracts/bibliophile/clearing_house.go b/precompile/contracts/bibliophile/clearing_house.go index 126c0568f3..a393c30278 100644 --- a/precompile/contracts/bibliophile/clearing_house.go +++ b/precompile/contracts/bibliophile/clearing_house.go @@ -49,7 +49,6 @@ func GetMarkets(stateDB contract.StateDB) []common.Address { for i := int64(0); i < numMarkets; i++ { amm := stateDB.GetState(common.HexToAddress(CLEARING_HOUSE_GENESIS_ADDRESS), common.BigToHash(new(big.Int).Add(baseStorageSlot, big.NewInt(i)))) markets[i] = common.BytesToAddress(amm.Bytes()) - } return markets } @@ -66,28 +65,40 @@ type GetNotionalPositionAndMarginOutput struct { } func getNotionalPositionAndMargin(stateDB contract.StateDB, input *GetNotionalPositionAndMarginInput) GetNotionalPositionAndMarginOutput { - margin := GetNormalizedMargin(stateDB, input.Trader) + markets := GetMarkets(stateDB) + numMarkets := len(markets) + positions := make(map[int]*hu.Position, numMarkets) + underlyingPrices := make(map[int]*big.Int, numMarkets) + lastPrices := make(map[int]*big.Int, numMarkets) + var marketIds []int + for i, market := range markets { + positions[i] = getPosition(stateDB, getMarketAddressFromMarketID(int64(i), stateDB), &input.Trader) + underlyingPrices[i] = getUnderlyingPrice(stateDB, market) + lastPrices[i] = getLastPrice(stateDB, market) + marketIds = append(marketIds, i) + } + pendingFunding := big.NewInt(0) if input.IncludeFundingPayments { - margin.Sub(margin, GetTotalFunding(stateDB, &input.Trader)) + pendingFunding = GetTotalFunding(stateDB, &input.Trader) } - notionalPosition, unrealizedPnl := GetTotalNotionalPositionAndUnrealizedPnl(stateDB, &input.Trader, margin, GetMarginMode(input.Mode)) + notionalPosition, margin := hu.GetNotionalPositionAndMargin( + &hu.HubbleState{ + Assets: GetCollaterals(stateDB), + OraclePrices: underlyingPrices, + LastPrices: lastPrices, + ActiveMarkets: marketIds, + }, + &hu.UserState{ + Positions: positions, + Margins: getMargins(stateDB, input.Trader), + PendingFunding: pendingFunding, + }, + input.Mode, + ) return GetNotionalPositionAndMarginOutput{ NotionalPosition: notionalPosition, - Margin: hu.Add(margin, unrealizedPnl), - } -} - -func GetTotalNotionalPositionAndUnrealizedPnl(stateDB contract.StateDB, trader *common.Address, margin *big.Int, marginMode MarginMode) (*big.Int, *big.Int) { - notionalPosition := big.NewInt(0) - unrealizedPnl := big.NewInt(0) - for _, market := range GetMarkets(stateDB) { - lastPrice := getLastPrice(stateDB, market) - oraclePrice := getUnderlyingPrice(stateDB, market) - _notionalPosition, _unrealizedPnl := getOptimalPnl(stateDB, market, oraclePrice, lastPrice, trader, margin, marginMode) - notionalPosition.Add(notionalPosition, _notionalPosition) - unrealizedPnl.Add(unrealizedPnl, _unrealizedPnl) + Margin: margin, } - return notionalPosition, unrealizedPnl } func GetTotalFunding(stateDB contract.StateDB, trader *common.Address) *big.Int { diff --git a/precompile/contracts/bibliophile/client_mock.go b/precompile/contracts/bibliophile/client_mock.go index a914fc80d9..1fcd080b85 100644 --- a/precompile/contracts/bibliophile/client_mock.go +++ b/precompile/contracts/bibliophile/client_mock.go @@ -8,6 +8,7 @@ import ( big "math/big" reflect "reflect" + hubbleutils "github.com/ava-labs/subnet-evm/plugin/evm/orderbook/hubbleutils" contract "github.com/ava-labs/subnet-evm/precompile/contract" common "github.com/ethereum/go-ethereum/common" gomock "github.com/golang/mock/gomock" @@ -149,6 +150,20 @@ func (mr *MockBibliophileClientMockRecorder) GetBlockPlaced(orderHash interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockPlaced", reflect.TypeOf((*MockBibliophileClient)(nil).GetBlockPlaced), orderHash) } +// GetCollaterals mocks base method. +func (m *MockBibliophileClient) GetCollaterals(stateDB contract.StateDB) []hubbleutils.Collateral { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCollaterals", stateDB) + ret0, _ := ret[0].([]hubbleutils.Collateral) + return ret0 +} + +// GetCollaterals indicates an expected call of GetCollaterals. +func (mr *MockBibliophileClientMockRecorder) GetCollaterals(stateDB interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCollaterals", reflect.TypeOf((*MockBibliophileClient)(nil).GetCollaterals), stateDB) +} + // GetImpactMarginNotional mocks base method. func (m *MockBibliophileClient) GetImpactMarginNotional(ammAddress common.Address) *big.Int { m.ctrl.T.Helper() diff --git a/precompile/contracts/bibliophile/margin_account.go b/precompile/contracts/bibliophile/margin_account.go index 68a446b201..9ae5aa4bd8 100644 --- a/precompile/contracts/bibliophile/margin_account.go +++ b/precompile/contracts/bibliophile/margin_account.go @@ -12,35 +12,72 @@ import ( const ( MARGIN_ACCOUNT_GENESIS_ADDRESS = "0x0300000000000000000000000000000000000001" - VAR_MARGIN_MAPPING_SLOT int64 = 10 - VAR_RESERVED_MARGIN_SLOT int64 = 11 + ORACLE_SLOT int64 = 4 + SUPPORTED_COLLATERAL_SLOT int64 = 8 + MARGIN_MAPPING_SLOT int64 = 10 + RESERVED_MARGIN_SLOT int64 = 11 ) func GetNormalizedMargin(stateDB contract.StateDB, trader common.Address) *big.Int { - // this is only written for single hUSD collateral - // TODO: generalize for multiple collaterals - return getMargin(stateDB, big.NewInt(0), trader) + assets := GetCollaterals(stateDB) + margins := getMargins(stateDB, trader) + return hu.GetNormalizedMargin(assets, margins) } -func getMargin(stateDB contract.StateDB, collateralIdx *big.Int, trader common.Address) *big.Int { - marginStorageSlot := crypto.Keccak256(append(common.LeftPadBytes(collateralIdx.Bytes(), 32), common.LeftPadBytes(big.NewInt(VAR_MARGIN_MAPPING_SLOT).Bytes(), 32)...)) +func getMargins(stateDB contract.StateDB, trader common.Address) []*big.Int { + numAssets := getCollateralCount(stateDB) + margins := make([]*big.Int, numAssets) + for i := uint8(0); i < numAssets; i++ { + margins[i] = getMargin(stateDB, big.NewInt(int64(i)), trader) + } + return margins +} + +func getMargin(stateDB contract.StateDB, idx *big.Int, trader common.Address) *big.Int { + marginStorageSlot := crypto.Keccak256(append(common.LeftPadBytes(idx.Bytes(), 32), common.LeftPadBytes(big.NewInt(MARGIN_MAPPING_SLOT).Bytes(), 32)...)) marginStorageSlot = crypto.Keccak256(append(common.LeftPadBytes(trader.Bytes(), 32), marginStorageSlot...)) return fromTwosComplement(stateDB.GetState(common.HexToAddress(MARGIN_ACCOUNT_GENESIS_ADDRESS), common.BytesToHash(marginStorageSlot)).Bytes()) } func getReservedMargin(stateDB contract.StateDB, trader common.Address) *big.Int { - baseMappingHash := crypto.Keccak256(append(common.LeftPadBytes(trader.Bytes(), 32), common.LeftPadBytes(big.NewInt(VAR_RESERVED_MARGIN_SLOT).Bytes(), 32)...)) + baseMappingHash := crypto.Keccak256(append(common.LeftPadBytes(trader.Bytes(), 32), common.LeftPadBytes(big.NewInt(RESERVED_MARGIN_SLOT).Bytes(), 32)...)) return stateDB.GetState(common.HexToAddress(MARGIN_ACCOUNT_GENESIS_ADDRESS), common.BytesToHash(baseMappingHash)).Big() } func GetAvailableMargin(stateDB contract.StateDB, trader common.Address) *big.Int { - includeFundingPayment := true - mode := uint8(1) // Min_Allowable_Margin - output := getNotionalPositionAndMargin(stateDB, &GetNotionalPositionAndMarginInput{Trader: trader, IncludeFundingPayments: includeFundingPayment, Mode: mode}) - notionalPostion := output.NotionalPosition - margin := output.Margin - utitlizedMargin := hu.Div1e6(big.NewInt(0).Mul(notionalPostion, GetMinAllowableMargin(stateDB))) - reservedMargin := getReservedMargin(stateDB, trader) - // log.Info("GetAvailableMargin", "trader", trader, "notionalPostion", notionalPostion, "margin", margin, "utitlizedMargin", utitlizedMargin, "reservedMargin", reservedMargin) - return big.NewInt(0).Sub(big.NewInt(0).Sub(margin, utitlizedMargin), reservedMargin) + output := getNotionalPositionAndMargin(stateDB, &GetNotionalPositionAndMarginInput{Trader: trader, IncludeFundingPayments: true, Mode: uint8(1)}) // Min_Allowable_Margin + return hu.GetAvailableMargin_(output.NotionalPosition, output.Margin, getReservedMargin(stateDB, trader), GetMinAllowableMargin(stateDB)) +} + +func getOracleAddress(stateDB contract.StateDB) common.Address { + return common.BytesToAddress(stateDB.GetState(common.HexToAddress(MARGIN_ACCOUNT_GENESIS_ADDRESS), common.BigToHash(big.NewInt(ORACLE_SLOT))).Bytes()) +} + +func GetCollaterals(stateDB contract.StateDB) []hu.Collateral { + numAssets := getCollateralCount(stateDB) + assets := make([]hu.Collateral, numAssets) + for i := uint8(0); i < numAssets; i++ { + assets[i] = getCollateralAt(stateDB, i) + } + return assets +} + +func getCollateralCount(stateDB contract.StateDB) uint8 { + rawVal := stateDB.GetState(common.HexToAddress(MARGIN_ACCOUNT_GENESIS_ADDRESS), common.BigToHash(big.NewInt(SUPPORTED_COLLATERAL_SLOT))) + return uint8(new(big.Int).SetBytes(rawVal.Bytes()).Uint64()) +} + +func getCollateralAt(stateDB contract.StateDB, idx uint8) hu.Collateral { + // struct Collateral { IERC20 token; uint weight; uint8 decimals; } + baseSlot := hu.Add(collateralStorageSlot(), big.NewInt(int64(idx)*3)) // collateral struct size = 3 * 32 bytes + tokenAddress := common.BytesToAddress(stateDB.GetState(common.HexToAddress(MARGIN_ACCOUNT_GENESIS_ADDRESS), common.BigToHash(baseSlot)).Bytes()) + return hu.Collateral{ + Weight: stateDB.GetState(common.HexToAddress(MARGIN_ACCOUNT_GENESIS_ADDRESS), common.BigToHash(hu.Add(baseSlot, big.NewInt(1)))).Big(), + Decimals: uint8(stateDB.GetState(common.HexToAddress(MARGIN_ACCOUNT_GENESIS_ADDRESS), common.BigToHash(hu.Add(baseSlot, big.NewInt(2)))).Big().Uint64()), + Price: getUnderlyingPrice_(stateDB, tokenAddress), + } +} + +func collateralStorageSlot() *big.Int { + return new(big.Int).SetBytes(crypto.Keccak256(common.BigToHash(big.NewInt(SUPPORTED_COLLATERAL_SLOT)).Bytes())) } diff --git a/precompile/contracts/bibliophile/oracle.go b/precompile/contracts/bibliophile/oracle.go new file mode 100644 index 0000000000..394abd17de --- /dev/null +++ b/precompile/contracts/bibliophile/oracle.go @@ -0,0 +1,61 @@ +package bibliophile + +import ( + "math/big" + + "github.com/ava-labs/subnet-evm/precompile/contract" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +var ( + RED_STONE_VALUES_MAPPING_STORAGE_LOCATION = common.HexToHash("0x4dd0c77efa6f6d590c97573d8c70b714546e7311202ff7c11c484cc841d91bfc") // keccak256("RedStone.oracleValuesMapping"); + RED_STONE_LATEST_ROUND_ID_STORAGE_LOCATION = common.HexToHash("0xc68d7f1ee07d8668991a8951e720010c9d44c2f11c06b5cac61fbc4083263938") // keccak256("RedStone.latestRoundId"); + + AGGREGATOR_MAP_SLOT int64 = 1 + RED_STONE_ADAPTER_SLOT int64 = 2 +) + +const ( + // this slot is from TestOracle.sol + TEST_ORACLE_PRICES_MAPPING_SLOT int64 = 3 +) + +func getUnderlyingPrice(stateDB contract.StateDB, market common.Address) *big.Int { + return getUnderlyingPrice_(stateDB, getUnderlyingAssetAddress(stateDB, market)) +} + +func getUnderlyingPrice_(stateDB contract.StateDB, underlying common.Address) *big.Int { + oracle := getOracleAddress(stateDB) // this comes from margin account + feedId := getRedStoneFeedId(stateDB, oracle, underlying) + if feedId.Big().Sign() != 0 { + // redstone oracle is configured for this market + redStoneAdapter := getRedStoneAdapterAddress(stateDB, oracle) + redstonePrice := getRedStonePrice(stateDB, redStoneAdapter, feedId) + // log.Info("redstone-price", "amm", market, "price", redstonePrice) + return redstonePrice + } + // red stone oracle is not enabled for this market, we use the default TestOracle + slot := crypto.Keccak256(append(common.LeftPadBytes(underlying.Bytes(), 32), common.BigToHash(big.NewInt(TEST_ORACLE_PRICES_MAPPING_SLOT)).Bytes()...)) + return fromTwosComplement(stateDB.GetState(oracle, common.BytesToHash(slot)).Bytes()) +} + +func getRedStoneAdapterAddress(stateDB contract.StateDB, oracle common.Address) common.Address { + return common.BytesToAddress(stateDB.GetState(oracle, common.BigToHash(big.NewInt(RED_STONE_ADAPTER_SLOT))).Bytes()) +} + +func getRedStonePrice(stateDB contract.StateDB, adapterAddress common.Address, redStoneFeedId common.Hash) *big.Int { + latestRoundId := getlatestRoundId(stateDB, adapterAddress) + slot := common.BytesToHash(crypto.Keccak256(append(append(redStoneFeedId.Bytes(), common.LeftPadBytes(latestRoundId.Bytes(), 32)...), RED_STONE_VALUES_MAPPING_STORAGE_LOCATION.Bytes()...))) + return new(big.Int).Div(fromTwosComplement(stateDB.GetState(adapterAddress, slot).Bytes()), big.NewInt(100)) // we use 6 decimals precision everywhere +} + +func getlatestRoundId(stateDB contract.StateDB, adapterAddress common.Address) *big.Int { + return fromTwosComplement(stateDB.GetState(adapterAddress, RED_STONE_LATEST_ROUND_ID_STORAGE_LOCATION).Bytes()) +} + +func getRedStoneFeedId(stateDB contract.StateDB, oracle, underlying common.Address) common.Hash { + aggregatorMapSlot := crypto.Keccak256(append(common.LeftPadBytes(underlying.Bytes(), 32), common.BigToHash(big.NewInt(AGGREGATOR_MAP_SLOT)).Bytes()...)) + return stateDB.GetState(oracle, common.BytesToHash(aggregatorMapSlot)) +} diff --git a/precompile/contracts/bibliophile/redstone.go b/precompile/contracts/bibliophile/redstone.go deleted file mode 100644 index db3939458a..0000000000 --- a/precompile/contracts/bibliophile/redstone.go +++ /dev/null @@ -1,25 +0,0 @@ -package bibliophile - -import ( - "math/big" - - "github.com/ava-labs/subnet-evm/precompile/contract" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -var ( - RED_STONE_VALUES_MAPPING_STORAGE_LOCATION = common.HexToHash("0x4dd0c77efa6f6d590c97573d8c70b714546e7311202ff7c11c484cc841d91bfc") // keccak256("RedStone.oracleValuesMapping"); - RED_STONE_LATEST_ROUND_ID_STORAGE_LOCATION = common.HexToHash("0xc68d7f1ee07d8668991a8951e720010c9d44c2f11c06b5cac61fbc4083263938") // keccak256("RedStone.latestRoundId"); -) - -func getRedStonePrice(stateDB contract.StateDB, adapterAddress common.Address, redStoneFeedId common.Hash) *big.Int { - latestRoundId := getlatestRoundId(stateDB, adapterAddress) - slot := common.BytesToHash(crypto.Keccak256(append(append(redStoneFeedId.Bytes(), common.LeftPadBytes(latestRoundId.Bytes(), 32)...), RED_STONE_VALUES_MAPPING_STORAGE_LOCATION.Bytes()...))) - return new(big.Int).Div(fromTwosComplement(stateDB.GetState(adapterAddress, slot).Bytes()), big.NewInt(100)) // we use 6 decimals precision everywhere -} - -func getlatestRoundId(stateDB contract.StateDB, adapterAddress common.Address) *big.Int { - return fromTwosComplement(stateDB.GetState(adapterAddress, RED_STONE_LATEST_ROUND_ID_STORAGE_LOCATION).Bytes()) -} diff --git a/precompile/contracts/ticks/logic.go b/precompile/contracts/ticks/logic.go index 3c51b1d36b..86a3fef8d8 100644 --- a/precompile/contracts/ticks/logic.go +++ b/precompile/contracts/ticks/logic.go @@ -56,8 +56,8 @@ func _sampleImpactBid(bibliophile b.BibliophileClient, ammAddress common.Address return big.NewInt(0) } impactMarginNotional := new(big.Int).Mul(_impactMarginNotional, big.NewInt(1e12)) - accNotional := big.NewInt(0) - accBaseQ := big.NewInt(0) + accNotional := big.NewInt(0) // 18 decimals + accBaseQ := big.NewInt(0) // 18 decimals tick := bibliophile.GetBidsHead(ammAddress) for tick.Sign() != 0 { amount := bibliophile.GetBidSize(ammAddress, tick) @@ -90,8 +90,8 @@ func _sampleImpactAsk(bibliophile b.BibliophileClient, ammAddress common.Address } impactMarginNotional := new(big.Int).Mul(_impactMarginNotional, big.NewInt(1e12)) tick := bibliophile.GetAsksHead(ammAddress) - accNotional := big.NewInt(0) - accBaseQ := big.NewInt(0) + accNotional := big.NewInt(0) // 18 decimals + accBaseQ := big.NewInt(0) // 18 decimals for tick.Sign() != 0 { amount := bibliophile.GetAskSize(ammAddress, tick) accumulator := new(big.Int).Add(accNotional, hu.Div1e6(big.NewInt(0).Mul(amount, tick))) @@ -109,10 +109,6 @@ func _sampleImpactAsk(bibliophile b.BibliophileClient, ammAddress common.Address return new(big.Int).Div(hu.Mul1e6(impactMarginNotional), new(big.Int).Add(baseQAtTick, accBaseQ)) // return value is in 6 decimals } -func GetQuote(bibliophile b.BibliophileClient, ammAddress common.Address, baseAssetQuantity *big.Int) *big.Int { - return big.NewInt(0) -} - func GetBaseQuote(bibliophile b.BibliophileClient, ammAddress common.Address, quoteAssetQuantity *big.Int) *big.Int { if quoteAssetQuantity.Sign() > 0 { // get the qoute to long quoteQuantity dollars return _sampleImpactAsk(bibliophile, ammAddress, quoteAssetQuantity) @@ -120,3 +116,58 @@ func GetBaseQuote(bibliophile b.BibliophileClient, ammAddress common.Address, qu // get the qoute to short quoteQuantity dollars return _sampleImpactBid(bibliophile, ammAddress, new(big.Int).Neg(quoteAssetQuantity)) } + +func GetQuote(bibliophile b.BibliophileClient, ammAddress common.Address, baseAssetQuantity *big.Int) *big.Int { + if baseAssetQuantity.Sign() > 0 { + return _sampleAsk(bibliophile, ammAddress, baseAssetQuantity) + } + return _sampleBid(bibliophile, ammAddress, new(big.Int).Neg(baseAssetQuantity)) +} + +func _sampleAsk(bibliophile b.BibliophileClient, ammAddress common.Address, baseAssetQuantity *big.Int) *big.Int { + if baseAssetQuantity.Sign() <= 0 { + return big.NewInt(0) + } + tick := bibliophile.GetAsksHead(ammAddress) + accNotional := big.NewInt(0) // 18 decimals + accBaseQ := big.NewInt(0) // 18 decimals + for tick.Sign() != 0 { + amount := bibliophile.GetAskSize(ammAddress, tick) + accumulator := hu.Add(accBaseQ, amount) + if accumulator.Cmp(baseAssetQuantity) >= 0 { + break + } + accNotional.Add(accNotional, hu.Div1e6(hu.Mul(amount, tick))) + accBaseQ = accumulator + tick = bibliophile.GetNextAskPrice(ammAddress, tick) + } + if tick.Sign() == 0 { + return big.NewInt(0) // insufficient liquidity + } + notionalAtTick := hu.Div1e6(hu.Mul(hu.Sub(baseAssetQuantity, accBaseQ), tick)) + return hu.Div(hu.Mul1e6(hu.Add(accNotional, notionalAtTick)), baseAssetQuantity) // return value is in 6 decimals +} + +func _sampleBid(bibliophile b.BibliophileClient, ammAddress common.Address, baseAssetQuantity *big.Int) *big.Int { + if baseAssetQuantity.Sign() <= 0 { + return big.NewInt(0) + } + tick := bibliophile.GetBidsHead(ammAddress) + accNotional := big.NewInt(0) // 18 decimals + accBaseQ := big.NewInt(0) // 18 decimals + for tick.Sign() != 0 { + amount := bibliophile.GetBidSize(ammAddress, tick) + accumulator := hu.Add(accBaseQ, amount) + if accumulator.Cmp(baseAssetQuantity) >= 0 { + break + } + accNotional.Add(accNotional, hu.Div1e6(hu.Mul(amount, tick))) + accBaseQ = accumulator + tick = bibliophile.GetNextBidPrice(ammAddress, tick) + } + if tick.Sign() == 0 { + return big.NewInt(0) // insufficient liquidity + } + notionalAtTick := hu.Div1e6(hu.Mul(hu.Sub(baseAssetQuantity, accBaseQ), tick)) + return hu.Div(hu.Mul1e6(hu.Add(accNotional, notionalAtTick)), baseAssetQuantity) // return value is in 6 decimals +} diff --git a/precompile/contracts/ticks/logic_test.go b/precompile/contracts/ticks/logic_test.go index 6be3ec5345..bc6d3882eb 100644 --- a/precompile/contracts/ticks/logic_test.go +++ b/precompile/contracts/ticks/logic_test.go @@ -287,7 +287,7 @@ func TestSampleImpactBid(t *testing.T) { output := SampleImpactBid(mockBibliophile, ammAddress) assert.Equal(t, big.NewInt(0), output) }) - t.Run("when there are multiple bids, it tries to fill with available bids and average price is returned for rest", func(t *testing.T) { + t.Run("when there are multiple bids", func(t *testing.T) { bids := []*big.Int{bidsHead, big.NewInt(2100000), big.NewInt(2200000), big.NewInt(2300000)} size := big.NewInt(1e18) // 1 ether mockBibliophile.EXPECT().GetImpactMarginNotional(ammAddress).Return(impactMarginNotional).Times(1) @@ -316,22 +316,22 @@ func TestSampleImpactBid(t *testing.T) { }) t.Run("when bids in orderbook are enough to cover impactMarginNotional", func(t *testing.T) { t.Run("when there is only one bid in orderbook it returns bidsHead", func(t *testing.T) { - newBidsHead := impactMarginNotional + bidsHead := impactMarginNotional mockBibliophile.EXPECT().GetImpactMarginNotional(ammAddress).Return(impactMarginNotional).Times(1) - mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(newBidsHead).Times(1) - mockBibliophile.EXPECT().GetBidSize(ammAddress, newBidsHead).Return(big.NewInt(1e18)).Times(1) + mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().GetBidSize(ammAddress, bidsHead).Return(big.NewInt(1e18)).Times(1) output := SampleImpactBid(mockBibliophile, ammAddress) - assert.Equal(t, newBidsHead, output) + assert.Equal(t, bidsHead, output) }) t.Run("when there are multiple bids, it tries to fill with available bids and average price is returned for rest", func(t *testing.T) { - newBidsHead := big.NewInt(2000000000) // 2000 units - bids := []*big.Int{newBidsHead} + bidsHead := big.NewInt(2000000000) // 2000 units + bids := []*big.Int{bidsHead} for i := int64(1); i < 6; i++ { - bids = append(bids, big.NewInt(0).Sub(newBidsHead, big.NewInt(i))) + bids = append(bids, big.NewInt(0).Sub(bidsHead, big.NewInt(i))) } size := big.NewInt(6e17) // 0.6 ether mockBibliophile.EXPECT().GetImpactMarginNotional(ammAddress).Return(impactMarginNotional).Times(1) - mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(newBidsHead).Times(1) + mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) mockBibliophile.EXPECT().GetNextBidPrice(ammAddress, bids[0]).Return(bids[1]).Times(1) mockBibliophile.EXPECT().GetNextBidPrice(ammAddress, bids[1]).Return(bids[2]).Times(1) mockBibliophile.EXPECT().GetNextBidPrice(ammAddress, bids[2]).Return(bids[3]).Times(1) @@ -348,7 +348,6 @@ func TestSampleImpactBid(t *testing.T) { filledQuote.Add(filledQuote, (hu.Div(hu.Mul(bids[i], size), big.NewInt(1e18)))) } unfulFilledQuote := big.NewInt(0).Sub(impactMarginNotional, filledQuote) - fmt.Println("unfulFilledQuote", unfulFilledQuote, "totalBaseQ", totalBaseQ, "filledQuote", filledQuote) // as quantity is in 1e18 baseQ = price * 1e18 / price baseQAtTick := big.NewInt(0).Div(big.NewInt(0).Mul(unfulFilledQuote, big.NewInt(1e18)), bids[3]) expectedOutput := big.NewInt(0).Div(big.NewInt(0).Mul(impactMarginNotional, big.NewInt(1e18)), big.NewInt(0).Add(totalBaseQ, baseQAtTick)) @@ -388,7 +387,7 @@ func TestSampleImpactAsk(t *testing.T) { output := SampleImpactAsk(mockBibliophile, ammAddress) assert.Equal(t, big.NewInt(0), output) }) - t.Run("when there are multiple asks, it tries to fill with available asks and average price is returned for rest", func(t *testing.T) { + t.Run("when there are multiple asks", func(t *testing.T) { asks := []*big.Int{asksHead, big.NewInt(2100000), big.NewInt(2200000), big.NewInt(2300000)} size := big.NewInt(1e18) // 1 ether mockBibliophile.EXPECT().GetImpactMarginNotional(ammAddress).Return(impactMarginNotional).Times(1) @@ -454,3 +453,149 @@ func TestSampleImpactAsk(t *testing.T) { }) }) } + +func TestSampleBid(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockBibliophile := b.NewMockBibliophileClient(ctrl) + ammAddress := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8") + bidsHead := big.NewInt(20 * 1e6) // $20 + baseAssetQuantity := big.NewInt(1e18) // 1 ether + t.Run("when bidsHead is 0", func(t *testing.T) { + mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(big.NewInt(0)).Times(1) + output := _sampleBid(mockBibliophile, ammAddress, baseAssetQuantity) + assert.Equal(t, big.NewInt(0), output) + }) + t.Run("when bidsHead > 0", func(t *testing.T) { + t.Run("when bids in orderbook are not enough to cover baseAssetQuantity", func(t *testing.T) { + t.Run("when there is only one bid in orderbook it returns 0", func(t *testing.T) { + mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().GetBidSize(ammAddress, bidsHead).Return(hu.Sub(baseAssetQuantity, big.NewInt(1))).Times(1) + mockBibliophile.EXPECT().GetNextBidPrice(ammAddress, bidsHead).Return(big.NewInt(0)).Times(1) + output := _sampleBid(mockBibliophile, ammAddress, baseAssetQuantity) + assert.Equal(t, big.NewInt(0), output) + }) + t.Run("when there are multiple bids", func(t *testing.T) { + bids := []*big.Int{bidsHead, big.NewInt(2100000), big.NewInt(2200000), big.NewInt(2300000)} + size := big.NewInt(24 * 1e16) // 0.24 ether + mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + for i := 0; i < len(bids); i++ { + mockBibliophile.EXPECT().GetBidSize(ammAddress, bids[i]).Return(size).Times(1) + if i != len(bids)-1 { + mockBibliophile.EXPECT().GetNextBidPrice(ammAddress, bids[i]).Return(bids[i+1]).Times(1) + } else { + mockBibliophile.EXPECT().GetNextBidPrice(ammAddress, bids[i]).Return(big.NewInt(0)).Times(1) + } + } + output := _sampleBid(mockBibliophile, ammAddress, baseAssetQuantity) + assert.Equal(t, big.NewInt(0), output) + }) + }) + t.Run("when bids in orderbook are enough to cover baseAssetQuantity", func(t *testing.T) { + t.Run("when there is only one bid in orderbook it returns bidsHead", func(t *testing.T) { + mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().GetBidSize(ammAddress, bidsHead).Return(baseAssetQuantity).Times(1) + output := _sampleBid(mockBibliophile, ammAddress, baseAssetQuantity) + assert.Equal(t, bidsHead, output) + }) + t.Run("when there are multiple bids, it tries to fill with available bids and average price is returned for rest", func(t *testing.T) { + bids := []*big.Int{bidsHead} + for i := int64(1); i < 6; i++ { + bids = append(bids, hu.Sub(bidsHead, big.NewInt(i))) + } + size := big.NewInt(3e17) // 0.3 ether + mockBibliophile.EXPECT().GetBidsHead(ammAddress).Return(bidsHead).Times(1) + mockBibliophile.EXPECT().GetNextBidPrice(ammAddress, bids[0]).Return(bids[1]).Times(1) + mockBibliophile.EXPECT().GetNextBidPrice(ammAddress, bids[1]).Return(bids[2]).Times(1) + mockBibliophile.EXPECT().GetNextBidPrice(ammAddress, bids[2]).Return(bids[3]).Times(1) + mockBibliophile.EXPECT().GetBidSize(ammAddress, bids[0]).Return(size).Times(1) + mockBibliophile.EXPECT().GetBidSize(ammAddress, bids[1]).Return(size).Times(1) + mockBibliophile.EXPECT().GetBidSize(ammAddress, bids[2]).Return(size).Times(1) + mockBibliophile.EXPECT().GetBidSize(ammAddress, bids[3]).Return(size).Times(1) + + output := _sampleBid(mockBibliophile, ammAddress, baseAssetQuantity) + accBaseQ := hu.Mul(size, big.NewInt(3)) + accNotional := big.NewInt(0) + for i := 0; i < 3; i++ { + accNotional.Add(accNotional, (hu.Div1e6(hu.Mul(bids[i], size)))) + } + notionalAtTick := hu.Div1e6(hu.Mul(hu.Sub(baseAssetQuantity, accBaseQ), bids[3])) + expectedOutput := hu.Div(hu.Mul1e6(hu.Add(accNotional, notionalAtTick)), baseAssetQuantity) + assert.Equal(t, expectedOutput, output) + }) + }) + }) +} + +func TestSampleAsk(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockBibliophile := b.NewMockBibliophileClient(ctrl) + ammAddress := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8") + asksHead := big.NewInt(20 * 1e6) // $20 + baseAssetQuantity := big.NewInt(1e18) // 1 ether + t.Run("when asksHead is 0", func(t *testing.T) { + mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(big.NewInt(0)).Times(1) + output := _sampleAsk(mockBibliophile, ammAddress, baseAssetQuantity) + assert.Equal(t, big.NewInt(0), output) + }) + t.Run("when asksHead > 0", func(t *testing.T) { + t.Run("when asks in orderbook are not enough to cover baseAssetQuantity", func(t *testing.T) { + t.Run("when there is only one ask in orderbook it returns 0", func(t *testing.T) { + mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) + mockBibliophile.EXPECT().GetAskSize(ammAddress, asksHead).Return(hu.Sub(baseAssetQuantity, big.NewInt(1))).Times(1) + mockBibliophile.EXPECT().GetNextAskPrice(ammAddress, asksHead).Return(big.NewInt(0)).Times(1) + output := _sampleAsk(mockBibliophile, ammAddress, baseAssetQuantity) + assert.Equal(t, big.NewInt(0), output) + }) + t.Run("when there are multiple asks, it tries to fill with available asks", func(t *testing.T) { + asks := []*big.Int{asksHead, big.NewInt(2100000), big.NewInt(2200000), big.NewInt(2300000)} + size := big.NewInt(24 * 1e16) // 0.24 ether + mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) + for i := 0; i < len(asks); i++ { + mockBibliophile.EXPECT().GetAskSize(ammAddress, asks[i]).Return(size).Times(1) + if i != len(asks)-1 { + mockBibliophile.EXPECT().GetNextAskPrice(ammAddress, asks[i]).Return(asks[i+1]).Times(1) + } else { + mockBibliophile.EXPECT().GetNextAskPrice(ammAddress, asks[i]).Return(big.NewInt(0)).Times(1) + } + } + output := _sampleAsk(mockBibliophile, ammAddress, baseAssetQuantity) + assert.Equal(t, big.NewInt(0), output) + }) + }) + t.Run("when asks in orderbook are enough to cover baseAssetQuantity", func(t *testing.T) { + t.Run("when there is only one ask in orderbook it returns asksHead", func(t *testing.T) { + mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) + mockBibliophile.EXPECT().GetAskSize(ammAddress, asksHead).Return(baseAssetQuantity).Times(1) + output := _sampleAsk(mockBibliophile, ammAddress, baseAssetQuantity) + assert.Equal(t, asksHead, output) + }) + t.Run("when there are multiple asks, it tries to fill with available asks and average price is returned for rest", func(t *testing.T) { + asks := []*big.Int{asksHead} + for i := int64(1); i < 6; i++ { + asks = append(asks, hu.Sub(asksHead, big.NewInt(i))) + } + size := big.NewInt(31e16) // 0.31 ether + mockBibliophile.EXPECT().GetAsksHead(ammAddress).Return(asksHead).Times(1) + mockBibliophile.EXPECT().GetNextAskPrice(ammAddress, asks[0]).Return(asks[1]).Times(1) + mockBibliophile.EXPECT().GetNextAskPrice(ammAddress, asks[1]).Return(asks[2]).Times(1) + mockBibliophile.EXPECT().GetNextAskPrice(ammAddress, asks[2]).Return(asks[3]).Times(1) + mockBibliophile.EXPECT().GetAskSize(ammAddress, asks[0]).Return(size).Times(1) + mockBibliophile.EXPECT().GetAskSize(ammAddress, asks[1]).Return(size).Times(1) + mockBibliophile.EXPECT().GetAskSize(ammAddress, asks[2]).Return(size).Times(1) + mockBibliophile.EXPECT().GetAskSize(ammAddress, asks[3]).Return(size).Times(1) + + output := _sampleAsk(mockBibliophile, ammAddress, baseAssetQuantity) + accBaseQ := hu.Mul(size, big.NewInt(3)) + accNotional := big.NewInt(0) + for i := 0; i < 3; i++ { + accNotional.Add(accNotional, (hu.Div1e6(hu.Mul(asks[i], size)))) + } + notionalAtTick := hu.Div1e6(hu.Mul(hu.Sub(baseAssetQuantity, accBaseQ), asks[3])) + expectedOutput := hu.Div(hu.Mul1e6(hu.Add(accNotional, notionalAtTick)), baseAssetQuantity) + assert.Equal(t, expectedOutput, output) + }) + }) + }) +} diff --git a/tests/orderbook/abi/AMM.json b/tests/orderbook/abi/AMM.json index c826447fb4..879faa24dd 100644 --- a/tests/orderbook/abi/AMM.json +++ b/tests/orderbook/abi/AMM.json @@ -1,1135 +1,1094 @@ [ - { - "inputs": [ - { - "internalType": "address", - "name": "_clearingHouse", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "asks", - "outputs": [ - { - "internalType": "uint256", - "name": "nextTick", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "asksHead", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "bids", - "outputs": [ - { - "internalType": "uint256", - "name": "nextTick", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "bidsHead", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "clearingHouse", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "cumulativePremiumFraction", - "outputs": [ - { - "internalType": "int256", - "name": "", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "fundingPeriod", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getAcceptableBounds", - "outputs": [ - { - "internalType": "uint256", - "name": "upperBound", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "lowerBound", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getAcceptableBoundsForLiquidation", - "outputs": [ - { - "internalType": "uint256", - "name": "upperBound", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "lowerBound", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - } - ], - "name": "getNotionalPositionAndUnrealizedPnl", - "outputs": [ - { - "internalType": "uint256", - "name": "notionalPosition", - "type": "uint256" - }, - { - "internalType": "int256", - "name": "unrealizedPnl", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "int256", - "name": "positionSize", - "type": "int256" - }, - { - "internalType": "uint256", - "name": "openNotional", - "type": "uint256" - }, - { - "internalType": "int256", - "name": "unrealizedPnl", - "type": "int256" - }, - { - "internalType": "int256", - "name": "baseAssetQuantity", - "type": "int256" - } - ], - "name": "getOpenNotionalWhileReducingPosition", - "outputs": [ - { - "internalType": "uint256", - "name": "remainOpenNotional", - "type": "uint256" - }, - { - "internalType": "int256", - "name": "realizedPnl", - "type": "int256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "internalType": "int256", - "name": "margin", - "type": "int256" - }, - { - "internalType": "enum IClearingHouse.Mode", - "name": "mode", - "type": "uint8" - } - ], - "name": "getOptimalPnl", - "outputs": [ - { - "internalType": "uint256", - "name": "notionalPosition", - "type": "uint256" - }, - { - "internalType": "int256", - "name": "unrealizedPnl", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - } - ], - "name": "getPendingFundingPayment", - "outputs": [ - { - "internalType": "int256", - "name": "takerFundingPayment", - "type": "int256" - }, - { - "internalType": "int256", - "name": "latestCumulativePremiumFraction", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "price", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "openNotional", - "type": "uint256" - }, - { - "internalType": "int256", - "name": "size", - "type": "int256" - }, - { - "internalType": "int256", - "name": "margin", - "type": "int256" - } - ], - "name": "getPositionMetadata", - "outputs": [ - { - "internalType": "uint256", - "name": "notionalPos", - "type": "uint256" - }, - { - "internalType": "int256", - "name": "uPnl", - "type": "int256" - }, - { - "internalType": "int256", - "name": "marginFraction", - "type": "int256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "getUnderlyingPrice", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "governance", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "impactMarginNotional", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_name", - "type": "string" - }, - { - "internalType": "address", - "name": "_underlyingAsset", - "type": "address" - }, - { - "internalType": "address", - "name": "_oracle", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_minSizeRequirement", - "type": "uint256" - }, - { - "internalType": "address", - "name": "_governance", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_pricePrecision", - "type": "uint256" - }, - { - "internalType": "address", - "name": "_ticks", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "interestRate", - "outputs": [ - { - "internalType": "int256", - "name": "", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "lastPrice", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "lastTradePrice", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "internalType": "uint256", - "name": "price", - "type": "uint256" - }, - { - "internalType": "int256", - "name": "fillAmount", - "type": "int256" - } - ], - "name": "liquidatePosition", - "outputs": [ - { - "internalType": "int256", - "name": "realizedPnl", - "type": "int256" - }, - { - "internalType": "uint256", - "name": "quoteAsset", - "type": "uint256" - }, - { - "internalType": "int256", - "name": "size", - "type": "int256" - }, - { - "internalType": "uint256", - "name": "openNotional", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "longOpenInterestNotional", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "maxFundingRate", - "outputs": [ - { - "internalType": "int256", - "name": "", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "maxLiquidationPriceSpread", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "maxLiquidationRatio", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "maxOracleSpreadRatio", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "minSizeRequirement", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "multiplier", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "nextFundingTime", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "openInterestNotional", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "internalType": "int256", - "name": "fillAmount", - "type": "int256" - }, - { - "internalType": "uint256", - "name": "fulfillPrice", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "is2ndTrade", - "type": "bool" - } - ], - "name": "openPosition", - "outputs": [ - { - "internalType": "int256", - "name": "realizedPnl", - "type": "int256" - }, - { - "internalType": "bool", - "name": "isPositionIncreased", - "type": "bool" - }, - { - "internalType": "int256", - "name": "size", - "type": "int256" - }, - { - "internalType": "uint256", - "name": "openNotional", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "openInterest", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "oracle", - "outputs": [ - { - "internalType": "contract IOracle", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "piData", - "outputs": [ - { - "internalType": "int256", - "name": "piTwap", - "type": "int256" - }, - { - "internalType": "uint256", - "name": "accTime", - "type": "uint256" - }, - { - "internalType": "int256", - "name": "piLast", - "type": "int256" - }, - { - "internalType": "uint256", - "name": "lastTS", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "positions", - "outputs": [ - { - "internalType": "int256", - "name": "size", - "type": "int256" - }, - { - "internalType": "uint256", - "name": "openNotional", - "type": "uint256" - }, - { - "internalType": "int256", - "name": "lastPremiumFraction", - "type": "int256" - }, - { - "internalType": "uint256", - "name": "liquidationThreshold", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "redStoneAdapter", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "redStoneFeedId", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "samplePI", - "outputs": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - }, - { - "internalType": "int256", - "name": "premiumIndex", - "type": "int256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_fundingPeriod", - "type": "uint256" - }, - { - "internalType": "int256", - "name": "_maxFundingRate", - "type": "int256" - } - ], - "name": "setFundingParams", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "__governance", - "type": "address" - } - ], - "name": "setGovernace", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_impactMarginNotional", - "type": "uint256" - } - ], - "name": "setImpactMarginNotional", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "int256", - "name": "_interestRate", - "type": "int256" - } - ], - "name": "setInterestRate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_maxLiquidationRatio", - "type": "uint256" - } - ], - "name": "setLiquidationSizeRatio", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_minSizeRequirement", - "type": "uint256" - } - ], - "name": "setMinSizeRequirement", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_oracle", - "type": "address" - }, - { - "internalType": "address", - "name": "_redStoneAdapter", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "_redStoneFeedId", - "type": "bytes32" - } - ], - "name": "setOracleConfig", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_pricePrecision", - "type": "uint256" - } - ], - "name": "setPricePrecision", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_maxOracleSpreadRatio", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_maxLiquidationPriceSpread", - "type": "uint256" - } - ], - "name": "setPriceSpreadParams", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_redStoneAdapter", - "type": "address" - } - ], - "name": "setRedStoneAdapterAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_ticks", - "type": "address" - } - ], - "name": "setTicks", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "settleFunding", - "outputs": [ - { - "internalType": "int256", - "name": "premiumFraction", - "type": "int256" - }, - { - "internalType": "int256", - "name": "underlyingPrice", - "type": "int256" - }, - { - "internalType": "int256", - "name": "", - "type": "int256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "shortOpenInterestNotional", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tick", - "type": "uint256" - }, - { - "internalType": "int256", - "name": "quantity", - "type": "int256" - } - ], - "name": "signalAddLiquidity", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tick", - "type": "uint256" - }, - { - "internalType": "int256", - "name": "quantity", - "type": "int256" - } - ], - "name": "signalRemoveLiquidity", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "startFunding", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "ticks", - "outputs": [ - { - "internalType": "contract ITicks", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_orderBook", - "type": "address" - } - ], - "name": "toggleWhitelistedOrderBook", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "underlyingAsset", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - } - ], - "name": "updatePosition", - "outputs": [ - { - "internalType": "bool", - "name": "isUpdated", - "type": "bool" - }, - { - "internalType": "int256", - "name": "fundingPayment", - "type": "int256" - }, - { - "internalType": "int256", - "name": "latestCumulativePremiumFraction", - "type": "int256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "whitelistedOrderBooks", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - } + { + "inputs": [ + { + "internalType": "address", + "name": "_clearingHouse", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "asks", + "outputs": [ + { + "internalType": "uint256", + "name": "nextTick", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "asksHead", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "bids", + "outputs": [ + { + "internalType": "uint256", + "name": "nextTick", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bidsHead", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "clearingHouse", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cumulativePremiumFraction", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fundingPeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAcceptableBounds", + "outputs": [ + { + "internalType": "uint256", + "name": "upperBound", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lowerBound", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAcceptableBoundsForLiquidation", + "outputs": [ + { + "internalType": "uint256", + "name": "upperBound", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lowerBound", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + } + ], + "name": "getNotionalPositionAndUnrealizedPnl", + "outputs": [ + { + "internalType": "uint256", + "name": "notionalPosition", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "unrealizedPnl", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "positionSize", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "openNotional", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "unrealizedPnl", + "type": "int256" + }, + { + "internalType": "int256", + "name": "baseAssetQuantity", + "type": "int256" + } + ], + "name": "getOpenNotionalWhileReducingPosition", + "outputs": [ + { + "internalType": "uint256", + "name": "remainOpenNotional", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "realizedPnl", + "type": "int256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "int256", + "name": "margin", + "type": "int256" + }, + { + "internalType": "enum IClearingHouse.Mode", + "name": "mode", + "type": "uint8" + } + ], + "name": "getOptimalPnl", + "outputs": [ + { + "internalType": "uint256", + "name": "notionalPosition", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "unrealizedPnl", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + } + ], + "name": "getPendingFundingPayment", + "outputs": [ + { + "internalType": "int256", + "name": "takerFundingPayment", + "type": "int256" + }, + { + "internalType": "int256", + "name": "latestCumulativePremiumFraction", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "openNotional", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "size", + "type": "int256" + }, + { + "internalType": "int256", + "name": "margin", + "type": "int256" + } + ], + "name": "getPositionMetadata", + "outputs": [ + { + "internalType": "uint256", + "name": "notionalPos", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "uPnl", + "type": "int256" + }, + { + "internalType": "int256", + "name": "marginFraction", + "type": "int256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getUnderlyingPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governance", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "impactMarginNotional", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "address", + "name": "_underlyingAsset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_minSizeRequirement", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_governance", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_pricePrecision", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_ticks", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "interestRate", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "fillAmount", + "type": "int256" + } + ], + "name": "liquidatePosition", + "outputs": [ + { + "internalType": "int256", + "name": "realizedPnl", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "quoteAsset", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "size", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "openNotional", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "longOpenInterestNotional", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "marginAccount", + "outputs": [ + { + "internalType": "contract IMarginAccount", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxFundingRate", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLiquidationPriceSpread", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLiquidationRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxOracleSpreadRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "midPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minSizeRequirement", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "multiplier", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nextFundingTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "openInterestNotional", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "int256", + "name": "fillAmount", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "fulfillPrice", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "is2ndTrade", + "type": "bool" + } + ], + "name": "openPosition", + "outputs": [ + { + "internalType": "int256", + "name": "realizedPnl", + "type": "int256" + }, + { + "internalType": "bool", + "name": "isPositionIncreased", + "type": "bool" + }, + { + "internalType": "int256", + "name": "size", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "openNotional", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "openInterest", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "oracle", + "outputs": [ + { + "internalType": "contract IOracle", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "piData", + "outputs": [ + { + "internalType": "int256", + "name": "piTwap", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "accTime", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "piLast", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "lastTS", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "positions", + "outputs": [ + { + "internalType": "int256", + "name": "size", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "openNotional", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "lastPremiumFraction", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "liquidationThreshold", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "samplePI", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "int256", + "name": "premiumIndex", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_fundingPeriod", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "_maxFundingRate", + "type": "int256" + } + ], + "name": "setFundingParams", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "__governance", + "type": "address" + } + ], + "name": "setGovernace", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_impactMarginNotional", + "type": "uint256" + } + ], + "name": "setImpactMarginNotional", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "_interestRate", + "type": "int256" + } + ], + "name": "setInterestRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_maxLiquidationRatio", + "type": "uint256" + } + ], + "name": "setLiquidationSizeRatio", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_minSizeRequirement", + "type": "uint256" + } + ], + "name": "setMinSizeRequirement", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pricePrecision", + "type": "uint256" + } + ], + "name": "setPricePrecision", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_maxOracleSpreadRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxLiquidationPriceSpread", + "type": "uint256" + } + ], + "name": "setPriceSpreadParams", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_ticks", + "type": "address" + } + ], + "name": "setTicks", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "settleFunding", + "outputs": [ + { + "internalType": "int256", + "name": "premiumFraction", + "type": "int256" + }, + { + "internalType": "int256", + "name": "underlyingPrice", + "type": "int256" + }, + { + "internalType": "int256", + "name": "", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "shortOpenInterestNotional", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tick", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "quantity", + "type": "int256" + } + ], + "name": "signalAddLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tick", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "quantity", + "type": "int256" + } + ], + "name": "signalRemoveLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "startFunding", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "ticks", + "outputs": [ + { + "internalType": "contract ITicks", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_orderBook", + "type": "address" + } + ], + "name": "toggleWhitelistedOrderBook", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "underlyingAsset", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + } + ], + "name": "updatePosition", + "outputs": [ + { + "internalType": "bool", + "name": "isUpdated", + "type": "bool" + }, + { + "internalType": "int256", + "name": "fundingPayment", + "type": "int256" + }, + { + "internalType": "int256", + "name": "latestCumulativePremiumFraction", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "whitelistedOrderBooks", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } ] diff --git a/tests/orderbook/abi/MarginAccount.json b/tests/orderbook/abi/MarginAccount.json index 7a8838bcdf..deda936e25 100644 --- a/tests/orderbook/abi/MarginAccount.json +++ b/tests/orderbook/abi/MarginAccount.json @@ -1,1093 +1,1119 @@ [ - { - "inputs": [ - { - "internalType": "address", - "name": "_trustedForwarder", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { - "internalType": "enum IMarginAccount.LiquidationStatus", - "name": "", - "type": "uint8" - } - ], - "name": "NOT_LIQUIDATABLE", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "idx", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "seizeAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "repayAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - } - ], - "name": "MarginAccountLiquidated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "idx", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - } - ], - "name": "MarginAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "MarginReleased", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "idx", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - } - ], - "name": "MarginRemoved", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "MarginReserved", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "Paused", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "indexed": false, - "internalType": "int256", - "name": "realizedPnl", - "type": "int256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - } - ], - "name": "PnLRealized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "seized", - "type": "uint256[]" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "repayAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - } - ], - "name": "SettledBadDebt", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "Unpaused", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "idx", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "addMargin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "idx", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - } - ], - "name": "addMarginFor", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "idx", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_weight", - "type": "uint256" - } - ], - "name": "changeCollateralWeight", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "clearingHouse", - "outputs": [ - { - "internalType": "contract IClearingHouse", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "credit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - } - ], - "name": "getAvailableMargin", - "outputs": [ - { - "internalType": "int256", - "name": "availableMargin", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "idx", - "type": "uint256" - } - ], - "name": "getCollateralToken", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - } - ], - "name": "getNormalizedMargin", - "outputs": [ - { - "internalType": "int256", - "name": "weighted", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - } - ], - "name": "getSpotCollateralValue", - "outputs": [ - { - "internalType": "int256", - "name": "spot", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "governance", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_governance", - "type": "address" - }, - { - "internalType": "address", - "name": "_vusd", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "insuranceFund", - "outputs": [ - { - "internalType": "contract IInsuranceFund", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "internalType": "bool", - "name": "includeFunding", - "type": "bool" - } - ], - "name": "isLiquidatable", - "outputs": [ - { - "internalType": "enum IMarginAccount.LiquidationStatus", - "name": "_isLiquidatable", - "type": "uint8" - }, - { - "internalType": "uint256", - "name": "repayAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "incentivePerDollar", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "forwarder", - "type": "address" - } - ], - "name": "isTrustedForwarder", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "juror", - "outputs": [ - { - "internalType": "contract IJuror", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "internalType": "uint256", - "name": "repay", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "idx", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minSeizeAmount", - "type": "uint256" - } - ], - "name": "liquidateExactRepay", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "internalType": "uint256", - "name": "maxRepay", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "idx", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "seize", - "type": "uint256" - } - ], - "name": "liquidateExactSeize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "internalType": "uint256", - "name": "maxRepay", - "type": "uint256" - }, - { - "internalType": "uint256[]", - "name": "idxs", - "type": "uint256[]" - } - ], - "name": "liquidateFlexible", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "liquidationIncentive", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "margin", - "outputs": [ - { - "internalType": "int256", - "name": "", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "marginAccountHelper", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "minAllowableMargin", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "oracle", - "outputs": [ - { - "internalType": "contract IOracle", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "paused", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "internalType": "int256", - "name": "realizedPnl", - "type": "int256" - } - ], - "name": "realizePnL", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "releaseMargin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "idx", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "removeMargin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "idx", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "trader", - "type": "address" - } - ], - "name": "removeMarginFor", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "reserveMargin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "reservedMargin", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "__governance", - "type": "address" - } - ], - "name": "setGovernace", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_juror", - "type": "address" - } - ], - "name": "setJuror", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - } - ], - "name": "settleBadDebt", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "supportedAssets", - "outputs": [ - { - "components": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "weight", - "type": "uint256" - }, - { - "internalType": "uint8", - "name": "decimals", - "type": "uint8" - } - ], - "internalType": "struct IMarginAccount.Collateral[]", - "name": "", - "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "supportedAssetsLen", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "supportedCollateral", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "weight", - "type": "uint256" - }, - { - "internalType": "uint8", - "name": "decimals", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_registry", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_liquidationIncentive", - "type": "uint256" - } - ], - "name": "syncDeps", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_settler", - "type": "address" - } - ], - "name": "toggleTrustedSettler", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_orderBook", - "type": "address" - } - ], - "name": "toggleWhitelistedOrderBook", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferOutVusd", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "trustedSettlers", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "unpause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_minAllowableMargin", - "type": "uint256" - } - ], - "name": "updateParams", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "vusd", - "outputs": [ - { - "internalType": "contract IERC20FlexibleSupply", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "trader", - "type": "address" - } - ], - "name": "weightedAndSpotCollateral", - "outputs": [ - { - "internalType": "int256", - "name": "weighted", - "type": "int256" - }, - { - "internalType": "int256", - "name": "spot", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_coin", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_weight", - "type": "uint256" - } - ], - "name": "whitelistCollateral", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "whitelistedOrderBooks", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "stateMutability": "payable", - "type": "receive" - } + { + "inputs": [ + { + "internalType": "address", + "name": "_trustedForwarder", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "enum IMarginAccount.LiquidationStatus", + "name": "", + "type": "uint8" + } + ], + "name": "NOT_LIQUIDATABLE", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "idx", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "seizeAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "MarginAccountLiquidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "idx", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "MarginAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "MarginReleased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "idx", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "MarginRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "MarginReserved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "realizedPnl", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "PnLRealized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "seized", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "SettledBadDebt", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "idx", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "addMargin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "idx", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "addMarginFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "idx", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_weight", + "type": "uint256" + } + ], + "name": "changeCollateralWeight", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "clearingHouse", + "outputs": [ + { + "internalType": "contract IClearingHouse", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "credit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + } + ], + "name": "getAvailableMargin", + "outputs": [ + { + "internalType": "int256", + "name": "availableMargin", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "idx", + "type": "uint256" + } + ], + "name": "getCollateralToken", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + } + ], + "name": "getNormalizedMargin", + "outputs": [ + { + "internalType": "int256", + "name": "weighted", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + } + ], + "name": "getSpotCollateralValue", + "outputs": [ + { + "internalType": "int256", + "name": "spot", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governance", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_governance", + "type": "address" + }, + { + "internalType": "address", + "name": "_vusd", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "insuranceFund", + "outputs": [ + { + "internalType": "contract IInsuranceFund", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "bool", + "name": "includeFunding", + "type": "bool" + } + ], + "name": "isLiquidatable", + "outputs": [ + { + "internalType": "enum IMarginAccount.LiquidationStatus", + "name": "_isLiquidatable", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "incentivePerDollar", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "forwarder", + "type": "address" + } + ], + "name": "isTrustedForwarder", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "juror", + "outputs": [ + { + "internalType": "contract IJuror", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repay", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "idx", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minSeizeAmount", + "type": "uint256" + } + ], + "name": "liquidateExactRepay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "maxRepay", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "idx", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "seize", + "type": "uint256" + } + ], + "name": "liquidateExactSeize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "maxRepay", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "idxs", + "type": "uint256[]" + } + ], + "name": "liquidateFlexible", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "liquidationIncentive", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "margin", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "marginAccountHelper", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minAllowableMargin", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracle", + "outputs": [ + { + "internalType": "contract IOracle", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "int256", + "name": "realizedPnl", + "type": "int256" + } + ], + "name": "realizePnL", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "releaseMargin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "idx", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "removeMargin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "idx", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "trader", + "type": "address" + } + ], + "name": "removeMarginFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "reserveMargin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "reservedMargin", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "__governance", + "type": "address" + } + ], + "name": "setGovernace", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_juror", + "type": "address" + } + ], + "name": "setJuror", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IOracle", + "name": "_oracle", + "type": "address" + } + ], + "name": "setOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + } + ], + "name": "settleBadDebt", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "supportedAssets", + "outputs": [ + { + "components": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "weight", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "decimals", + "type": "uint8" + } + ], + "internalType": "struct IMarginAccount.Collateral[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "supportedAssetsLen", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "supportedCollateral", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "weight", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "decimals", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registry", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_liquidationIncentive", + "type": "uint256" + } + ], + "name": "syncDeps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_settler", + "type": "address" + } + ], + "name": "toggleTrustedSettler", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_orderBook", + "type": "address" + } + ], + "name": "toggleWhitelistedOrderBook", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferOutVusd", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "trustedSettlers", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_minAllowableMargin", + "type": "uint256" + } + ], + "name": "updateParams", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vusd", + "outputs": [ + { + "internalType": "contract IERC20FlexibleSupply", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "trader", + "type": "address" + } + ], + "name": "weightedAndSpotCollateral", + "outputs": [ + { + "internalType": "int256", + "name": "weighted", + "type": "int256" + }, + { + "internalType": "int256", + "name": "spot", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_coin", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_weight", + "type": "uint256" + } + ], + "name": "whitelistCollateral", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "whitelistedOrderBooks", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } ] diff --git a/tests/orderbook/abi/Oracle.json b/tests/orderbook/abi/Oracle.json index 42f91a8337..f51ba1e0e9 100644 --- a/tests/orderbook/abi/Oracle.json +++ b/tests/orderbook/abi/Oracle.json @@ -1,142 +1,146 @@ [ - { - "inputs": [ - { - "internalType": "address", - "name": "_governance", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "chainLinkAggregatorMap", - "outputs": [ - { - "internalType": "address", - "name": "aggregator", - "type": "address" - }, - { - "internalType": "uint256", - "name": "heartbeat", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "underlying", - "type": "address" - } - ], - "name": "getUnderlyingPrice", - "outputs": [ - { - "internalType": "int256", - "name": "answer", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "governance", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "underlying", - "type": "address" - }, - { - "internalType": "address", - "name": "aggregator", - "type": "address" - }, - { - "internalType": "uint256", - "name": "hearbeat", - "type": "uint256" - } - ], - "name": "setAggregator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "__governance", - "type": "address" - } - ], - "name": "setGovernace", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "underlying", - "type": "address" - }, - { - "internalType": "uint256", - "name": "price", - "type": "uint256" - } - ], - "name": "setStablePrice", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "stablePrice", - "outputs": [ - { - "internalType": "int256", - "name": "", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - } + { + "inputs": [ + { + "internalType": "address", + "name": "_governance", + "type": "address" + }, + { + "internalType": "address", + "name": "_redStoneAdapter", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "aggregatorMap", + "outputs": [ + { + "internalType": "bytes32", + "name": "redstoneFeedId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "aggregator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "heartbeat", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "underlying", + "type": "address" + } + ], + "name": "getUnderlyingPrice", + "outputs": [ + { + "internalType": "int256", + "name": "answer", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governance", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "redStoneAdapter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "underlying", + "type": "address" + }, + { + "internalType": "address", + "name": "aggregator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "heartbeat", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "redstoneFeedId", + "type": "bytes32" + } + ], + "name": "setAggregator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "__governance", + "type": "address" + } + ], + "name": "setGovernace", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_redStoneAdapter", + "type": "address" + } + ], + "name": "setRedStoneAdapterAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } ] diff --git a/tests/orderbook/bibliophile/variablesReadFromSlotTests.js b/tests/orderbook/bibliophile/variablesReadFromSlotTests.js index dac41053c1..79a43c226c 100644 --- a/tests/orderbook/bibliophile/variablesReadFromSlotTests.js +++ b/tests/orderbook/bibliophile/variablesReadFromSlotTests.js @@ -127,7 +127,7 @@ describe('Testing variables read from slots by precompile', function () { }) context("AMM contract variables", function () { // vars read from slot - // positions, cumulativePremiumFraction, maxOracleSpreadRatio, maxLiquidationRatio, minSizeRequirement, oracle, underlyingAsset, + // positions, cumulativePremiumFraction, maxOracleSpreadRatio, maxLiquidationRatio, minSizeRequirement, oracle, underlyingAsset, // maxLiquidationPriceSpread, redStoneAdapter, redStoneFeedId, impactMarginNotional, lastTradePrice, bids, asks, bidsHead, asksHead let ammIndex = 0 let method ="testing_getAMMVars" @@ -163,28 +163,28 @@ describe('Testing variables read from slots by precompile', function () { context("when variables dont have default value after setup", async function () { // positions, cumulativePremiumFraction, redStoneAdapter, redStoneFeedId, impactMarginNotional, lastTradePrice, bids, asks, bidsHead, asksHead context("variables which need set config before reading", async function () { - // redStoneAdapter, redStoneFeedId, impactMarginNotional - // impactMarginNotional - let amm, oracleAddress, redStoneAdapterAddress, redStoneFeedId, impactMarginNotional + let amm, oracleAddress, redStoneAdapterAddress, impactMarginNotional this.beforeAll(async function () { amm = await getAMMContract(ammIndex) oracleAddress = await amm.oracle() - redStoneAdapterAddress = await amm.redStoneAdapter() - redStoneFeedId = await amm.redStoneFeedId() + oracle = new ethers.Contract(oracleAddress, require("../abi/Oracle.json"), provider); + marginAccount = new ethers.Contract(await amm.marginAccount(), require("../abi/MarginAccount.json"), provider); + + redStoneAdapterAddress = await oracle.redStoneAdapter() + console.log("redStoneAdapterAddress", redStoneAdapterAddress) impactMarginNotional = await amm.impactMarginNotional() }) this.afterAll(async function () { - await amm.connect(governance).setOracleConfig(oracleAddress, redStoneAdapterAddress, redStoneFeedId) + await oracle.connect(governance).setRedStoneAdapterAddress(redStoneAdapterAddress) + await marginAccount.connect(governance).setOracle(oracleAddress) await amm.connect(governance).setImpactMarginNotional(impactMarginNotional) }) it("should read the correct value from contracts", async function () { newOracleAddress = alice.address newRedStoneAdapterAddress = bob.address - newRedStoneFeedId = ethers.utils.formatBytes32String("redStoneFeedId") - tx = await amm.connect(governance).setOracleConfig(newOracleAddress, newRedStoneAdapterAddress, newRedStoneFeedId) - await tx.wait() - newImpactMarginNotional = BigNumber.from(100000) + + tx = await oracle.connect(governance).setRedStoneAdapterAddress(newRedStoneAdapterAddress) tx = await amm.connect(governance).setImpactMarginNotional(newImpactMarginNotional) await tx.wait() @@ -192,9 +192,16 @@ describe('Testing variables read from slots by precompile', function () { response = await makehttpCall(method, params) result = response.body.result - expect(result.oracle_address.toLowerCase()).to.equal(newOracleAddress.toLowerCase()) expect(result.red_stone_adapter_address.toLowerCase()).to.equal(newRedStoneAdapterAddress.toLowerCase()) - expect(result.red_stone_feed_id).to.equal(newRedStoneFeedId) + expect(result.impact_margin_notional).to.equal(newImpactMarginNotional.toNumber()) + + // setOracle + tx = await marginAccount.connect(governance).setOracle(newOracleAddress) + await tx.wait() + response = await makehttpCall(method, params) + result = response.body.result + expect(result.oracle_address.toLowerCase()).to.equal(newOracleAddress.toLowerCase()) + expect(result.red_stone_adapter_address.toLowerCase()).to.equal('0x' + '0'.repeat(40)) // red stone adapter should be zero in new oracle expect(result.impact_margin_notional).to.equal(newImpactMarginNotional.toNumber()) }) }) @@ -287,7 +294,7 @@ describe('Testing variables read from slots by precompile', function () { //ioc expiration cap it("should read the correct value from contracts", async function () { params = [ "0xe97a0702264091714ea19b481c1fd12d9686cb4602efbfbec41ec5ea5410da84"] - + result = (await makehttpCall(method, params)).body.result actualExpirationCap = await ioc.expirationCap() expect(result.ioc_expiration_cap).to.eq(actualExpirationCap.toNumber()) @@ -319,7 +326,7 @@ describe('Testing variables read from slots by precompile', function () { longIOCOrder = getIOCOrder(expireAt, market, charlie.address, longOrderBaseAssetQuantity, orderPrice, getRandomSalt(), false) orderHash = await ioc.getOrderHash(longIOCOrder) params = [ orderHash ] - txDetails = await placeIOCOrder(longIOCOrder, charlie) + txDetails = await placeIOCOrder(longIOCOrder, charlie) result = (await makehttpCall(method, params)).body.result //cleanup diff --git a/tests/orderbook/juror/JurorTests.js b/tests/orderbook/juror/JurorTests.js index 37d01eb2a8..22d319e7d8 100644 --- a/tests/orderbook/juror/JurorTests.js +++ b/tests/orderbook/juror/JurorTests.js @@ -38,7 +38,7 @@ describe("Juror tests", async function() { let longOrderBaseAssetQuantity = multiplySize(0.1) // 0.1 ether let orderPrice = multiplyPrice(1800) let market = BigNumber.from(0) - let longOrder = getOrderV2(market, alice.address, longOrderBaseAssetQuantity, orderPrice, getRandomSalt()) + let longOrder = getOrderV2(market, alice.address, longOrderBaseAssetQuantity, orderPrice, getRandomSalt()) it("should fail as trader has not margin", async function() { await removeAllAvailableMargin(alice) @@ -132,7 +132,7 @@ describe("Juror tests", async function() { let shortOrderBaseAssetQuantity = multiplySize(-0.1) // 0.1 ether let orderPrice = multiplyPrice(1800) let market = BigNumber.from(0) - let shortOrder = getOrderV2(market, bob.address, shortOrderBaseAssetQuantity, orderPrice, getRandomSalt()) + let shortOrder = getOrderV2(market, bob.address, shortOrderBaseAssetQuantity, orderPrice, getRandomSalt()) let tradingAuthority = charlie it("should fail as trader has no margin", async function() { @@ -202,7 +202,7 @@ describe("Juror tests", async function() { expect(output.orderHash).to.equal(expectedOrderHash) expect(output.res.unfilledAmount.toString()).to.equal(shortOrder.baseAssetQuantity.toString()) expect(output.res.amm).to.equal(await clearingHouse.amms(market)) - + await cancelOrderFromLimitOrderV2(shortOrder, tradingAuthority) orderStatus = await limitOrderBook.orderStatus(expectedOrderHash) expect(orderStatus.status).to.equal(3) @@ -408,7 +408,7 @@ describe("Juror tests", async function() { totalRequiredMarginForShortOrder3 = await getRequiredMarginForShortOrder(shortOrder3) expect(output.res.reserveAmount.toNumber()).to.equal(totalRequiredMarginForShortOrder3.toNumber()) expectedAmmAddress = await clearingHouse.amms(market) - + // place the order output = await placeOrderFromLimitOrderV2(shortOrder3, marketMaker) limitOrderBookLogWithEvent = (await getEventsFromLimitOrderBookTx(output.txReceipt.transactionHash))[0] @@ -469,7 +469,7 @@ describe("Juror tests", async function() { expect(output.orderHash).to.equal(expectedOrderHash) expect(output.res.unfilledAmount.toString()).to.equal(longOrder.baseAssetQuantity.toString()) expect(output.res.amm).to.equal(await clearingHouse.amms(market)) - + await cancelOrderFromLimitOrderV2(longOrder, marketMaker) orderStatus = await limitOrderBook.orderStatus(expectedOrderHash) expect(orderStatus.status).to.equal(3) @@ -563,7 +563,7 @@ describe("Juror tests", async function() { // Alice has long Position and bob has short position // If reduceOnly order is longOrder - it should fail // Alice tries to place a short reduceOnly order when she has an open shortOrder - it should fail - // when there is no open shortOrder for alice and alice tries to place a short reduceOnly order - it should succeed + // when there is no open shortOrder for alice and alice tries to place a short reduceOnly order - it should succeed // after placing short reduceOnly order, alice tries to place a normal shortOrder - it should fail // if currentOrder size + (sum of size of all reduceOnly orders) > posSize of alice - it should fail // if currentOrder size + (sum of size of all reduceOnly orders) < posSize of alice - it should succeed diff --git a/tests/orderbook/tests/test.js b/tests/orderbook/tests/test.js index 9579d36564..93e9e404dc 100644 --- a/tests/orderbook/tests/test.js +++ b/tests/orderbook/tests/test.js @@ -664,7 +664,7 @@ async function setOraclePrice(market, price) { const oracleAddress = await marginAccount.oracle() const oracle = new ethers.Contract(oracleAddress, require('../abi/Oracle.json'), provider); - await oracle.connect(governance).setStablePrice(underlying, price) + await oracle.connect(governance).setUnderlyingPrice(underlying, price) } async function getOraclePrice(market) { @@ -713,4 +713,4 @@ module.exports = { sleep, getOraclePrice, getLastPrice -} \ No newline at end of file +} diff --git a/tests/orderbook/utils.js b/tests/orderbook/utils.js index 774159aba4..f414f6bf32 100644 --- a/tests/orderbook/utils.js +++ b/tests/orderbook/utils.js @@ -18,7 +18,7 @@ const OrderBookContractAddress = "0x0300000000000000000000000000000000000000" const MarginAccountContractAddress = "0x0300000000000000000000000000000000000001" const ClearingHouseContractAddress = "0x0300000000000000000000000000000000000002" const JurorPrecompileAddress = "0x0300000000000000000000000000000000000003" -const TicksPrecompileAddress = "0x0300000000000000000000000000000000000004" +const TicksPrecompileAddress = "0x0300000000000000000000000000000000000004" const LimitOrderBookContractAddress = "0x0300000000000000000000000000000000000005" const IOCContractAddress = "0x0300000000000000000000000000000000000006"