code.vegaprotocol.io/vega@v0.79.0/core/banking/oneoff_transfers.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  	"errors"
    21  	"fmt"
    22  	"sort"
    23  	"time"
    24  
    25  	"code.vegaprotocol.io/vega/core/events"
    26  	"code.vegaprotocol.io/vega/core/types"
    27  	vgcontext "code.vegaprotocol.io/vega/libs/context"
    28  	"code.vegaprotocol.io/vega/libs/crypto"
    29  	"code.vegaprotocol.io/vega/logging"
    30  	checkpoint "code.vegaprotocol.io/vega/protos/vega/checkpoint/v1"
    31  )
    32  
    33  var ErrUnsupportedTransferKind = errors.New("unsupported transfer kind")
    34  
    35  type scheduledTransfer struct {
    36  	// to send events
    37  	oneoff      *types.OneOffTransfer
    38  	transfer    *types.Transfer
    39  	accountType types.AccountType
    40  	reference   string
    41  }
    42  
    43  func (s *scheduledTransfer) ToProto() *checkpoint.ScheduledTransfer {
    44  	return &checkpoint.ScheduledTransfer{
    45  		OneoffTransfer: s.oneoff.IntoEvent(nil),
    46  		Transfer:       s.transfer.IntoProto(),
    47  		AccountType:    s.accountType,
    48  		Reference:      s.reference,
    49  	}
    50  }
    51  
    52  func scheduledTransferFromProto(p *checkpoint.ScheduledTransfer) (scheduledTransfer, error) {
    53  	transfer, err := types.TransferFromProto(p.Transfer)
    54  	if err != nil {
    55  		return scheduledTransfer{}, err
    56  	}
    57  
    58  	return scheduledTransfer{
    59  		oneoff:      types.OneOffTransferFromEvent(p.OneoffTransfer),
    60  		transfer:    transfer,
    61  		accountType: p.AccountType,
    62  		reference:   p.Reference,
    63  	}, nil
    64  }
    65  
    66  func (e *Engine) updateStakingAccounts(
    67  	ctx context.Context, transfer *types.OneOffTransfer,
    68  ) {
    69  	if transfer.Asset != e.stakingAsset {
    70  		// nothing to do
    71  		return
    72  	}
    73  
    74  	var (
    75  		now          = e.timeService.GetTimeNow().Unix()
    76  		height, _    = vgcontext.BlockHeightFromContext(ctx)
    77  		txhash, _    = vgcontext.TxHashFromContext(ctx)
    78  		id           = crypto.HashStrToHex(fmt.Sprintf("%v%v", txhash, height))
    79  		stakeLinking *types.StakeLinking
    80  	)
    81  
    82  	// manually send funds from the general account to the locked for staking
    83  	if transfer.FromAccountType == types.AccountTypeGeneral && transfer.ToAccountType == types.AccountTypeLockedForStaking {
    84  		stakeLinking = &types.StakeLinking{
    85  			ID:              id,
    86  			Type:            types.StakeLinkingTypeDeposited,
    87  			TS:              now,
    88  			Party:           transfer.From,
    89  			Amount:          transfer.Amount.Clone(),
    90  			Status:          types.StakeLinkingStatusAccepted,
    91  			FinalizedAt:     now,
    92  			TxHash:          txhash,
    93  			BlockHeight:     height,
    94  			BlockTime:       now,
    95  			LogIndex:        1,
    96  			EthereumAddress: "",
    97  		}
    98  	}
    99  
   100  	// from staking account or vested rewards, we send a remove event
   101  	if (transfer.FromAccountType == types.AccountTypeLockedForStaking && transfer.ToAccountType == types.AccountTypeGeneral) ||
   102  		(transfer.FromAccountType == types.AccountTypeVestedRewards && transfer.ToAccountType == types.AccountTypeGeneral) {
   103  		stakeLinking = &types.StakeLinking{
   104  			ID:              id,
   105  			Type:            types.StakeLinkingTypeRemoved,
   106  			TS:              now,
   107  			Party:           transfer.From,
   108  			Amount:          transfer.Amount.Clone(),
   109  			Status:          types.StakeLinkingStatusAccepted,
   110  			FinalizedAt:     now,
   111  			TxHash:          txhash,
   112  			BlockHeight:     height,
   113  			BlockTime:       now,
   114  			LogIndex:        1,
   115  			EthereumAddress: "",
   116  		}
   117  	}
   118  
   119  	if stakeLinking != nil {
   120  		e.stakeAccounting.AddEvent(ctx, stakeLinking)
   121  		e.broker.Send(events.NewStakeLinking(ctx, *stakeLinking))
   122  	}
   123  }
   124  
   125  func (e *Engine) oneOffTransfer(
   126  	ctx context.Context,
   127  	transfer *types.OneOffTransfer,
   128  ) (err error) {
   129  	defer func() {
   130  		if err != nil {
   131  			e.broker.Send(events.NewOneOffTransferFundsEventWithReason(ctx, transfer, err.Error()))
   132  		} else {
   133  			e.broker.Send(events.NewOneOffTransferFundsEvent(ctx, transfer))
   134  			e.updateStakingAccounts(ctx, transfer)
   135  		}
   136  	}()
   137  
   138  	// ensure asset exists
   139  	a, err := e.assets.Get(transfer.Asset)
   140  	if err != nil {
   141  		transfer.Status = types.TransferStatusRejected
   142  		e.log.Debug("cannot transfer funds, invalid asset", logging.Error(err))
   143  		return fmt.Errorf("could not transfer funds: %w", err)
   144  	}
   145  
   146  	if err := transfer.IsValid(); err != nil {
   147  		transfer.Status = types.TransferStatusRejected
   148  		return err
   149  	}
   150  
   151  	if transfer.FromDerivedKey != nil {
   152  		if ownsDerivedKey := e.parties.CheckDerivedKeyOwnership(types.PartyID(transfer.From), *transfer.FromDerivedKey); !ownsDerivedKey {
   153  			transfer.Status = types.TransferStatusRejected
   154  			return fmt.Errorf("party %s does not own derived key %s", transfer.From, *transfer.FromDerivedKey)
   155  		}
   156  	}
   157  
   158  	if err := e.ensureMinimalTransferAmount(a, transfer.Amount, transfer.FromAccountType, transfer.From, transfer.FromDerivedKey); err != nil {
   159  		transfer.Status = types.TransferStatusRejected
   160  		return err
   161  	}
   162  
   163  	tresps, err := e.processTransfer(
   164  		ctx, a, transfer.From, transfer.To, "", transfer.FromAccountType,
   165  		transfer.ToAccountType, transfer.Amount, transfer.Reference, transfer.ID, e.currentEpoch, transfer.FromDerivedKey,
   166  		transfer,
   167  	)
   168  	if err != nil {
   169  		transfer.Status = types.TransferStatusRejected
   170  		return err
   171  	}
   172  
   173  	// all was OK
   174  	transfer.Status = types.TransferStatusDone
   175  	e.broker.Send(events.NewLedgerMovements(ctx, tresps))
   176  
   177  	return nil
   178  }
   179  
   180  type timesToTransfers struct {
   181  	deliverOn int64
   182  	transfer  []scheduledTransfer
   183  }
   184  
   185  func (e *Engine) distributeScheduledTransfers(ctx context.Context, now time.Time) error {
   186  	ttfs := []timesToTransfers{}
   187  
   188  	// iterate over those scheduled transfers to sort them by time
   189  	for k, v := range e.scheduledTransfers {
   190  		if now.UnixNano() >= k {
   191  			ttfs = append(ttfs, timesToTransfers{k, v})
   192  			delete(e.scheduledTransfers, k)
   193  		}
   194  	}
   195  
   196  	// sort slice by time.
   197  	// no need to sort transfers they are going out as first in first out.
   198  	sort.SliceStable(ttfs, func(i, j int) bool {
   199  		return ttfs[i].deliverOn < ttfs[j].deliverOn
   200  	})
   201  
   202  	transfers := []*types.Transfer{}
   203  	accountTypes := []types.AccountType{}
   204  	references := []string{}
   205  	evts := []events.Event{}
   206  	for _, v := range ttfs {
   207  		for _, t := range v.transfer {
   208  			t.oneoff.Status = types.TransferStatusDone
   209  			evts = append(evts, events.NewOneOffTransferFundsEvent(ctx, t.oneoff))
   210  			transfers = append(transfers, t.transfer)
   211  			accountTypes = append(accountTypes, t.accountType)
   212  			references = append(references, t.reference)
   213  		}
   214  	}
   215  
   216  	if len(transfers) <= 0 {
   217  		// nothing to do yeay
   218  		return nil
   219  	}
   220  
   221  	// at least 1 transfer updated, set to true
   222  	tresps, err := e.col.TransferFunds(
   223  		ctx, transfers, accountTypes, references, nil, nil, // no fees required there, they've been paid already
   224  	)
   225  	if err != nil {
   226  		return err
   227  	}
   228  
   229  	e.broker.Send(events.NewLedgerMovements(ctx, tresps))
   230  	e.broker.SendBatch(evts)
   231  
   232  	return nil
   233  }
   234  
   235  func (e *Engine) scheduleTransfer(
   236  	oneoff *types.OneOffTransfer,
   237  	t *types.Transfer,
   238  	ty types.AccountType,
   239  	reference string,
   240  	deliverOn time.Time,
   241  ) {
   242  	sts, ok := e.scheduledTransfers[deliverOn.UnixNano()]
   243  	if !ok {
   244  		e.scheduledTransfers[deliverOn.UnixNano()] = []scheduledTransfer{}
   245  		sts = e.scheduledTransfers[deliverOn.UnixNano()]
   246  	}
   247  
   248  	sts = append(sts, scheduledTransfer{
   249  		oneoff:      oneoff,
   250  		transfer:    t,
   251  		accountType: ty,
   252  		reference:   reference,
   253  	})
   254  	e.scheduledTransfers[deliverOn.UnixNano()] = sts
   255  }