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  }