code.vegaprotocol.io/vega@v0.79.0/datanode/entities/transfer.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 entities 17 18 import ( 19 "context" 20 "encoding/json" 21 "fmt" 22 "time" 23 24 "code.vegaprotocol.io/vega/libs/ptr" 25 v2 "code.vegaprotocol.io/vega/protos/data-node/api/v2" 26 "code.vegaprotocol.io/vega/protos/vega" 27 eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" 28 29 "github.com/shopspring/decimal" 30 ) 31 32 type AccountSource interface { 33 Obtain(ctx context.Context, a *Account) error 34 GetByID(ctx context.Context, id AccountID) (Account, error) 35 } 36 37 type _Transfer struct{} 38 39 type TransferID = ID[_Transfer] 40 41 type TransferDetails struct { 42 Transfer 43 Fees []*TransferFees 44 } 45 46 type Transfer struct { 47 ID TransferID 48 TxHash TxHash 49 VegaTime time.Time 50 FromAccountID AccountID 51 ToAccountID AccountID 52 AssetID AssetID 53 Amount decimal.Decimal 54 Reference string 55 Status TransferStatus 56 TransferType TransferType 57 DeliverOn *time.Time 58 StartEpoch *uint64 59 EndEpoch *uint64 60 Factor *decimal.Decimal 61 DispatchStrategy *vega.DispatchStrategy 62 Reason *string 63 GameID GameID 64 } 65 66 type TransferFees struct { 67 TransferID TransferID 68 EpochSeq uint64 69 Amount decimal.Decimal 70 DiscountApplied decimal.Decimal 71 VegaTime time.Time 72 } 73 74 type TransferFeesDiscount struct { 75 PartyID PartyID 76 AssetID AssetID 77 Amount decimal.Decimal 78 EpochSeq uint64 79 VegaTime time.Time 80 } 81 82 func (f *TransferFeesDiscount) ToProto() *eventspb.TransferFeesDiscount { 83 return &eventspb.TransferFeesDiscount{ 84 Party: f.PartyID.String(), 85 Asset: f.AssetID.String(), 86 Amount: f.Amount.String(), 87 Epoch: f.EpochSeq, 88 } 89 } 90 91 func TransferFeesDiscountFromProto(f *eventspb.TransferFeesDiscount, vegaTime time.Time) *TransferFeesDiscount { 92 amt, _ := decimal.NewFromString(f.Amount) 93 return &TransferFeesDiscount{ 94 PartyID: PartyID(f.Party), 95 AssetID: AssetID(f.Asset), 96 EpochSeq: f.Epoch, 97 Amount: amt, 98 VegaTime: vegaTime, 99 } 100 } 101 102 func (t *Transfer) ToProto(ctx context.Context, accountSource AccountSource) (*eventspb.Transfer, error) { 103 fromAcc, err := accountSource.GetByID(ctx, t.FromAccountID) 104 if err != nil { 105 return nil, fmt.Errorf("getting from account for transfer proto:%w", err) 106 } 107 108 toAcc, err := accountSource.GetByID(ctx, t.ToAccountID) 109 if err != nil { 110 return nil, fmt.Errorf("getting to account for transfer proto:%w", err) 111 } 112 113 var gameID *string 114 if len(t.GameID) > 0 { 115 gameID = ptr.From(t.GameID.String()) 116 } 117 118 proto := eventspb.Transfer{ 119 Id: t.ID.String(), 120 From: fromAcc.PartyID.String(), 121 FromAccountType: fromAcc.Type, 122 To: toAcc.PartyID.String(), 123 ToAccountType: toAcc.Type, 124 Asset: t.AssetID.String(), 125 Amount: t.Amount.String(), 126 Reference: t.Reference, 127 Status: eventspb.Transfer_Status(t.Status), 128 Timestamp: t.VegaTime.UnixNano(), 129 Kind: nil, 130 Reason: t.Reason, 131 GameId: gameID, 132 } 133 134 switch t.TransferType { 135 case OneOff: 136 proto.Kind = &eventspb.Transfer_OneOff{OneOff: &eventspb.OneOffTransfer{DeliverOn: t.DeliverOn.UnixNano()}} 137 case Recurring: 138 recurringTransfer := &eventspb.RecurringTransfer{ 139 StartEpoch: *t.StartEpoch, 140 Factor: t.Factor.String(), 141 } 142 recurringTransfer.DispatchStrategy = t.DispatchStrategy 143 if t.EndEpoch != nil { 144 endEpoch := *t.EndEpoch 145 recurringTransfer.EndEpoch = &endEpoch 146 } 147 148 proto.Kind = &eventspb.Transfer_Recurring{Recurring: recurringTransfer} 149 case GovernanceOneOff: 150 proto.Kind = &eventspb.Transfer_OneOffGovernance{OneOffGovernance: &eventspb.OneOffGovernanceTransfer{DeliverOn: t.DeliverOn.UnixNano()}} 151 case GovernanceRecurring: 152 recurringTransfer := &eventspb.RecurringGovernanceTransfer{ 153 StartEpoch: *t.StartEpoch, 154 DispatchStrategy: t.DispatchStrategy, 155 } 156 157 if t.EndEpoch != nil { 158 endEpoch := *t.EndEpoch 159 recurringTransfer.EndEpoch = &endEpoch 160 } 161 proto.Kind = &eventspb.Transfer_RecurringGovernance{RecurringGovernance: recurringTransfer} 162 case Unknown: 163 // leave Kind as nil 164 } 165 166 return &proto, nil 167 } 168 169 func (f *TransferFees) ToProto() *eventspb.TransferFees { 170 return &eventspb.TransferFees{ 171 TransferId: f.TransferID.String(), 172 Amount: f.Amount.String(), 173 Epoch: f.EpochSeq, 174 DiscountApplied: f.DiscountApplied.String(), 175 } 176 } 177 178 func TransferFeesFromProto(f *eventspb.TransferFees, vegaTime time.Time) *TransferFees { 179 amt, _ := decimal.NewFromString(f.Amount) 180 discount, _ := decimal.NewFromString(f.DiscountApplied) 181 return &TransferFees{ 182 TransferID: TransferID(f.TransferId), 183 EpochSeq: f.Epoch, 184 Amount: amt, 185 DiscountApplied: discount, 186 VegaTime: vegaTime, 187 } 188 } 189 190 func TransferFromProto(ctx context.Context, t *eventspb.Transfer, txHash TxHash, vegaTime time.Time, accountSource AccountSource) (*Transfer, error) { 191 fromAcc := Account{ 192 ID: "", 193 PartyID: PartyID(t.From), 194 AssetID: AssetID(t.Asset), 195 Type: t.FromAccountType, 196 TxHash: txHash, 197 VegaTime: time.Unix(0, t.Timestamp), 198 } 199 200 if t.From == "0000000000000000000000000000000000000000000000000000000000000000" { 201 fromAcc.PartyID = "network" 202 } 203 204 if err := accountSource.Obtain(ctx, &fromAcc); err != nil { 205 return nil, fmt.Errorf("could not obtain source account for transfer: %w", err) 206 } 207 208 toAcc := Account{ 209 ID: "", 210 PartyID: PartyID(t.To), 211 AssetID: AssetID(t.Asset), 212 Type: t.ToAccountType, 213 TxHash: txHash, 214 VegaTime: vegaTime, 215 } 216 217 if t.To == "0000000000000000000000000000000000000000000000000000000000000000" { 218 toAcc.PartyID = "network" 219 } 220 221 if err := accountSource.Obtain(ctx, &toAcc); err != nil { 222 return nil, fmt.Errorf("could not obtain destination account for transfer: %w", err) 223 } 224 225 amount, err := decimal.NewFromString(t.Amount) 226 if err != nil { 227 return nil, fmt.Errorf("invalid transfer amount: %w", err) 228 } 229 230 var gameID GameID 231 if t.GameId != nil { 232 gameID = GameID(*t.GameId) 233 } 234 235 transfer := Transfer{ 236 ID: TransferID(t.Id), 237 TxHash: txHash, 238 VegaTime: vegaTime, 239 FromAccountID: fromAcc.ID, 240 ToAccountID: toAcc.ID, 241 Amount: amount, 242 AssetID: AssetID(t.Asset), 243 Reference: t.Reference, 244 Status: TransferStatus(t.Status), 245 TransferType: 0, 246 DeliverOn: nil, 247 StartEpoch: nil, 248 EndEpoch: nil, 249 Factor: nil, 250 Reason: t.Reason, 251 GameID: gameID, 252 } 253 254 switch v := t.Kind.(type) { 255 case *eventspb.Transfer_OneOff: 256 transfer.TransferType = OneOff 257 if v.OneOff != nil { 258 deliverOn := time.Unix(0, v.OneOff.DeliverOn) 259 transfer.DeliverOn = &deliverOn 260 } 261 case *eventspb.Transfer_OneOffGovernance: 262 transfer.TransferType = GovernanceOneOff 263 if v.OneOffGovernance != nil { 264 deliverOn := time.Unix(0, v.OneOffGovernance.DeliverOn) 265 transfer.DeliverOn = &deliverOn 266 } 267 case *eventspb.Transfer_RecurringGovernance: 268 transfer.TransferType = GovernanceRecurring 269 transfer.StartEpoch = &v.RecurringGovernance.StartEpoch 270 transfer.DispatchStrategy = v.RecurringGovernance.DispatchStrategy 271 transfer.EndEpoch = v.RecurringGovernance.EndEpoch 272 case *eventspb.Transfer_Recurring: 273 transfer.TransferType = Recurring 274 transfer.StartEpoch = &v.Recurring.StartEpoch 275 transfer.DispatchStrategy = v.Recurring.DispatchStrategy 276 transfer.EndEpoch = v.Recurring.EndEpoch 277 278 factor, err := decimal.NewFromString(v.Recurring.Factor) 279 if err != nil { 280 return nil, fmt.Errorf("invalid factor for recurring transfer:%w", err) 281 } 282 transfer.Factor = &factor 283 default: 284 transfer.TransferType = Unknown 285 } 286 287 return &transfer, nil 288 } 289 290 func (t Transfer) Cursor() *Cursor { 291 wc := TransferCursor{ 292 VegaTime: t.VegaTime, 293 ID: t.ID, 294 } 295 return NewCursor(wc.String()) 296 } 297 298 func (d TransferDetails) ToProtoEdge(input ...any) (*v2.TransferEdge, error) { 299 te, err := d.Transfer.ToProtoEdge(input...) 300 if err != nil { 301 return nil, err 302 } 303 if len(d.Fees) == 0 { 304 return te, nil 305 } 306 te.Node.Fees = make([]*eventspb.TransferFees, 0, len(d.Fees)) 307 for _, f := range d.Fees { 308 te.Node.Fees = append(te.Node.Fees, f.ToProto()) 309 } 310 return te, nil 311 } 312 313 func (t Transfer) ToProtoEdge(input ...any) (*v2.TransferEdge, error) { 314 if len(input) != 2 { 315 return nil, fmt.Errorf("expected account source and context argument") 316 } 317 318 ctx, ok := input[0].(context.Context) 319 if !ok { 320 return nil, fmt.Errorf("first argument must be a context.Context, got: %v", input[0]) 321 } 322 323 as, ok := input[1].(AccountSource) 324 if !ok { 325 return nil, fmt.Errorf("second argument must be an AccountSource, got: %v", input[1]) 326 } 327 328 transferProto, err := t.ToProto(ctx, as) 329 if err != nil { 330 return nil, err 331 } 332 return &v2.TransferEdge{ 333 Node: &v2.TransferNode{ 334 Transfer: transferProto, 335 }, 336 Cursor: t.Cursor().Encode(), 337 }, nil 338 } 339 340 type TransferCursor struct { 341 VegaTime time.Time `json:"vegaTime"` 342 ID TransferID `json:"id"` 343 } 344 345 func (tc TransferCursor) String() string { 346 bs, err := json.Marshal(tc) 347 if err != nil { 348 // This should never happen 349 panic(fmt.Errorf("failed to marshal withdrawal cursor: %w", err)) 350 } 351 return string(bs) 352 } 353 354 func (tc *TransferCursor) Parse(cursorString string) error { 355 if cursorString == "" { 356 return nil 357 } 358 return json.Unmarshal([]byte(cursorString), tc) 359 }