code.vegaprotocol.io/vega@v0.79.0/core/banking/transfers_common.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 banking
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"time"
    22  
    23  	"code.vegaprotocol.io/vega/core/assets"
    24  	"code.vegaprotocol.io/vega/core/events"
    25  	"code.vegaprotocol.io/vega/core/types"
    26  	"code.vegaprotocol.io/vega/libs/num"
    27  	"code.vegaprotocol.io/vega/logging"
    28  )
    29  
    30  func (e *Engine) OnRewardsUpdateFrequencyUpdate(ctx context.Context, d time.Duration) error {
    31  	if !e.nextMetricUpdate.IsZero() {
    32  		e.nextMetricUpdate = e.nextMetricUpdate.Add(-e.metricUpdateFrequency)
    33  	}
    34  	e.nextMetricUpdate = e.nextMetricUpdate.Add(d)
    35  	e.metricUpdateFrequency = d
    36  	return nil
    37  }
    38  
    39  func (e *Engine) OnTransferFeeFactorUpdate(ctx context.Context, f num.Decimal) error {
    40  	e.transferFeeFactor = f
    41  	return nil
    42  }
    43  
    44  func (e *Engine) OnMinTransferQuantumMultiple(ctx context.Context, f num.Decimal) error {
    45  	e.minTransferQuantumMultiple = f
    46  	return nil
    47  }
    48  
    49  func (e *Engine) OnMaxQuantumAmountUpdate(ctx context.Context, f num.Decimal) error {
    50  	e.maxQuantumAmount = f
    51  	return nil
    52  }
    53  
    54  func (e *Engine) OnTransferFeeDiscountDecayFractionUpdate(ctx context.Context, v num.Decimal) error {
    55  	e.feeDiscountDecayFraction = v
    56  	return nil
    57  }
    58  
    59  func (e *Engine) OnTransferFeeDiscountMinimumTrackedAmountUpdate(ctx context.Context, v num.Decimal) error {
    60  	e.feeDiscountMinimumTrackedAmount = v
    61  	return nil
    62  }
    63  
    64  func (e *Engine) TransferFunds(
    65  	ctx context.Context,
    66  	transfer *types.TransferFunds,
    67  ) error {
    68  	now := e.timeService.GetTimeNow()
    69  	// add timestamps straight away
    70  	switch transfer.Kind {
    71  	case types.TransferCommandKindOneOff:
    72  		transfer.OneOff.Timestamp = now
    73  		return e.oneOffTransfer(ctx, transfer.OneOff)
    74  	case types.TransferCommandKindRecurring:
    75  		transfer.Recurring.Timestamp = now
    76  		return e.recurringTransfer(ctx, transfer.Recurring)
    77  	default:
    78  		return ErrUnsupportedTransferKind
    79  	}
    80  }
    81  
    82  func (e *Engine) CheckTransfer(t *types.TransferBase) error {
    83  	// ensure asset exists
    84  	a, err := e.assets.Get(t.Asset)
    85  	if err != nil {
    86  		e.log.Debug("cannot transfer funds, invalid asset", logging.Error(err))
    87  		return fmt.Errorf("could not transfer funds, %w", err)
    88  	}
    89  
    90  	if err = t.IsValid(); err != nil {
    91  		return fmt.Errorf("could not transfer funds, %w", err)
    92  	}
    93  
    94  	if err := e.ensureMinimalTransferAmount(a, t.Amount, t.FromAccountType, t.From, t.FromDerivedKey); err != nil {
    95  		return err
    96  	}
    97  
    98  	if err = e.ensureFeeForTransferFunds(a, t.Amount, t.From, t.FromAccountType, t.FromDerivedKey, t.To, t.ToAccountType); err != nil {
    99  		return fmt.Errorf("could not transfer funds, %w", err)
   100  	}
   101  	return nil
   102  }
   103  
   104  func (e *Engine) ensureMinimalTransferAmount(
   105  	a *assets.Asset,
   106  	amount *num.Uint,
   107  	fromAccType types.AccountType,
   108  	from string,
   109  	fromSubAccount *string,
   110  ) error {
   111  	quantum := a.Type().Details.Quantum
   112  	// no reason this would produce an error
   113  	minAmount, _ := num.UintFromDecimal(quantum.Mul(e.minTransferQuantumMultiple))
   114  
   115  	// no verify amount
   116  	if amount.LT(minAmount) {
   117  		if fromAccType == types.AccountTypeVestedRewards {
   118  			if fromSubAccount != nil {
   119  				from = *fromSubAccount
   120  			}
   121  			return e.ensureMinimalTransferAmountFromVested(amount, from, a.Type().ID)
   122  		}
   123  
   124  		e.log.Debug("cannot transfer funds, less than minimal amount requested to transfer",
   125  			logging.BigUint("min-amount", minAmount),
   126  			logging.BigUint("requested-amount", amount),
   127  		)
   128  		return fmt.Errorf("could not transfer funds, less than minimal amount requested to transfer")
   129  	}
   130  
   131  	return nil
   132  }
   133  
   134  func (e *Engine) ensureMinimalTransferAmountFromVested(
   135  	transferAmount *num.Uint,
   136  	from, asset string,
   137  ) error {
   138  	account, err := e.col.GetPartyVestedRewardAccount(from, asset)
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	if transferAmount.EQ(account.Balance) {
   144  		return nil
   145  	}
   146  
   147  	return fmt.Errorf("transfer from vested account under minimal transfer amount must be the full balance")
   148  }
   149  
   150  func (e *Engine) processTransfer(
   151  	ctx context.Context,
   152  	asset *assets.Asset,
   153  	from, to, toMarket string,
   154  	fromAcc, toAcc types.AccountType,
   155  	amount *num.Uint,
   156  	reference string,
   157  	transferID string,
   158  	epoch uint64,
   159  	// optional from derived key transfer
   160  	fromDerivedKey *string,
   161  	// optional oneoff transfer
   162  	// in case we need to schedule the delivery
   163  	oneoff *types.OneOffTransfer,
   164  ) ([]*types.LedgerMovement, error) {
   165  	assetType := asset.ToAssetType()
   166  
   167  	// ensure the party have enough funds for both the
   168  	// amount and the fee for the transfer
   169  	feeTransfer, discount, err := e.makeFeeTransferForFundsTransfer(ctx, assetType, amount, from, fromAcc, fromDerivedKey, to, toAcc)
   170  	if err != nil {
   171  		return nil, fmt.Errorf("could not pay the fee for transfer: %w", err)
   172  	}
   173  	feeTransferAccountType := []types.AccountType{fromAcc}
   174  
   175  	// transfer from sub account to owners general account
   176  	if fromDerivedKey != nil {
   177  		from = *fromDerivedKey
   178  	}
   179  
   180  	fromTransfer, toTransfer := e.makeTransfers(from, to, assetType.ID, "", toMarket, amount, &transferID)
   181  	transfers := []*types.Transfer{fromTransfer}
   182  	accountTypes := []types.AccountType{fromAcc}
   183  	references := []string{reference}
   184  
   185  	// does the transfer needs to be finalized now?
   186  	now := e.timeService.GetTimeNow()
   187  	if oneoff == nil || (oneoff.DeliverOn == nil || oneoff.DeliverOn.Before(now)) {
   188  		transfers = append(transfers, toTransfer)
   189  		accountTypes = append(accountTypes, toAcc)
   190  		references = append(references, reference)
   191  		// if this goes well the whole transfer will be done
   192  		// so we can set it to the proper status
   193  	} else {
   194  		// schedule the transfer
   195  		e.scheduleTransfer(
   196  			oneoff,
   197  			toTransfer,
   198  			toAcc,
   199  			reference,
   200  			*oneoff.DeliverOn,
   201  		)
   202  	}
   203  
   204  	// process the transfer
   205  	tresps, err := e.col.TransferFunds(
   206  		ctx, transfers, accountTypes, references, []*types.Transfer{feeTransfer}, feeTransferAccountType,
   207  	)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	e.broker.Send(events.NewTransferFeesEvent(ctx, transferID, feeTransfer.Amount.Amount, discount, epoch))
   213  
   214  	return tresps, nil
   215  }
   216  
   217  func (e *Engine) makeTransfers(
   218  	from, to, asset, fromMarket, toMarket string,
   219  	amount *num.Uint,
   220  	transferID *string,
   221  ) (*types.Transfer, *types.Transfer) {
   222  	return &types.Transfer{
   223  			Owner: from,
   224  			Amount: &types.FinancialAmount{
   225  				Amount: amount.Clone(),
   226  				Asset:  asset,
   227  			},
   228  			Type:       types.TransferTypeTransferFundsSend,
   229  			MinAmount:  amount.Clone(),
   230  			Market:     fromMarket,
   231  			TransferID: transferID,
   232  		}, &types.Transfer{
   233  			Owner: to,
   234  			Amount: &types.FinancialAmount{
   235  				Amount: amount.Clone(),
   236  				Asset:  asset,
   237  			},
   238  			Type:       types.TransferTypeTransferFundsDistribute,
   239  			MinAmount:  amount.Clone(),
   240  			Market:     toMarket,
   241  			TransferID: transferID,
   242  		}
   243  }
   244  
   245  func (e *Engine) calculateFeeTransferForTransfer(
   246  	asset *types.Asset,
   247  	amount *num.Uint,
   248  	from string,
   249  	fromAccountType types.AccountType,
   250  	fromDerivedKey *string,
   251  	to string,
   252  	toAccountType types.AccountType,
   253  ) *num.Uint {
   254  	return calculateFeeForTransfer(
   255  		asset.Details.Quantum,
   256  		e.maxQuantumAmount,
   257  		e.transferFeeFactor,
   258  		amount,
   259  		from,
   260  		fromAccountType,
   261  		fromDerivedKey,
   262  		to,
   263  		toAccountType,
   264  	)
   265  }
   266  
   267  func (e *Engine) makeFeeTransferForFundsTransfer(
   268  	ctx context.Context,
   269  	asset *types.Asset,
   270  	amount *num.Uint,
   271  	from string,
   272  	fromAccountType types.AccountType,
   273  	fromDerivedKey *string,
   274  	to string,
   275  	toAccountType types.AccountType,
   276  ) (*types.Transfer, *num.Uint, error) {
   277  	theoreticalFee := e.calculateFeeTransferForTransfer(asset, amount, from, fromAccountType, fromDerivedKey, to, toAccountType)
   278  	feeAmount, discountAmount := e.ApplyFeeDiscount(ctx, asset.ID, from, theoreticalFee)
   279  
   280  	if err := e.ensureEnoughFundsForTransfer(asset, amount, from, fromAccountType, fromDerivedKey, feeAmount); err != nil {
   281  		return nil, nil, err
   282  	}
   283  
   284  	switch fromAccountType {
   285  	case types.AccountTypeGeneral, types.AccountTypeVestedRewards, types.AccountTypeLockedForStaking:
   286  	default:
   287  		e.log.Panic("from account not supported",
   288  			logging.String("account-type", fromAccountType.String()),
   289  			logging.String("asset", asset.ID),
   290  			logging.String("from", from),
   291  		)
   292  	}
   293  
   294  	return &types.Transfer{
   295  		Owner: from,
   296  		Amount: &types.FinancialAmount{
   297  			Amount: feeAmount.Clone(),
   298  			Asset:  asset.ID,
   299  		},
   300  		Type:      types.TransferTypeInfrastructureFeePay,
   301  		MinAmount: feeAmount,
   302  	}, discountAmount, nil
   303  }
   304  
   305  func (e *Engine) ensureFeeForTransferFunds(
   306  	asset *assets.Asset,
   307  	amount *num.Uint,
   308  	from string,
   309  	fromAccountType types.AccountType,
   310  	fromDerivedKey *string,
   311  	to string,
   312  	toAccountType types.AccountType,
   313  ) error {
   314  	assetType := asset.ToAssetType()
   315  	theoreticalFee := e.calculateFeeTransferForTransfer(assetType, amount, from, fromAccountType, fromDerivedKey, to, toAccountType)
   316  	feeAmount, _ := e.EstimateFeeDiscount(assetType.ID, from, theoreticalFee)
   317  	return e.ensureEnoughFundsForTransfer(assetType, amount, from, fromAccountType, fromDerivedKey, feeAmount)
   318  }
   319  
   320  func (e *Engine) ensureEnoughFundsForTransfer(
   321  	asset *types.Asset,
   322  	amount *num.Uint,
   323  	from string,
   324  	fromAccountType types.AccountType,
   325  	fromDerivedKey *string,
   326  	feeAmount *num.Uint,
   327  ) error {
   328  	var (
   329  		totalAmount = num.Sum(feeAmount, amount)
   330  		account     *types.Account
   331  		err         error
   332  	)
   333  	switch fromAccountType {
   334  	case types.AccountTypeGeneral:
   335  		account, err = e.col.GetPartyGeneralAccount(from, asset.ID)
   336  		if err != nil {
   337  			return err
   338  		}
   339  	case types.AccountTypeLockedForStaking:
   340  		account, err = e.col.GetPartyLockedForStaking(from, asset.ID)
   341  		if err != nil {
   342  			return err
   343  		}
   344  	case types.AccountTypeVestedRewards:
   345  		// sending from sub account to owners general account
   346  		if fromDerivedKey != nil {
   347  			from = *fromDerivedKey
   348  		}
   349  
   350  		account, err = e.col.GetPartyVestedRewardAccount(from, asset.ID)
   351  		if err != nil {
   352  			return err
   353  		}
   354  
   355  	default:
   356  		e.log.Panic("from account not supported",
   357  			logging.String("account-type", fromAccountType.String()),
   358  			logging.String("asset", asset.ID),
   359  			logging.String("from", from),
   360  		)
   361  	}
   362  
   363  	if account.Balance.LT(totalAmount) {
   364  		e.log.Debug("not enough funds to transfer",
   365  			logging.BigUint("amount", amount),
   366  			logging.BigUint("fee", feeAmount),
   367  			logging.BigUint("total-amount", totalAmount),
   368  			logging.BigUint("account-balance", account.Balance),
   369  			logging.String("account-type", fromAccountType.String()),
   370  			logging.String("asset", asset.ID),
   371  			logging.String("from", from),
   372  		)
   373  		return ErrNotEnoughFundsToTransfer
   374  	}
   375  	return nil
   376  }