Skip to content

Commit

Permalink
Merge pull request #9984 from vegaprotocol/feature/9982
Browse files Browse the repository at this point in the history
feat: remove fee and minimal amount on transfer from vested account
  • Loading branch information
jeremyletang committed Nov 6, 2023
1 parent ff33565 commit 43d421a
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 10 deletions.
2 changes: 1 addition & 1 deletion core/banking/oneoff_transfers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
6 changes: 3 additions & 3 deletions core/banking/recurring_transfers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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()))
Expand All @@ -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
Expand Down
46 changes: 46 additions & 0 deletions core/banking/transfer_common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)
}
44 changes: 38 additions & 6 deletions core/banking/transfers_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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,
Expand All @@ -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)
}
Expand Down Expand Up @@ -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:
Expand All @@ -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 (
Expand Down

0 comments on commit 43d421a

Please sign in to comment.