code.vegaprotocol.io/vega@v0.79.0/core/execution/spot/holding_account_tracker.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 spot 17 18 import ( 19 "context" 20 "fmt" 21 "sort" 22 23 "code.vegaprotocol.io/vega/core/execution/common" 24 "code.vegaprotocol.io/vega/core/types" 25 "code.vegaprotocol.io/vega/libs/num" 26 "code.vegaprotocol.io/vega/libs/proto" 27 "code.vegaprotocol.io/vega/logging" 28 29 "golang.org/x/exp/maps" 30 ) 31 32 type HoldingAccountTracker struct { 33 orderIDToQuantity map[string]*num.Uint 34 orderIDToFee map[string]*num.Uint 35 collateral common.Collateral 36 stopped bool 37 log *logging.Logger 38 snapshot *types.PayloadHoldingAccountTracker 39 } 40 41 func NewHoldingAccountTracker(marketID string, log *logging.Logger, collateral common.Collateral) *HoldingAccountTracker { 42 return &HoldingAccountTracker{ 43 orderIDToQuantity: map[string]*num.Uint{}, 44 orderIDToFee: map[string]*num.Uint{}, 45 collateral: collateral, 46 log: log, 47 snapshot: &types.PayloadHoldingAccountTracker{ 48 HoldingAccountTracker: &types.HoldingAccountTracker{ 49 MarketID: marketID, 50 }, 51 }, 52 } 53 } 54 55 func (hat *HoldingAccountTracker) GetCurrentHolding(orderID string) (*num.Uint, *num.Uint) { 56 qty := num.UintZero() 57 fees := num.UintZero() 58 if q, ok := hat.orderIDToQuantity[orderID]; ok { 59 qty = q 60 } 61 if f, ok := hat.orderIDToFee[orderID]; ok { 62 fees = f 63 } 64 return qty, fees 65 } 66 67 func (hat *HoldingAccountTracker) TransferToHoldingAccount(ctx context.Context, orderID, party, asset string, quantity *num.Uint, fee *num.Uint, accountType types.AccountType) (*types.LedgerMovement, error) { 68 if _, ok := hat.orderIDToQuantity[orderID]; ok { 69 return nil, fmt.Errorf("funds for the order have already been transferred to the holding account") 70 } 71 total := num.Sum(quantity, fee) 72 73 transfer := &types.Transfer{ 74 Owner: party, 75 Amount: &types.FinancialAmount{ 76 Asset: asset, 77 Amount: total, 78 }, 79 Type: types.TransferTypeHoldingAccount, 80 } 81 le, err := hat.collateral.TransferToHoldingAccount(ctx, transfer, accountType) 82 if err != nil { 83 return nil, err 84 } 85 hat.orderIDToQuantity[orderID] = quantity 86 if !fee.IsZero() { 87 hat.orderIDToFee[orderID] = fee 88 } 89 return le, nil 90 } 91 92 func (hat *HoldingAccountTracker) TransferFeeToHoldingAccount(ctx context.Context, orderID, party, asset string, feeQuantity *num.Uint, fromAccountType types.AccountType) (*types.LedgerMovement, error) { 93 if feeQuantity.IsZero() { 94 return nil, nil 95 } 96 transfer := &types.Transfer{ 97 Owner: party, 98 Amount: &types.FinancialAmount{ 99 Asset: asset, 100 Amount: feeQuantity.Clone(), 101 }, 102 Type: types.TransferTypeHoldingAccount, 103 } 104 le, err := hat.collateral.TransferToHoldingAccount(ctx, transfer, fromAccountType) 105 if err != nil { 106 return nil, err 107 } 108 fee, ok := hat.orderIDToFee[orderID] 109 if ok { 110 hat.orderIDToFee[orderID].Add(feeQuantity, fee) 111 } else { 112 hat.orderIDToFee[orderID] = feeQuantity 113 } 114 return le, nil 115 } 116 117 func (hat *HoldingAccountTracker) ReleaseFeeFromHoldingAccount(ctx context.Context, orderID, party, asset string, toAccountType types.AccountType) (*types.LedgerMovement, error) { 118 feeQuantity, ok := hat.orderIDToFee[orderID] 119 if !ok { 120 return nil, fmt.Errorf("failed to find locked fee amount for order id %s", orderID) 121 } 122 transfer := &types.Transfer{ 123 Owner: party, 124 Amount: &types.FinancialAmount{ 125 Asset: asset, 126 Amount: feeQuantity.Clone(), 127 }, 128 Type: types.TransferTypeHoldingAccount, 129 } 130 delete(hat.orderIDToFee, orderID) 131 le, err := hat.collateral.ReleaseFromHoldingAccount(ctx, transfer, toAccountType) 132 if err != nil { 133 return nil, err 134 } 135 return le, err 136 } 137 138 func (hat *HoldingAccountTracker) ReleaseQuantityHoldingAccount(ctx context.Context, orderID, party, asset string, quantity *num.Uint, fee *num.Uint, toAccountType types.AccountType) (*types.LedgerMovement, error) { 139 total := num.Sum(quantity, fee) 140 if !fee.IsZero() { 141 lockedFee, ok := hat.orderIDToFee[orderID] 142 if !ok || lockedFee.LT(fee) { 143 return nil, fmt.Errorf("insufficient locked fee to release for order %s", orderID) 144 } 145 } 146 lockedQuantity, ok := hat.orderIDToQuantity[orderID] 147 if !ok || lockedQuantity.LT(quantity) { 148 return nil, fmt.Errorf("insufficient locked quantity to release for order %s", orderID) 149 } 150 if !fee.IsZero() { 151 hat.orderIDToFee[orderID] = num.UintZero().Sub(hat.orderIDToFee[orderID], fee) 152 } 153 hat.orderIDToQuantity[orderID] = num.UintZero().Sub(lockedQuantity, quantity) 154 transfer := &types.Transfer{ 155 Owner: party, 156 Amount: &types.FinancialAmount{ 157 Asset: asset, 158 Amount: total, 159 }, 160 Type: types.TransferTypeReleaseHoldingAccount, 161 } 162 le, err := hat.collateral.ReleaseFromHoldingAccount(ctx, transfer, toAccountType) 163 if err != nil { 164 return nil, err 165 } 166 return le, err 167 } 168 169 func (hat *HoldingAccountTracker) ReleaseQuantityHoldingAccountAuctionEnd(ctx context.Context, orderID, party, asset string, quantity *num.Uint, fee *num.Uint, toAccountType types.AccountType) (*types.LedgerMovement, error) { 170 effectiveFee := num.UintZero().Div(fee, num.NewUint(2)) 171 total := num.Sum(quantity, effectiveFee) 172 173 if !effectiveFee.IsZero() { 174 lockedFee, ok := hat.orderIDToFee[orderID] 175 if !ok { 176 effectiveFee = num.UintZero() 177 } else { 178 effectiveFee = num.Min(effectiveFee, lockedFee) 179 } 180 } 181 lockedQuantity, ok := hat.orderIDToQuantity[orderID] 182 if !ok || lockedQuantity.LT(quantity) { 183 return nil, fmt.Errorf("insufficient locked quantity to release for order %s", orderID) 184 } 185 if !effectiveFee.IsZero() { 186 hat.orderIDToFee[orderID] = num.UintZero().Sub(hat.orderIDToFee[orderID], effectiveFee) 187 } 188 hat.orderIDToQuantity[orderID] = num.UintZero().Sub(lockedQuantity, quantity) 189 transfer := &types.Transfer{ 190 Owner: party, 191 Amount: &types.FinancialAmount{ 192 Asset: asset, 193 Amount: total, 194 }, 195 Type: types.TransferTypeReleaseHoldingAccount, 196 } 197 le, err := hat.collateral.ReleaseFromHoldingAccount(ctx, transfer, toAccountType) 198 if err != nil { 199 return nil, err 200 } 201 return le, err 202 } 203 204 func (hat *HoldingAccountTracker) ReleaseAllFromHoldingAccount(ctx context.Context, orderID, party, asset string, toAccountType types.AccountType) (*types.LedgerMovement, error) { 205 fee := num.UintZero() 206 amt := num.UintZero() 207 if f, ok := hat.orderIDToFee[orderID]; ok { 208 fee = f 209 } 210 if a, ok := hat.orderIDToQuantity[orderID]; ok { 211 amt = a 212 } 213 214 total := num.Sum(fee, amt) 215 delete(hat.orderIDToFee, orderID) 216 delete(hat.orderIDToQuantity, orderID) 217 218 if total.IsZero() { 219 return nil, nil 220 } 221 transfer := &types.Transfer{ 222 Owner: party, 223 Amount: &types.FinancialAmount{ 224 Asset: asset, 225 Amount: total, 226 }, 227 Type: types.TransferTypeReleaseHoldingAccount, 228 } 229 return hat.collateral.ReleaseFromHoldingAccount(ctx, transfer, toAccountType) 230 } 231 232 func (hat *HoldingAccountTracker) StopSnapshots() { 233 hat.stopped = true 234 } 235 236 func (hat *HoldingAccountTracker) Keys() []string { 237 return []string{hat.snapshot.Key()} 238 } 239 240 func (hat *HoldingAccountTracker) Stopped() bool { 241 return hat.stopped 242 } 243 244 func (hat *HoldingAccountTracker) Namespace() types.SnapshotNamespace { 245 return types.HoldingAccountTrackerSnapshot 246 } 247 248 func (hat *HoldingAccountTracker) GetState(key string) ([]byte, []types.StateProvider, error) { 249 if key != hat.snapshot.Key() { 250 return nil, nil, types.ErrSnapshotKeyDoesNotExist 251 } 252 253 if hat.stopped { 254 return nil, nil, nil 255 } 256 payload := hat.buildPayload() 257 258 s, err := proto.Marshal(payload.IntoProto()) 259 return s, nil, err 260 } 261 262 func (hat *HoldingAccountTracker) buildPayload() *types.Payload { 263 quantities := make([]*types.HoldingAccountQuantity, 0, len(hat.orderIDToQuantity)) 264 265 orderIDs := map[string]struct{}{} 266 for k := range hat.orderIDToQuantity { 267 orderIDs[k] = struct{}{} 268 } 269 for k := range hat.orderIDToFee { 270 orderIDs[k] = struct{}{} 271 } 272 orderIDSlice := maps.Keys(orderIDs) 273 sort.Strings(orderIDSlice) 274 275 for _, oid := range orderIDSlice { 276 quantities = append(quantities, &types.HoldingAccountQuantity{ 277 ID: oid, 278 Quantity: hat.orderIDToQuantity[oid], 279 FeeQuantity: hat.orderIDToFee[oid], 280 }) 281 } 282 283 return &types.Payload{ 284 Data: &types.PayloadHoldingAccountTracker{ 285 HoldingAccountTracker: &types.HoldingAccountTracker{ 286 MarketID: hat.snapshot.HoldingAccountTracker.MarketID, 287 HoldingAccountQuantities: quantities, 288 }, 289 }, 290 } 291 } 292 293 func (hat *HoldingAccountTracker) LoadState(_ context.Context, payload *types.Payload) ([]types.StateProvider, error) { 294 if hat.Namespace() != payload.Namespace() { 295 return nil, types.ErrInvalidSnapshotNamespace 296 } 297 298 var at *types.HoldingAccountTracker 299 300 switch pl := payload.Data.(type) { 301 case *types.PayloadHoldingAccountTracker: 302 at = pl.HoldingAccountTracker 303 default: 304 return nil, types.ErrUnknownSnapshotType 305 } 306 307 for _, haq := range at.HoldingAccountQuantities { 308 if haq.FeeQuantity != nil { 309 hat.orderIDToFee[haq.ID] = haq.FeeQuantity 310 } 311 if haq.Quantity != nil { 312 hat.orderIDToQuantity[haq.ID] = haq.Quantity 313 } 314 } 315 316 return nil, nil 317 }