From 9b9e5bca240ee454d0564687b594c341bffa8c6f Mon Sep 17 00:00:00 2001 From: ze97286 Date: Fri, 26 Apr 2024 00:23:26 +0100 Subject: [PATCH] fix: verify parties can afford the spot trade they're trying to do --- CHANGELOG.md | 1 + core/execution/spot/market.go | 43 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d64a697ab31..a47d2d2e138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ - [11161](https://github.com/vegaprotocol/vega/issues/11161) - Add validation for time in force GFA in stop order submission. - [11177](https://github.com/vegaprotocol/vega/issues/11177) - Adjust the formulas for reduced position to the spec update and fix handling of closed out position. - [11193](https://github.com/vegaprotocol/vega/issues/11193) - Fix loading of liquidation strategy from proto with missing data. +- [11200](https://github.com/vegaprotocol/vega/issues/11200) - Make sure a party can afford the trades before they are submitted to the book. ## 0.75.0 diff --git a/core/execution/spot/market.go b/core/execution/spot/market.go index 0ed2fd5c5e9..d4eca82ca0b 100644 --- a/core/execution/spot/market.go +++ b/core/execution/spot/market.go @@ -1403,6 +1403,35 @@ func (m *Market) submitOrder(ctx context.Context, order *types.Order) (*types.Or return orderConf, orderUpdates, err } +func (m *Market) canCoverTradesAndFees(party string, partySide types.Side, trades []*types.Trade) error { + // check that the party can afford the traded amount + fees + if partySide == types.SideBuy { + totalTraded := num.UintZero() + for _, t := range trades { + fees, err := m.calculateFeesForTrades([]*types.Trade{t}) + if err != nil { + m.log.Panic("failed to calculate fees for trade", logging.Trade(t)) + } + size := num.NewUint(t.Size) + totalTraded.AddSum(size.Mul(size, t.Price), fees.TotalFeesAmountPerParty()[party]) + } + totalTraded, _ = num.UintFromDecimal(totalTraded.ToDecimal().Div(m.positionFactor)) + if err := m.collateral.PartyHasSufficientBalance(m.quoteAsset, party, totalTraded); err != nil { + return err + } + } else { + sizeTraded := uint64(0) + for _, t := range trades { + sizeTraded += t.Size + } + totalTraded := scaleBaseQuantityToAssetDP(sizeTraded, m.baseFactor) + if err := m.collateral.PartyHasSufficientBalance(m.baseAsset, party, totalTraded); err != nil { + return err + } + } + return nil +} + // submitValidatedOrder submits a new order. func (m *Market) submitValidatedOrder(ctx context.Context, order *types.Order) (*types.OrderConfirmation, []*types.Order, error) { isPegged := order.PeggedOrder != nil @@ -1441,6 +1470,13 @@ func (m *Market) submitValidatedOrder(ctx context.Context, order *types.Order) ( // not be an issue } + // check that the party can afford the trade and fees + if trades != nil { + if err := m.canCoverTradesAndFees(order.Party, order.Side, trades); err != nil { + return nil, nil, m.unregisterAndReject(ctx, order, err) + } + } + // if an auction is ongoing and the order is pegged, park it and return if m.as.InAuction() && isPegged { if isPegged { @@ -2398,6 +2434,13 @@ func (m *Market) orderCancelReplace(ctx context.Context, existingOrder, newOrder return nil, nil, errors.New("couldn't insert order in book") } + // check that the party can afford the trade - if not return error and ignore the update + if trades != nil { + if err := m.canCoverTradesAndFees(newOrder.Party, newOrder.Side, trades); err != nil { + return nil, nil, err + } + } + // "hot-swap" of the orders conf, err = m.matching.ReplaceOrder(existingOrder, newOrder) if err != nil {