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 }