code.vegaprotocol.io/vega@v0.79.0/core/types/banking.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 types 17 18 import ( 19 "errors" 20 "time" 21 22 vgcrypto "code.vegaprotocol.io/vega/libs/crypto" 23 "code.vegaprotocol.io/vega/libs/num" 24 vegapb "code.vegaprotocol.io/vega/protos/vega" 25 checkpointpb "code.vegaprotocol.io/vega/protos/vega/checkpoint/v1" 26 commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1" 27 eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" 28 ) 29 30 type TransferStatus = eventspb.Transfer_Status 31 32 const ( 33 // Default value. 34 TransferStatsUnspecified TransferStatus = eventspb.Transfer_STATUS_UNSPECIFIED 35 // A pending transfer. 36 TransferStatusPending TransferStatus = eventspb.Transfer_STATUS_PENDING 37 // A finished transfer. 38 TransferStatusDone TransferStatus = eventspb.Transfer_STATUS_DONE 39 // A rejected transfer. 40 TransferStatusRejected TransferStatus = eventspb.Transfer_STATUS_REJECTED 41 // A stopped transfer. 42 TransferStatusStopped TransferStatus = eventspb.Transfer_STATUS_STOPPED 43 // A cancelled transfer. 44 TransferStatusCancelled TransferStatus = eventspb.Transfer_STATUS_CANCELLED 45 ) 46 47 var ( 48 ErrMissingTransferKind = errors.New("missing transfer kind") 49 ErrCannotTransferZeroFunds = errors.New("cannot transfer zero funds") 50 ErrInvalidFromAccount = errors.New("invalid from account") 51 ErrInvalidFromDerivedKey = errors.New("invalid from derived key") 52 ErrInvalidToAccount = errors.New("invalid to account") 53 ErrUnsupportedFromAccountType = errors.New("unsupported from account type") 54 ErrUnsupportedToAccountType = errors.New("unsupported to account type") 55 ErrEndEpochIsZero = errors.New("end epoch is zero") 56 ErrStartEpochIsZero = errors.New("start epoch is zero") 57 ErrInvalidFactor = errors.New("invalid factor") 58 ErrStartEpochAfterEndEpoch = errors.New("start epoch after end epoch") 59 ErrInvalidToForRewardAccountType = errors.New("to party is invalid for reward account type") 60 ErrTransferFromLockedForStakingAllowedOnlyToOwnGeneralAccount = errors.New("transfers from locked for staking allowed only to own general account") 61 ErrTransferToLockedForStakingAllowedOnlyFromOwnGeneralAccount = errors.New("transfers to locked for staking allowed only from own general account") 62 ErrCanOnlyTransferFromLockedForStakingToGeneralAccount = errors.New("can only transfer from locked for staking to general account") 63 ) 64 65 type TransferCommandKind int 66 67 const ( 68 TransferCommandKindOneOff TransferCommandKind = iota 69 TransferCommandKindRecurring 70 ) 71 72 type TransferBase struct { 73 ID string 74 From string 75 FromDerivedKey *string 76 FromAccountType AccountType 77 To string 78 ToAccountType AccountType 79 Asset string 80 Amount *num.Uint 81 Reference string 82 Status TransferStatus 83 Timestamp time.Time 84 } 85 86 func (t *TransferBase) IsValid() error { 87 if !vgcrypto.IsValidVegaPubKey(t.From) { 88 return ErrInvalidFromAccount 89 } 90 if !vgcrypto.IsValidVegaPubKey(t.To) { 91 return ErrInvalidToAccount 92 } 93 94 // ensure amount makes senses 95 if t.Amount.IsZero() { 96 return ErrCannotTransferZeroFunds 97 } 98 99 if t.FromAccountType == AccountTypeLockedForStaking && t.ToAccountType != AccountTypeGeneral { 100 return ErrCanOnlyTransferFromLockedForStakingToGeneralAccount 101 } 102 103 if t.FromAccountType == AccountTypeGeneral && t.ToAccountType == AccountTypeLockedForStaking && t.From != t.To { 104 return ErrTransferToLockedForStakingAllowedOnlyFromOwnGeneralAccount 105 } 106 107 if t.ToAccountType == AccountTypeGeneral && t.FromAccountType == AccountTypeLockedForStaking && t.From != t.To { 108 return ErrTransferFromLockedForStakingAllowedOnlyToOwnGeneralAccount 109 } 110 111 // check for derived account transfer 112 if t.FromDerivedKey != nil { 113 if !vgcrypto.IsValidVegaPubKey(*t.FromDerivedKey) { 114 return ErrInvalidFromDerivedKey 115 } 116 117 if t.FromAccountType != AccountTypeVestedRewards { 118 return ErrUnsupportedFromAccountType 119 } 120 121 if t.ToAccountType != AccountTypeGeneral { 122 return ErrUnsupportedToAccountType 123 } 124 } 125 126 // check for any other transfers 127 switch t.FromAccountType { 128 case AccountTypeGeneral, AccountTypeVestedRewards, AccountTypeLockedForStaking: 129 break 130 default: 131 return ErrUnsupportedFromAccountType 132 } 133 134 switch t.ToAccountType { 135 case AccountTypeGlobalReward, AccountTypeNetworkTreasury: 136 if t.To != "0000000000000000000000000000000000000000000000000000000000000000" { 137 return ErrInvalidToForRewardAccountType 138 } 139 case AccountTypeGeneral, AccountTypeLPFeeReward, AccountTypeMakerReceivedFeeReward, AccountTypeMakerPaidFeeReward, AccountTypeMarketProposerReward, 140 AccountTypeAverageNotionalReward, AccountTypeRelativeReturnReward, AccountTypeValidatorRankingReward, AccountTypeReturnVolatilityReward, AccountTypeRealisedReturnReward, AccountTypeEligibleEntitiesReward, AccountTypeBuyBackFees, AccountTypeLockedForStaking: 141 break 142 default: 143 return ErrUnsupportedToAccountType 144 } 145 146 return nil 147 } 148 149 type GovernanceTransfer struct { 150 ID string // NB: this is the ID of the proposal 151 Reference string 152 Config *NewTransferConfiguration 153 Status TransferStatus 154 Timestamp time.Time 155 } 156 157 func (g *GovernanceTransfer) IntoProto() *checkpointpb.GovernanceTransfer { 158 return &checkpointpb.GovernanceTransfer{ 159 Id: g.ID, 160 Reference: g.Reference, 161 Timestamp: g.Timestamp.UnixNano(), 162 Status: g.Status, 163 Config: g.Config.IntoProto(), 164 } 165 } 166 167 func GovernanceTransferFromProto(g *checkpointpb.GovernanceTransfer) *GovernanceTransfer { 168 c, _ := NewTransferConfigurationFromProto(g.Config) 169 return &GovernanceTransfer{ 170 ID: g.Id, 171 Reference: g.Reference, 172 Timestamp: time.Unix(0, g.Timestamp), 173 Status: g.Status, 174 Config: c, 175 } 176 } 177 178 func (g *GovernanceTransfer) IntoEvent(amount *num.Uint, reason, gameID *string) *eventspb.Transfer { 179 // Not sure if this symbology gonna work for datanode 180 from := "0000000000000000000000000000000000000000000000000000000000000000" 181 if len(g.Config.Source) > 0 { 182 from = g.Config.Source 183 } 184 to := g.Config.Destination 185 if g.Config.DestinationType == AccountTypeGlobalReward || g.Config.DestinationType == AccountTypeNetworkTreasury || g.Config.DestinationType == AccountTypeGlobalInsurance { 186 to = "0000000000000000000000000000000000000000000000000000000000000000" 187 } 188 189 out := &eventspb.Transfer{ 190 Id: g.ID, 191 From: from, 192 FromAccountType: g.Config.SourceType, 193 To: to, 194 ToAccountType: g.Config.DestinationType, 195 Asset: g.Config.Asset, 196 Amount: amount.String(), 197 Reference: g.Reference, 198 Status: g.Status, 199 Timestamp: g.Timestamp.UnixNano(), 200 Reason: reason, 201 GameId: gameID, 202 } 203 204 if g.Config.OneOffTransferConfig != nil { 205 out.Kind = &eventspb.Transfer_OneOffGovernance{} 206 if g.Config.OneOffTransferConfig.DeliverOn > 0 { 207 out.Kind = &eventspb.Transfer_OneOffGovernance{ 208 OneOffGovernance: &eventspb.OneOffGovernanceTransfer{ 209 DeliverOn: g.Config.OneOffTransferConfig.DeliverOn, 210 }, 211 } 212 } 213 } else { 214 out.Kind = &eventspb.Transfer_RecurringGovernance{ 215 RecurringGovernance: &eventspb.RecurringGovernanceTransfer{ 216 StartEpoch: g.Config.RecurringTransferConfig.StartEpoch, 217 EndEpoch: g.Config.RecurringTransferConfig.EndEpoch, 218 DispatchStrategy: g.Config.RecurringTransferConfig.DispatchStrategy, 219 Factor: g.Config.RecurringTransferConfig.Factor, 220 }, 221 } 222 } 223 224 return out 225 } 226 227 type OneOffTransfer struct { 228 *TransferBase 229 DeliverOn *time.Time 230 } 231 232 func (o *OneOffTransfer) IsValid() error { 233 if err := o.TransferBase.IsValid(); err != nil { 234 return err 235 } 236 237 return nil 238 } 239 240 func OneOffTransferFromEvent(p *eventspb.Transfer) *OneOffTransfer { 241 var deliverOn *time.Time 242 if t := p.GetOneOff().GetDeliverOn(); t > 0 { 243 d := time.Unix(0, t) 244 deliverOn = &d 245 } 246 247 amount, overflow := num.UintFromString(p.Amount, 10) 248 if overflow { 249 // panic is alright here, this should come only from 250 // a checkpoint, and it would mean the checkpoint is fucked 251 // so executions is not possible. 252 panic("invalid transfer amount") 253 } 254 255 return &OneOffTransfer{ 256 TransferBase: &TransferBase{ 257 ID: p.Id, 258 From: p.From, 259 FromAccountType: p.FromAccountType, 260 To: p.To, 261 ToAccountType: p.ToAccountType, 262 Asset: p.Asset, 263 Amount: amount, 264 Reference: p.Reference, 265 Status: p.Status, 266 Timestamp: time.Unix(0, p.Timestamp), 267 }, 268 DeliverOn: deliverOn, 269 } 270 } 271 272 func (o *OneOffTransfer) IntoEvent(reason *string) *eventspb.Transfer { 273 out := &eventspb.Transfer{ 274 Id: o.ID, 275 From: o.From, 276 FromAccountType: o.FromAccountType, 277 To: o.To, 278 ToAccountType: o.ToAccountType, 279 Asset: o.Asset, 280 Amount: o.Amount.String(), 281 Reference: o.Reference, 282 Status: o.Status, 283 Timestamp: o.Timestamp.UnixNano(), 284 Reason: reason, 285 } 286 287 out.Kind = &eventspb.Transfer_OneOff{} 288 if o.DeliverOn != nil { 289 out.Kind = &eventspb.Transfer_OneOff{ 290 OneOff: &eventspb.OneOffTransfer{ 291 DeliverOn: o.DeliverOn.UnixNano(), 292 }, 293 } 294 } 295 296 return out 297 } 298 299 type RecurringTransfer struct { 300 *TransferBase 301 StartEpoch uint64 302 EndEpoch *uint64 303 Factor num.Decimal 304 DispatchStrategy *vegapb.DispatchStrategy 305 } 306 307 func (r *RecurringTransfer) IsValid() error { 308 if err := r.TransferBase.IsValid(); err != nil { 309 return err 310 } 311 312 if r.EndEpoch != nil && *r.EndEpoch == 0 { 313 return ErrEndEpochIsZero 314 } 315 if r.StartEpoch == 0 { 316 return ErrStartEpochIsZero 317 } 318 319 if r.EndEpoch != nil && r.StartEpoch > *r.EndEpoch { 320 return ErrStartEpochAfterEndEpoch 321 } 322 323 if r.Factor.Cmp(num.DecimalFromFloat(0)) <= 0 { 324 return ErrInvalidFactor 325 } 326 327 return nil 328 } 329 330 func (r *RecurringTransfer) IntoEvent(reason *string, gameID *string) *eventspb.Transfer { 331 var endEpoch *uint64 332 if r.EndEpoch != nil { 333 endEpoch = toPtr(*r.EndEpoch) 334 } 335 336 return &eventspb.Transfer{ 337 Id: r.ID, 338 From: r.From, 339 FromAccountType: r.FromAccountType, 340 To: r.To, 341 ToAccountType: r.ToAccountType, 342 Asset: r.Asset, 343 Amount: r.Amount.String(), 344 Reference: r.Reference, 345 Status: r.Status, 346 Timestamp: r.Timestamp.UnixNano(), 347 Reason: reason, 348 GameId: gameID, 349 Kind: &eventspb.Transfer_Recurring{ 350 Recurring: &eventspb.RecurringTransfer{ 351 StartEpoch: r.StartEpoch, 352 EndEpoch: endEpoch, 353 Factor: r.Factor.String(), 354 DispatchStrategy: r.DispatchStrategy, 355 }, 356 }, 357 } 358 } 359 360 // Just a wrapper, use the Kind on a 361 // switch to access the proper value. 362 type TransferFunds struct { 363 Kind TransferCommandKind 364 OneOff *OneOffTransfer 365 Recurring *RecurringTransfer 366 } 367 368 func NewTransferFromProto(id, from string, tf *commandspb.Transfer) (*TransferFunds, error) { 369 base, err := newTransferBase(id, from, tf) 370 if err != nil { 371 return nil, err 372 } 373 switch tf.Kind.(type) { 374 case *commandspb.Transfer_OneOff: 375 return newOneOffTransfer(base, tf) 376 case *commandspb.Transfer_Recurring: 377 return newRecurringTransfer(base, tf) 378 default: 379 return nil, ErrMissingTransferKind 380 } 381 } 382 383 func (t *TransferFunds) IntoEvent(reason, gameID *string) *eventspb.Transfer { 384 switch t.Kind { 385 case TransferCommandKindOneOff: 386 return t.OneOff.IntoEvent(reason) 387 case TransferCommandKindRecurring: 388 return t.Recurring.IntoEvent(reason, gameID) 389 default: 390 panic("invalid transfer kind") 391 } 392 } 393 394 func newTransferBase(id, from string, tf *commandspb.Transfer) (*TransferBase, error) { 395 amount, overflowed := num.UintFromString(tf.Amount, 10) 396 if overflowed { 397 return nil, errors.New("invalid transfer amount") 398 } 399 400 tb := &TransferBase{ 401 ID: id, 402 From: from, 403 FromAccountType: tf.FromAccountType, 404 To: tf.To, 405 ToAccountType: tf.ToAccountType, 406 Asset: tf.Asset, 407 Amount: amount, 408 Reference: tf.Reference, 409 Status: TransferStatusPending, 410 } 411 412 if tf.From != nil { 413 tb.FromDerivedKey = tf.From 414 } 415 416 return tb, nil 417 } 418 419 func newOneOffTransfer(base *TransferBase, tf *commandspb.Transfer) (*TransferFunds, error) { 420 var t *time.Time 421 if tf.GetOneOff().GetDeliverOn() > 0 { 422 tmpt := time.Unix(0, tf.GetOneOff().GetDeliverOn()) 423 t = &tmpt 424 } 425 426 return &TransferFunds{ 427 Kind: TransferCommandKindOneOff, 428 OneOff: &OneOffTransfer{ 429 TransferBase: base, 430 DeliverOn: t, 431 }, 432 }, nil 433 } 434 435 func newRecurringTransfer(base *TransferBase, tf *commandspb.Transfer) (*TransferFunds, error) { 436 factor, err := num.DecimalFromString(tf.GetRecurring().GetFactor()) 437 if err != nil { 438 return nil, err 439 } 440 var endEpoch *uint64 441 if tf.GetRecurring().EndEpoch != nil { 442 ee := tf.GetRecurring().GetEndEpoch() 443 endEpoch = &ee 444 } 445 446 return &TransferFunds{ 447 Kind: TransferCommandKindRecurring, 448 Recurring: &RecurringTransfer{ 449 TransferBase: base, 450 StartEpoch: tf.GetRecurring().GetStartEpoch(), 451 EndEpoch: endEpoch, 452 Factor: factor, 453 DispatchStrategy: tf.GetRecurring().DispatchStrategy, 454 }, 455 }, nil 456 } 457 458 func RecurringTransferFromEvent(p *eventspb.Transfer) *RecurringTransfer { 459 var endEpoch *uint64 460 if p.GetRecurring().EndEpoch != nil { 461 ee := p.GetRecurring().GetEndEpoch() 462 endEpoch = &ee 463 } 464 465 factor, err := num.DecimalFromString(p.GetRecurring().GetFactor()) 466 if err != nil { 467 panic("invalid decimal, should never happen") 468 } 469 470 amount, overflow := num.UintFromString(p.Amount, 10) 471 if overflow { 472 // panic is alright here, this should come only from 473 // a checkpoint, and it would mean the checkpoint is fucked 474 // so executions is not possible. 475 panic("invalid transfer amount") 476 } 477 478 return &RecurringTransfer{ 479 TransferBase: &TransferBase{ 480 ID: p.Id, 481 From: p.From, 482 FromAccountType: p.FromAccountType, 483 To: p.To, 484 ToAccountType: p.ToAccountType, 485 Asset: p.Asset, 486 Amount: amount, 487 Reference: p.Reference, 488 Status: p.Status, 489 Timestamp: time.Unix(0, p.Timestamp), 490 }, 491 StartEpoch: p.GetRecurring().GetStartEpoch(), 492 EndEpoch: endEpoch, 493 Factor: factor, 494 DispatchStrategy: p.GetRecurring().DispatchStrategy, 495 } 496 } 497 498 type CancelTransferFunds struct { 499 Party string 500 TransferID string 501 } 502 503 func NewCancelTransferFromProto(party string, p *commandspb.CancelTransfer) *CancelTransferFunds { 504 return &CancelTransferFunds{ 505 Party: party, 506 TransferID: p.TransferId, 507 } 508 }