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  }