From 43d421aca851a6422f6a948a5d2e4814d13a2673 Mon Sep 17 00:00:00 2001 From: Jeremy Letang Date: Mon, 6 Nov 2023 12:09:05 +0100 Subject: [PATCH] Merge pull request #9984 from vegaprotocol/feature/9982 feat: remove fee and minimal amount on transfer from vested account --- core/banking/oneoff_transfers.go | 2 +- core/banking/recurring_transfers.go | 6 ++-- core/banking/transfer_common_test.go | 46 ++++++++++++++++++++++++++++ core/banking/transfers_common.go | 44 ++++++++++++++++++++++---- 4 files changed, 88 insertions(+), 10 deletions(-) diff --git a/core/banking/oneoff_transfers.go b/core/banking/oneoff_transfers.go index ddf6fa5851..cacd3254d3 100644 --- a/core/banking/oneoff_transfers.go +++ b/core/banking/oneoff_transfers.go @@ -86,7 +86,7 @@ func (e *Engine) oneOffTransfer( return err } - if err := e.ensureMinimalTransferAmount(a, transfer.Amount); err != nil { + if err := e.ensureMinimalTransferAmount(a, transfer.Amount, transfer.FromAccountType, transfer.From); err != nil { transfer.Status = types.TransferStatusRejected return err } diff --git a/core/banking/recurring_transfers.go b/core/banking/recurring_transfers.go index 653180760a..8f67f370e8 100644 --- a/core/banking/recurring_transfers.go +++ b/core/banking/recurring_transfers.go @@ -87,7 +87,7 @@ func (e *Engine) recurringTransfer( return err } - if err := e.ensureMinimalTransferAmount(a, transfer.Amount); err != nil { + if err := e.ensureMinimalTransferAmount(a, transfer.Amount, transfer.FromAccountType, transfer.From); err != nil { transfer.Status = types.TransferStatusRejected return err } @@ -238,7 +238,7 @@ func (e *Engine) distributeRecurringTransfers(ctx context.Context, newEpoch uint e.log.Panic("this should never happen", logging.Error(err)) } - if err = e.ensureMinimalTransferAmount(a, amount); err != nil { + if err = e.ensureMinimalTransferAmount(a, amount, v.FromAccountType, v.From); err != nil { v.Status = types.TransferStatusStopped transfersDone = append(transfersDone, events.NewRecurringTransferFundsEventWithReason(ctx, v, err.Error())) @@ -256,7 +256,7 @@ func (e *Engine) distributeRecurringTransfers(ctx context.Context, newEpoch uint ) } else { // check if the amount + fees can be covered by the party issuing the transfer - if _, err = e.ensureFeeForTransferFunds(amount, v.From, v.Asset, v.FromAccountType); err == nil { + if _, err = e.ensureFeeForTransferFunds(amount, v.From, v.Asset, v.FromAccountType, v.To); err == nil { // NB: if the metric is market value we're going to transfer the bonus if any directly // to the market account of the asset/reward type - this is similar to previous behaviour and // different to how all other metric based rewards behave. The reason is that we need the context of the funder diff --git a/core/banking/transfer_common_test.go b/core/banking/transfer_common_test.go index 5dc89ac3e6..605ae842ed 100644 --- a/core/banking/transfer_common_test.go +++ b/core/banking/transfer_common_test.go @@ -80,3 +80,49 @@ func TestCheckTransfer(t *testing.T) { transfer.Amount = num.NewUint(200) require.EqualError(t, e.CheckTransfer(transfer), "could not transfer funds, not enough funds to transfer") } + +func TestCheckTransferWithVestedAccount(t *testing.T) { + e := getTestEngine(t) + + transfer := &types.TransferBase{ + From: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", + FromAccountType: types.AccountTypeVestedRewards, + To: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", + ToAccountType: types.AccountTypeGeneral, + Asset: "eth", + Amount: num.NewUint(10), + Reference: "someref", + } + + e.OnMinTransferQuantumMultiple(context.Background(), num.DecimalFromFloat(1)) + + // balance is under the min amount + e.col.EXPECT().GetPartyVestedRewardAccount(gomock.Any(), gomock.Any()).Return(&types.Account{Balance: num.NewUint(90)}, nil).Times(1) + + // asset exists + e.assets.EXPECT().Get(gomock.Any()).Times(1).Return(assets.NewAsset(&mockAsset{num.DecimalFromFloat(100)}), nil) + // try to transfer a small balance, but not the whole balance + require.EqualError(t, + e.CheckTransfer(transfer), + "transfer from vested account under minimal transfer amount must be the full balance", + ) + + // now we try to transfre the full amount + e.col.EXPECT().GetPartyVestedRewardAccount(gomock.Any(), gomock.Any()).Return(&types.Account{Balance: num.NewUint(90)}, nil).Times(2) + transfer.Amount = num.NewUint(90) + e.assets.EXPECT().Get(gomock.Any()).Times(1).Return(assets.NewAsset(&mockAsset{num.DecimalFromFloat(100)}), nil) + require.NoError(t, + e.CheckTransfer(transfer), + ) + + // now we try again, with a balance above the min amount, but not the whole balance + + e.col.EXPECT().GetPartyVestedRewardAccount(gomock.Any(), gomock.Any()).Return(&types.Account{Balance: num.NewUint(300)}, nil).Times(1) + e.assets.EXPECT().Get(gomock.Any()).Times(1).Return(assets.NewAsset(&mockAsset{num.DecimalFromFloat(100)}), nil) + + transfer.Amount = num.NewUint(110) + // try to transfer a small balance, but not the whole balance + require.NoError(t, + e.CheckTransfer(transfer), + ) +} diff --git a/core/banking/transfers_common.go b/core/banking/transfers_common.go index c20537c744..43d6e07154 100644 --- a/core/banking/transfers_common.go +++ b/core/banking/transfers_common.go @@ -66,24 +66,33 @@ func (e *Engine) CheckTransfer(t *types.TransferBase) error { return fmt.Errorf("could not transfer funds, %w", err) } - if err := e.ensureMinimalTransferAmount(a, t.Amount); err != nil { + if err := e.ensureMinimalTransferAmount(a, t.Amount, t.FromAccountType, t.From); err != nil { return err } - _, err = e.ensureFeeForTransferFunds(t.Amount, t.From, t.Asset, t.FromAccountType) + _, err = e.ensureFeeForTransferFunds(t.Amount, t.From, t.Asset, t.FromAccountType, t.To) if err != nil { return fmt.Errorf("could not transfer funds, %w", err) } return nil } -func (e *Engine) ensureMinimalTransferAmount(a *assets.Asset, amount *num.Uint) error { +func (e *Engine) ensureMinimalTransferAmount( + a *assets.Asset, + amount *num.Uint, + fromAccType types.AccountType, + from string, +) error { quantum := a.Type().Details.Quantum // no reason this would produce an error minAmount, _ := num.UintFromDecimal(quantum.Mul(e.minTransferQuantumMultiple)) // no verify amount if amount.LT(minAmount) { + if fromAccType == types.AccountTypeVestedRewards { + return e.ensureMinimalTransferAmountFromVested(amount, from, a.Type().ID) + } + e.log.Debug("cannot transfer funds, less than minimal amount requested to transfer", logging.BigUint("min-amount", minAmount), logging.BigUint("requested-amount", amount), @@ -94,6 +103,22 @@ func (e *Engine) ensureMinimalTransferAmount(a *assets.Asset, amount *num.Uint) return nil } +func (e *Engine) ensureMinimalTransferAmountFromVested( + transferAmount *num.Uint, + from, asset string, +) error { + account, err := e.col.GetPartyVestedRewardAccount(from, asset) + if err != nil { + return err + } + + if transferAmount.EQ(account.Balance) { + return nil + } + + return fmt.Errorf("transfer from vested account under minimal transfer amount must be the full balance") +} + func (e *Engine) processTransfer( ctx context.Context, from, to, asset, toMarket string, @@ -108,7 +133,7 @@ func (e *Engine) processTransfer( ) ([]*types.LedgerMovement, error) { // ensure the party have enough funds for both the // amount and the fee for the transfer - feeTransfer, err := e.ensureFeeForTransferFunds(amount, from, asset, fromAcc) + feeTransfer, err := e.ensureFeeForTransferFunds(amount, from, asset, fromAcc, to) if err != nil { return nil, fmt.Errorf("could not pay the fee for transfer: %w", err) } @@ -179,9 +204,15 @@ func (e *Engine) makeFeeTransferForTransferFunds( amount *num.Uint, from, asset string, fromAccountType types.AccountType, + to string, ) *types.Transfer { + // no fee for Vested account + feeAmount := num.UintZero() + // first we calculate the fee - feeAmount, _ := num.UintFromDecimal(amount.ToDecimal().Mul(e.transferFeeFactor)) + if !(fromAccountType == types.AccountTypeVestedRewards && from == to) { + feeAmount, _ = num.UintFromDecimal(amount.ToDecimal().Mul(e.transferFeeFactor)) + } switch fromAccountType { case types.AccountTypeGeneral, types.AccountTypeVestedRewards: @@ -208,9 +239,10 @@ func (e *Engine) ensureFeeForTransferFunds( amount *num.Uint, from, asset string, fromAccountType types.AccountType, + to string, ) (*types.Transfer, error) { transfer := e.makeFeeTransferForTransferFunds( - amount, from, asset, fromAccountType, + amount, from, asset, fromAccountType, to, ) var (