code.vegaprotocol.io/vega@v0.79.0/core/execution/future/collateral.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package future
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  
    22  	"code.vegaprotocol.io/vega/core/events"
    23  	"code.vegaprotocol.io/vega/core/types"
    24  	"code.vegaprotocol.io/vega/libs/num"
    25  	"code.vegaprotocol.io/vega/logging"
    26  )
    27  
    28  // ErrBondSlashing - just indicates that we had to penalize the party due to insufficient funds, and as such, we have to cancel their LP.
    29  var ErrBondSlashing = errors.New("bond slashing")
    30  
    31  func (m *Market) transferMargins(ctx context.Context, risk []events.Risk, closed []events.MarketPosition) error {
    32  	if m.as.InAuction() {
    33  		return m.transferMarginsAuction(ctx, risk, closed)
    34  	}
    35  	return m.transferMarginsContinuous(ctx, risk)
    36  }
    37  
    38  func (m *Market) transferMarginsAuction(ctx context.Context, risk []events.Risk, distressed []events.MarketPosition) error {
    39  	evts := make([]events.Event, 0, len(risk))
    40  	mID := m.GetID()
    41  	// first, update the margin accounts for all parties who have enough balance
    42  	for _, re := range risk {
    43  		tr, _, err := m.collateral.MarginUpdateOnOrder(ctx, mID, re)
    44  		if err != nil {
    45  			// @TODO handle this
    46  			return err
    47  		}
    48  		if len(tr.Entries) > 0 {
    49  			evts = append(evts, events.NewLedgerMovements(ctx, []*types.LedgerMovement{tr}))
    50  		}
    51  	}
    52  
    53  	m.broker.SendBatch(evts)
    54  	rmorders, err := m.matching.RemoveDistressedOrders(distressed)
    55  	if err != nil {
    56  		return err
    57  	}
    58  	evts = make([]events.Event, 0, len(rmorders))
    59  	for _, o := range rmorders {
    60  		// cancel order
    61  		o.Status = types.OrderStatusCancelled
    62  		o.Reason = types.OrderErrorInsufficientAssetBalance
    63  		// create event
    64  		evts = append(evts, events.NewOrderEvent(ctx, o))
    65  		// remove order from positions
    66  		_ = m.position.UnregisterOrder(ctx, o)
    67  	}
    68  	m.broker.SendBatch(evts)
    69  	return nil
    70  }
    71  
    72  func (m *Market) transferRecheckMargins(ctx context.Context, risk []events.Risk) {
    73  	if len(risk) == 0 {
    74  		return
    75  	}
    76  	mID := m.GetID()
    77  	evts := make([]events.Event, 0, len(risk))
    78  	for _, r := range risk {
    79  		var tr *types.LedgerMovement
    80  		responses := make([]*types.LedgerMovement, 0, 1)
    81  		tr, closed, err := m.collateral.MarginUpdateOnOrder(ctx, mID, r)
    82  		if err != nil {
    83  			m.log.Warn("margin recheck failed",
    84  				logging.MarketID(m.GetID()),
    85  				logging.PartyID(r.Party()),
    86  				logging.Error(err))
    87  		}
    88  		if tr != nil {
    89  			responses = append(responses, tr)
    90  		}
    91  		if closed != nil && !closed.MarginShortFall().IsZero() {
    92  			resp, err := m.bondSlashing(ctx, closed)
    93  			if err != nil {
    94  				m.log.Panic("Bond slashing for non-distressed LP failed",
    95  					logging.String("party", closed.Party()),
    96  					logging.Error(err),
    97  				)
    98  			}
    99  			responses = append(responses, resp...)
   100  		}
   101  		if len(responses) > 0 {
   102  			evts = append(evts, events.NewLedgerMovements(ctx, responses))
   103  		}
   104  	}
   105  	m.broker.SendBatch(evts)
   106  }
   107  
   108  func (m *Market) transferMarginsContinuous(ctx context.Context, risk []events.Risk) error {
   109  	if len(risk) > 1 {
   110  		return errors.New("transferMarginsContinuous should not be possible when len(risk) > 1")
   111  	}
   112  	if len(risk) == 0 {
   113  		return nil
   114  	}
   115  	mID := m.GetID()
   116  	tr, closed, err := m.collateral.MarginUpdateOnOrder(ctx, mID, risk[0])
   117  	if err != nil {
   118  		return err
   119  	}
   120  	// if LP shortfall is not empty, this party will have to pay the LP penalty
   121  	responses := make([]*types.LedgerMovement, 0, len(risk))
   122  	if tr != nil {
   123  		responses = append(responses, tr)
   124  	}
   125  	// margin shortfall && liquidity provider -> bond slashing
   126  	if closed != nil && !closed.MarginShortFall().IsZero() {
   127  		// get bond penalty
   128  		resp, err := m.bondSlashing(ctx, closed)
   129  		if err != nil {
   130  			return err
   131  		}
   132  		responses = append(responses, resp...)
   133  	}
   134  	if len(responses) > 0 {
   135  		m.broker.Send(events.NewLedgerMovements(ctx, responses))
   136  	}
   137  
   138  	return nil
   139  }
   140  
   141  func (m *Market) bondSlashing(ctx context.Context, closed ...events.Margin) ([]*types.LedgerMovement, error) {
   142  	mID := m.GetID()
   143  	ret := make([]*types.LedgerMovement, 0, len(closed))
   144  	for _, c := range closed {
   145  		if !m.liquidityEngine.IsLiquidityProvider(c.Party()) {
   146  			continue
   147  		}
   148  		penalty, _ := num.UintFromDecimal(
   149  			num.DecimalFromUint(c.MarginShortFall()).Mul(m.bondPenaltyFactor).Floor(),
   150  		)
   151  
   152  		resp, err := m.collateral.BondUpdate(ctx, mID, &types.Transfer{
   153  			Owner: c.Party(),
   154  			Amount: &types.FinancialAmount{
   155  				Amount: penalty,
   156  				Asset:  c.Asset(),
   157  			},
   158  			Type:      types.TransferTypeBondSlashing,
   159  			MinAmount: num.UintZero(),
   160  		})
   161  		if err != nil {
   162  			return nil, err
   163  		}
   164  		ret = append(ret, resp)
   165  	}
   166  	return ret, nil
   167  }