Skip to content

Commit

Permalink
Merge pull request #11366 from vegaprotocol/loss-socialisation-underflow
Browse files Browse the repository at this point in the history
fix: underflow on win socialisation check
  • Loading branch information
EVODelavega authored Jun 12, 2024
2 parents 2c13dcb + f1834e5 commit 834c3e0
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 7 deletions.
5 changes: 4 additions & 1 deletion core/settlement/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,10 @@ func (e *Engine) SettleMTM(ctx context.Context, markPrice *num.Uint, positions [
}
// no need for this lock anymore
e.mu.Unlock()
delta := num.UintZero().Sub(lossTotal, winTotal)
delta := num.UintZero()
if lossTotal.GT(winTotal) {
delta.Sub(lossTotal, winTotal)
}
// make sure largests share is never nil
if largestShare == nil {
largestShare = &mtmTransfer{
Expand Down
100 changes: 100 additions & 0 deletions core/settlement/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func TestMTMWinDistribution(t *testing.T) {
t.Run("A MTM loss party with a loss of value 1, with several parties needing a win", testMTMWinOneExcess)
t.Run("Distribute win excess in a scenario where no transfer amount is < 1", testMTMWinNoZero)
t.Run("Distribute loss excess in a scenario where no transfer amount is < 1", testMTMWinWithZero)
t.Run("A MTM case where win total > loss total", testMTMWinGTLoss)
}

func testSettlingAFundingPeriod(t *testing.T) {
Expand Down Expand Up @@ -1395,6 +1396,105 @@ func testMTMWinWithZero(t *testing.T) {
require.NotEmpty(t, transfers)
}

func testMTMWinGTLoss(t *testing.T) {
// cheat by setting the factor to some specific value, makes it easier to create a scenario where win/loss amounts don't match
engine := getTestEngineWithFactor(t, 5)
defer engine.Finish()

price := num.NewUint(100000)
one := num.NewUint(1)
two := num.Sum(one, one)
ctx := context.Background()

initPos := []testPos{
{
price: price.Clone(),
party: "party-1",
size: 10,
},
{
price: price.Clone(),
party: "party-2",
size: -4,
},
{
price: price.Clone(),
party: "party-3",
size: -1,
},
{
price: price.Clone(),
party: "party-4",
size: -5,
},
{
price: price.Clone(),
party: "party-6",
size: -1,
},
{
price: price.Clone(),
party: "party-7",
size: 1,
},
}
init := make([]events.MarketPosition, 0, len(initPos))
for _, p := range initPos {
init = append(init, p)
}

somePrice := num.Sum(price, two)
newPrice := num.Sum(price, one)
newParty := testPos{
size: 3,
price: newPrice.Clone(),
party: "party-5",
}

trades := []*types.Trade{
{
Size: 1,
Buyer: newParty.party,
Seller: initPos[0].party,
Price: somePrice.Clone(),
},
{
Size: 1,
Buyer: newParty.party,
Seller: initPos[3].party,
Price: somePrice.Clone(),
},
{
Size: 1,
Buyer: newParty.party,
Seller: initPos[0].party,
Price: newPrice.Clone(),
},
}
updates := make([]events.MarketPosition, 0, len(initPos)+1)
for _, trade := range trades {
for i, p := range initPos {
if p.party == trade.Seller {
p.size -= int64(trade.Size)
initPos[i] = p
}
p.price = trade.Price.Clone()
}
}
for _, p := range initPos {
updates = append(updates, p)
}
updates = append(updates, newParty)
engine.Update(init)
for _, trade := range trades {
engine.AddTrade(trade)
}
transfers := engine.SettleMTM(ctx, newPrice.Clone(), updates)
require.NotEmpty(t, transfers)
// just a single transfer of 2
require.True(t, transfers[0].Transfer().Amount.Amount.EQ(two))
}

// {{{.
func (te *testEngine) getExpiryPositions(positions ...posValue) []events.MarketPosition {
te.positions = make([]*mocks.MockMarketPosition, 0, len(positions))
Expand Down
12 changes: 6 additions & 6 deletions datanode/networkhistory/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,12 +379,12 @@ func TestMain(t *testing.M) {
log.Infof("%s", goldenSourceHistorySegment[4000].HistorySegmentID)
log.Infof("%s", goldenSourceHistorySegment[5000].HistorySegmentID)

panicIfHistorySegmentIdsNotEqual(goldenSourceHistorySegment[1000].HistorySegmentID, "QmRHcfKhp4XHAaXDKwEqKVrhTCJPT53pQ7RQEoAj423CA3", snapshots)
panicIfHistorySegmentIdsNotEqual(goldenSourceHistorySegment[2000].HistorySegmentID, "QmZPvVJj9P3yQxr9S4Ck5NFp1ncKmQN1HQzLt9h8A7Gxoz", snapshots)
panicIfHistorySegmentIdsNotEqual(goldenSourceHistorySegment[2500].HistorySegmentID, "QmPnhU8dxWJoMRUwwJZ35YnS9SV7PFFSE8PJuB5u9iscwR", snapshots)
panicIfHistorySegmentIdsNotEqual(goldenSourceHistorySegment[3000].HistorySegmentID, "Qma4K4eug73JcwP9v5Cgrf4L9LkQ3W5H5TKQEb8zdqjpXM", snapshots)
panicIfHistorySegmentIdsNotEqual(goldenSourceHistorySegment[4000].HistorySegmentID, "QmX3P7SKGNXma8be9c3Q94K6ycMuc67oRhAJAREntxzXUx", snapshots)
panicIfHistorySegmentIdsNotEqual(goldenSourceHistorySegment[5000].HistorySegmentID, "QmUbgGpmRZi8mc3nmkGewwksSiSCR3gyhoa4JXa3ca9RxT", snapshots)
panicIfHistorySegmentIdsNotEqual(goldenSourceHistorySegment[1000].HistorySegmentID, "Qmf5QzhfokNqFE7N5Kf1oUmrzA94tTJNRWxB9ULShf1uzV", snapshots)
panicIfHistorySegmentIdsNotEqual(goldenSourceHistorySegment[2000].HistorySegmentID, "QmbsusmxWeUxCHdJRznLbtcevgKhF4dkvL9yFnhpJS3wQa", snapshots)
panicIfHistorySegmentIdsNotEqual(goldenSourceHistorySegment[2500].HistorySegmentID, "QmPfUnXkuBzmMjB2rcdtGMNhdd6mc3cvYMfS9rnMXjfu6L", snapshots)
panicIfHistorySegmentIdsNotEqual(goldenSourceHistorySegment[3000].HistorySegmentID, "QmRwtm3pUuUYG69tmbssFkYd2DafLELBeDfxuEjqjmXct8", snapshots)
panicIfHistorySegmentIdsNotEqual(goldenSourceHistorySegment[4000].HistorySegmentID, "QmQmC4xrdSJMiJ9MLgWdU1mptVv9TdSsYyosusd8dCt8A4", snapshots)
panicIfHistorySegmentIdsNotEqual(goldenSourceHistorySegment[5000].HistorySegmentID, "QmU3zDAHiQYWLyeYzwwCAynoh8A7g38ZaLpdV2h5pgkuL5", snapshots)
}, postgresRuntimePath, sqlFs)

if exitCode != 0 {
Expand Down
27 changes: 27 additions & 0 deletions datanode/sqlstore/migrations/0112_fix_pnl.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-- +goose Up

WITH updated_pnl AS (
SELECT DISTINCT ON (pc.party_id) pc.party_id AS pid, pc.market_id AS mid, pc.loss - ph.loss AS correct_loss, pc.loss_socialisation_amount - ph.loss_socialisation_amount AS correct_loss_soc,
pc.realised_pnl - ph.realised_pnl AS correct_pnl,
pc.pending_realised_pnl - ph.pending_realised_pnl AS correct_ppnl,
pc.adjustment - ph.adjustment AS correct_adj
FROM positions_current AS pc
JOIN positions AS ph
ON pc.party_id = ph.party_id
AND pc.market_id = ph.market_id
WHERE pc.party_id IN ('\x947a700141e3d175304ee176d0beecf9ee9f462e09330e33c386952caf21f679', '\x15a8f372e255c6fa596a0b3acd62bc3be63b65188c23d33fc350f38ef52902e3', '\xaa1ce33b0b31a2e0f0a947ba83f64fa4a7e5d977fffb82c278c3b33fb0498113', '\x6527ffdd223ef2b4695ad90d832adc5493e9b8e25ad3185e67d873767f1f275e')
AND ph.vega_time >= '2024-06-08 19:38:49.89053+00'
AND pc.market_id = '\xe63a37edae8b74599d976f5dedbf3316af82579447f7a08ae0495a021fd44d13'
ORDER BY pc.party_id, ph.vega_time ASC
)
UPDATE positions_current
SET loss = updated_pnl.correct_loss,
loss_socialisation_amount = updated_pnl.correct_loss_soc,
realised_pnl = updated_pnl.correct_pnl,
pending_realised_pnl = updated_pnl.correct_ppnl,
adjustment = updated_pnl.correct_adj
FROM updated_pnl
WHERE party_id = updated_pnl.pid AND market_id = updated_pnl.mid;

-- +goose Down
-- nothing

0 comments on commit 834c3e0

Please sign in to comment.