From 2085c56d5287212853ee44e49f37ad8a069ce848 Mon Sep 17 00:00:00 2001 From: Elias Van Ootegem Date: Mon, 2 Sep 2024 16:51:19 +0100 Subject: [PATCH] test: add checks for funding payments and loss socialisation to integration tests Signed-off-by: Elias Van Ootegem --- core/events/loss_socialization.go | 4 + core/integration/features/perpetual.feature | 8 + .../features/settlement/0053-PERP-039.feature | 20 +- core/integration/main_test.go | 7 + .../steps/funding_payment_events.go | 227 ++++++++++++++++++ core/integration/steps/table_wrapper.go | 22 ++ .../steps/the_loss_socialisation_amount_is.go | 19 +- core/integration/stubs/broker_stub.go | 17 ++ 8 files changed, 317 insertions(+), 7 deletions(-) diff --git a/core/events/loss_socialization.go b/core/events/loss_socialization.go index d13aab779e0..597f05aac55 100644 --- a/core/events/loss_socialization.go +++ b/core/events/loss_socialization.go @@ -47,6 +47,10 @@ func NewLossSocializationEvent(ctx context.Context, partyID, marketID string, am } } +func (l LossSoc) LossType() types.LossType { + return l.lType +} + func (l LossSoc) IsFunding() bool { return l.lType == types.LossTypeFunding } diff --git a/core/integration/features/perpetual.feature b/core/integration/features/perpetual.feature index 185b81adfc2..26a52270e30 100644 --- a/core/integration/features/perpetual.feature +++ b/core/integration/features/perpetual.feature @@ -307,6 +307,14 @@ Feature: Simple test creating a perpetual market. | start | end | internal twap | external twap | funding payment | funding rate | | 1575072007 | 1575072014 | 9820000000000000 | 9750000000000000 | 70000000000000 | 0.0071794871794872 | | 1575072014 | | 9890000000000000 | 9720000000000000 | | | + And the following funding payment events should be emitted: + | party | market | amount | + | trader2 | ETH/DEC19 | -100000000 | + | trader2 | ETH/DEC19 | 700000000 | + | trader1 | ETH/DEC19 | 100000000 | + | trader1 | ETH/DEC19 | -700000000 | + | trader3 | ETH/DEC19 | -1400000000 | + | trader4 | ETH/DEC19 | 1400000000 | # payments for trader3 and trader4 should be twice those of trader1 and trader2 And the following transfers should happen: | type | from | to | from account | to account | market id | amount | asset | diff --git a/core/integration/features/settlement/0053-PERP-039.feature b/core/integration/features/settlement/0053-PERP-039.feature index 4eab6eb5617..a78028398b1 100644 --- a/core/integration/features/settlement/0053-PERP-039.feature +++ b/core/integration/features/settlement/0053-PERP-039.feature @@ -108,26 +108,34 @@ Feature: If a market insurance pool does not have enough funds to cover a fundin And the mark price should be "1200" for the market "ETH/DEC19" When time is updated to "2021-08-12T11:04:12Z" - Then system unix time is "1628766252" + And system unix time is "1628766252" - When the oracles broadcast data with block time signed with "0xCAFECAFE1": + And the oracles broadcast data with block time signed with "0xCAFECAFE1": | name | value | time offset | | perp.funding.cue | 1628766252 | 0s | - And the following funding period events should be emitted: + Then the following funding period events should be emitted: | start | end | internal twap | external twap | funding payment | - | 1612998252 | 1628766252 | 1200 | 6200 | -5000 | + | 1612998252 | 1628766252 | 1200 | 6200 | -5000 | # funding payment is 5000000 but party "aux" only has 4793200 # check that loss socialisation has happened and that the insurance pool has been cleared to indicate # that there wasn't enough in there to cover the funding payment hence the winning parties received a haircut + #And debug funding payment events + And the following funding payment events should be emitted: + | party | market | amount | loss amount | loss type | + | party2 | ETH/DEC19 | 5000000 | -68867 | TYPE_FUNDING_PAYMENT | + | party3 | ETH/DEC19 | 5000000 | -68866 | TYPE_FUNDING_PAYMENT | + | aux2 | ETH/DEC19 | 5000000 | -68867 | TYPE_FUNDING_PAYMENT | + | aux | ETH/DEC19 | -5000000 | 206600 | TYPE_FUNDING_PAYMENT | + | party1 | ETH/DEC19 | -10000000 | | | And the following transfers should happen: | from | to | from account | to account | market id | amount | asset | type | | party1 | market | ACCOUNT_TYPE_MARGIN | ACCOUNT_TYPE_SETTLEMENT | ETH/DEC19 | 1008000 | USD | TRANSFER_TYPE_PERPETUALS_FUNDING_LOSS | | party1 | market | ACCOUNT_TYPE_GENERAL | ACCOUNT_TYPE_SETTLEMENT | ETH/DEC19 | 8992000 | USD | TRANSFER_TYPE_PERPETUALS_FUNDING_LOSS | - | aux | market | ACCOUNT_TYPE_MARGIN | ACCOUNT_TYPE_SETTLEMENT | ETH/DEC19 | 648000 | USD | TRANSFER_TYPE_PERPETUALS_FUNDING_LOSS | + | aux | market | ACCOUNT_TYPE_MARGIN | ACCOUNT_TYPE_SETTLEMENT | ETH/DEC19 | 648000 | USD | TRANSFER_TYPE_PERPETUALS_FUNDING_LOSS | | aux | market | ACCOUNT_TYPE_GENERAL | ACCOUNT_TYPE_SETTLEMENT | ETH/DEC19 | 4145200 | USD | TRANSFER_TYPE_PERPETUALS_FUNDING_LOSS | - | market | market | ACCOUNT_TYPE_INSURANCE | ACCOUNT_TYPE_SETTLEMENT | ETH/DEC19 | 200 | USD | TRANSFER_TYPE_PERPETUALS_FUNDING_LOSS | + | market | market | ACCOUNT_TYPE_INSURANCE | ACCOUNT_TYPE_SETTLEMENT | ETH/DEC19 | 200 | USD | TRANSFER_TYPE_PERPETUALS_FUNDING_LOSS | | market | aux2 | ACCOUNT_TYPE_SETTLEMENT | ACCOUNT_TYPE_MARGIN | ETH/DEC19 | 4931133 | USD | TRANSFER_TYPE_PERPETUALS_FUNDING_WIN | | market | party2 | ACCOUNT_TYPE_SETTLEMENT | ACCOUNT_TYPE_MARGIN | ETH/DEC19 | 4931133 | USD | TRANSFER_TYPE_PERPETUALS_FUNDING_WIN | | market | party3 | ACCOUNT_TYPE_SETTLEMENT | ACCOUNT_TYPE_MARGIN | ETH/DEC19 | 4931134 | USD | TRANSFER_TYPE_PERPETUALS_FUNDING_WIN | diff --git a/core/integration/main_test.go b/core/integration/main_test.go index c3cb05de7e5..379cc72e697 100644 --- a/core/integration/main_test.go +++ b/core/integration/main_test.go @@ -698,6 +698,10 @@ func InitializeScenario(s *godog.ScenarioContext) { s.Step(`^debug network parameter "([^"]*)"$`, func(name string) error { return steps.DebugNetworkParameter(execsetup.log, execsetup.netParams, name) }) + s.Step(`^debug funding payment events$`, func() error { + steps.DebugFundingPaymentsEvents(execsetup.broker, execsetup.log) + return nil + }) // Event steps s.Step(`^clear all events$`, func() error { @@ -712,6 +716,9 @@ func InitializeScenario(s *godog.ScenarioContext) { s.Step(`^the following funding period events should be emitted:$`, func(table *godog.Table) error { return steps.TheFollowingFundingPeriodEventsShouldBeEmitted(execsetup.broker, table) }) + s.Step(`^the following funding payment events should be emitted:$`, func(table *godog.Table) error { + return steps.TheFollowingFundingPaymentEventsShouldBeEmitted(execsetup.broker, table) + }) s.Step(`^the following events should be emitted:$`, func(table *godog.Table) error { return steps.TheFollowingEventsShouldBeEmitted(execsetup.broker, table) }) diff --git a/core/integration/steps/funding_payment_events.go b/core/integration/steps/funding_payment_events.go index 54d4b5eac71..6231c0e6b44 100644 --- a/core/integration/steps/funding_payment_events.go +++ b/core/integration/steps/funding_payment_events.go @@ -21,7 +21,10 @@ import ( "code.vegaprotocol.io/vega/core/events" "code.vegaprotocol.io/vega/core/integration/stubs" + "code.vegaprotocol.io/vega/core/types" + "code.vegaprotocol.io/vega/libs/num" "code.vegaprotocol.io/vega/logging" + eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" "github.com/cucumber/godog" ) @@ -49,6 +52,171 @@ func TheFollowingFundingPeriodEventsShouldBeEmitted(broker *stubs.BrokerStub, ta return nil } +func TheFollowingFundingPaymentEventsShouldBeEmitted(broker *stubs.BrokerStub, table *godog.Table) error { + paymentEvts := broker.GetFundginPaymentEvents() + checkLoss := false + rows := parseFundingPaymentsTable(table) + matchers := make([]FundingPaymentsWrapper, 0, len(rows)) + for _, row := range rows { + w := FundingPaymentsWrapper{ + r: row, + } + matchers = append(matchers, w) + checkLoss = (checkLoss || w.CheckLoss()) + } + // map the events by party and market + lsEvt := map[string]map[string][]*events.LossSoc{} + if checkLoss { + for _, ls := range broker.GetLossSoc() { + mID, pID := ls.MarketID(), ls.PartyID() + mmap, ok := lsEvt[mID] + if !ok { + mmap = map[string][]*events.LossSoc{} + } + ps, ok := mmap[pID] + if !ok { + ps = []*events.LossSoc{} + } + ps = append(ps, ls) + mmap[pID] = ps + lsEvt[mID] = mmap + } + } + // get by party and market + pEvts := map[string]map[string][]*eventspb.FundingPayment{} + for _, pe := range paymentEvts { + mID := pe.MarketID() + mmap, ok := pEvts[mID] + if !ok { + mmap = map[string][]*eventspb.FundingPayment{} + } + for _, fp := range pe.FundingPayments().Payments { + fps, ok := mmap[fp.PartyId] + if !ok { + fps = []*eventspb.FundingPayment{} + } + fps = append(fps, fp) + mmap[fp.PartyId] = fps + } + pEvts[mID] = mmap + } + // now start matching + for _, row := range matchers { + mID, pID := row.Market(), row.Party() + mmap, ok := pEvts[mID] + if !ok { + return fmt.Errorf("could not find funding payment events for market %s", mID) + } + ppayments, ok := mmap[pID] + if !ok { + return fmt.Errorf("could not find funding payment events for party %s in market %s", pID, mID) + } + matched := false + amt := row.Amount().String() + for _, fp := range ppayments { + if fp.Amount == amt { + matched = true + break + } + } + if !matched { + return fmt.Errorf("could not find funding payment of amount %s for party %s in market %s", amt, pID, mID) + } + if !checkLoss || !row.CheckLoss() { + continue + } + mloss, ok := lsEvt[mID] + if !ok { + return fmt.Errorf("could not find loss socialisation events for market %s", mID) + } + pLoss, ok := mloss[pID] + if !ok { + return fmt.Errorf("could not find loss socialisation event for party %s in market %s", pID, mID) + } + matched = false + for _, le := range pLoss { + if !row.matchLossType(le.LossType()) { + continue + } + if !row.matchLossAmount(le.Amount()) { + continue + } + matched = true + break + } + if !matched { + return fmt.Errorf("could not find loss amount/type %s/%s for party %s in market %s", row.LossAmount().String(), row.LossType().String(), pID, mID) + } + } + return nil +} + +func DebugFundingPaymentsEvents(broker *stubs.BrokerStub, log *logging.Logger) { + paymentEvts := broker.GetFundginPaymentEvents() + lossSoc := broker.GetLossSoc() + pEvts := map[string]map[string][]*eventspb.FundingPayment{} + lsEvt := map[string]map[string][]*events.LossSoc{} + for _, pe := range paymentEvts { + mID := pe.MarketID() + mmap, ok := pEvts[mID] + if !ok { + mmap = map[string][]*eventspb.FundingPayment{} + } + for _, fp := range pe.FundingPayments().Payments { + fps, ok := mmap[fp.PartyId] + if !ok { + fps = []*eventspb.FundingPayment{} + } + fps = append(fps, fp) + mmap[fp.PartyId] = fps + } + pEvts[mID] = mmap + } + for _, le := range lossSoc { + mID, pID := le.MarketID(), le.PartyID() + // ignore loss socialisation unless they are related to funding payments: + if mmap, ok := pEvts[mID]; !ok { + continue + } else if _, ok := mmap[pID]; !ok { + // also skip the parties that don't have funding payment events. + continue + } + mmap, ok := lsEvt[mID] + if !ok { + mmap = map[string][]*events.LossSoc{} + } + // ignore irrelevant parties? + ps, ok := mmap[pID] + if !ok { + ps = []*events.LossSoc{} + } + ps = append(ps, le) + mmap[pID] = ps + lsEvt[mID] = mmap + } + log.Info("DUMPING FUNDING PAYMENTS EVENTS") + for mID, fpMap := range pEvts { + log.Infof("Market ID: %s\n", mID) + for pID, fpe := range fpMap { + log.Infof("PartyID: %s\n", pID) + var lSoc []*events.LossSoc + lossM, ok := lsEvt[mID] + if ok { + lSoc = lossM[pID] + } + for i, fe := range fpe { + log.Infof("%d: Amount %s\n", i+1, fe.Amount) + } + if len(lSoc) > 0 { + log.Info("\nLOSS SOCIALISATION:\n") + } + for i, le := range lSoc { + log.Infof("%d: Amount: %s - Type: %s\n", i+1, le.Amount().String(), le.LossType().String()) + } + } + } +} + func DebugFundingPeriodEventss(broker *stubs.BrokerStub, log *logging.Logger) { log.Info("DUMPING FUNDING PERIOD EVENTS") data := broker.GetFundingPeriodEvents() @@ -112,6 +280,10 @@ type FundingPeriodEventWrapper struct { row RowWrapper } +type FundingPaymentsWrapper struct { + r RowWrapper +} + func parseFundingPeriodEventTable(table *godog.Table) []RowWrapper { return StrictParseTable(table, []string{ "internal twap", @@ -124,6 +296,61 @@ func parseFundingPeriodEventTable(table *godog.Table) []RowWrapper { }) } +func parseFundingPaymentsTable(table *godog.Table) []RowWrapper { + return StrictParseTable(table, []string{ + "party", + "market", + "amount", + }, []string{ + "loss type", + "loss amount", + }) +} + +func (f FundingPaymentsWrapper) Party() string { + return f.r.MustStr("party") +} + +func (f FundingPaymentsWrapper) Market() string { + return f.r.MustStr("market") +} + +func (f FundingPaymentsWrapper) Amount() *num.Int { + return f.r.MustInt("amount") +} + +func (f FundingPaymentsWrapper) LossAmount() *num.Int { + if !f.r.HasColumn("loss amount") { + return num.IntZero() + } + return f.r.MustInt("loss amount") +} + +func (f FundingPaymentsWrapper) LossType() types.LossType { + if !f.r.HasColumn("loss type") { + return types.LossTypeUnspecified + } + return f.r.MustLossType("loss type") +} + +func (f FundingPaymentsWrapper) CheckLoss() bool { + return f.r.HasColumn("loss type") || f.r.HasColumn("loss amount") +} + +func (f FundingPaymentsWrapper) matchLossType(t types.LossType) bool { + if !f.r.HasColumn("loss type") { + return true + } + return f.LossType() == t +} + +func (f FundingPaymentsWrapper) matchLossAmount(amt *num.Int) bool { + if !f.r.HasColumn("loss amount") { + return true + } + return f.LossAmount().EQ(amt) +} + func (f FundingPeriodEventWrapper) InternalTWAP() string { return f.row.MustStr("internal twap") } diff --git a/core/integration/steps/table_wrapper.go b/core/integration/steps/table_wrapper.go index d2e202c5a7d..554daf156f5 100644 --- a/core/integration/steps/table_wrapper.go +++ b/core/integration/steps/table_wrapper.go @@ -494,12 +494,34 @@ func EventType(rawValue string) (events.Type, error) { return *ty, nil } +func (r RowWrapper) LossType(name string) types.LossType { + lt, err := LossType(r.Str(name)) + if err != nil { + return types.LossTypeUnspecified + } + return lt +} + +func (r RowWrapper) MustLossType(name string) types.LossType { + lt, err := LossType(r.MustStr(name)) + panicW(name, err) + return lt +} + func (r RowWrapper) MustOrderType(name string) types.OrderType { orderType, err := OrderType(r.MustStr(name)) panicW(name, err) return orderType } +func LossType(rawValue string) (types.LossType, error) { + lt, ok := eventspb.LossSocialization_Type_value[rawValue] + if !ok { + return types.LossType(lt), fmt.Errorf("invalid loss socialisation type: %v", rawValue) + } + return types.LossType(lt), nil +} + func OrderType(rawValue string) (types.OrderType, error) { ty, ok := proto.Order_Type_value[rawValue] if !ok { diff --git a/core/integration/steps/the_loss_socialisation_amount_is.go b/core/integration/steps/the_loss_socialisation_amount_is.go index b352cea03ec..724a74283a6 100644 --- a/core/integration/steps/the_loss_socialisation_amount_is.go +++ b/core/integration/steps/the_loss_socialisation_amount_is.go @@ -20,6 +20,7 @@ import ( "code.vegaprotocol.io/vega/core/events" "code.vegaprotocol.io/vega/core/integration/stubs" + "code.vegaprotocol.io/vega/core/types" "code.vegaprotocol.io/vega/libs/num" "code.vegaprotocol.io/vega/logging" @@ -47,7 +48,7 @@ func TheLossSocialisationAmountsAre(broker *stubs.BrokerStub, table *godog.Table } for _, p := range lsr.Party() { if _, ok := parties[p]; !ok { - return fmt.Errorf("no loss socialisation found for party %s on market %s for amount %s", p, lsr.Market(), lsr.Amount().String()) + return fmt.Errorf("no loss socialisation found for party %s on market %s for amount %s (type: %s)", p, lsr.Market(), lsr.Amount().String(), lsr.Type().String()) } } } @@ -91,6 +92,7 @@ func parseLossSocTable(table *godog.Table) []RowWrapper { }, []string{ "party", "count", + "type", }) } @@ -119,3 +121,18 @@ func (l lossSocRow) Count() int { } return -1 } + +func (l lossSocRow) matchesType(t types.LossType) bool { + if l.r.HasColumn("type") { + exp := l.Type() + return exp == t + } + return true +} + +func (l lossSocRow) Type() types.LossType { + if !l.r.HasColumn("type") { + return types.LossTypeUnspecified + } + return l.r.MustLossType("type") +} diff --git a/core/integration/stubs/broker_stub.go b/core/integration/stubs/broker_stub.go index 2269f146728..737980472b7 100644 --- a/core/integration/stubs/broker_stub.go +++ b/core/integration/stubs/broker_stub.go @@ -621,6 +621,23 @@ func (b *BrokerStub) GetFundingPeriodEvents() []events.FundingPeriod { return ret } +func (b *BrokerStub) GetFundginPaymentEvents() []events.FundingPayments { + batch := b.GetBatch(events.FundingPaymentsEvent) + if len(batch) == 0 { + return nil + } + ret := make([]events.FundingPayments, 0, len(batch)) + for _, e := range batch { + switch et := e.(type) { + case *events.FundingPayments: + ret = append(ret, *et) + case events.FundingPayments: + ret = append(ret, et) + } + } + return ret +} + func (b *BrokerStub) GetTradeEvents() []events.Trade { batch := b.GetBatch(events.TradeEvent) if len(batch) == 0 {