code.vegaprotocol.io/vega@v0.79.0/core/banking/recurring_transfers_test.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_test
    17  
    18  import (
    19  	"context"
    20  	"encoding/hex"
    21  	"fmt"
    22  	"testing"
    23  
    24  	"code.vegaprotocol.io/vega/core/assets"
    25  	"code.vegaprotocol.io/vega/core/banking"
    26  	"code.vegaprotocol.io/vega/core/events"
    27  	"code.vegaprotocol.io/vega/core/types"
    28  	"code.vegaprotocol.io/vega/libs/crypto"
    29  	"code.vegaprotocol.io/vega/libs/num"
    30  	"code.vegaprotocol.io/vega/libs/proto"
    31  	"code.vegaprotocol.io/vega/protos/vega"
    32  
    33  	"github.com/golang/mock/gomock"
    34  	"github.com/stretchr/testify/assert"
    35  	"github.com/stretchr/testify/require"
    36  )
    37  
    38  func TestRecurringTransfers(t *testing.T) {
    39  	t.Run("recurring invalid transfers", testRecurringTransferInvalidTransfers)
    40  	t.Run("valid recurring transfers", testValidRecurringTransfer)
    41  	t.Run("valid forever transfers, cancelled not enough funds", testForeverTransferCancelledNotEnoughFunds)
    42  	t.Run("invalid recurring transfers, duplicates", testInvalidRecurringTransfersDuplicates)
    43  	t.Run("invalid recurring transfers, bad amount", testInvalidRecurringTransfersBadAmount)
    44  	t.Run("invalid recurring transfers, in the past", testInvalidRecurringTransfersInThePast)
    45  }
    46  
    47  func TestExpireOldTransfers(t *testing.T) {
    48  	e := getTestEngine(t)
    49  
    50  	ctx := context.Background()
    51  
    52  	e.OnMinTransferQuantumMultiple(context.Background(), num.DecimalFromFloat(1))
    53  	e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(10)}), nil)
    54  	e.broker.EXPECT().Send(gomock.Any()).AnyTimes()
    55  	fromAcc := types.Account{
    56  		Balance: num.NewUint(100000), // enough for the all
    57  	}
    58  	e.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).AnyTimes().Return(&fromAcc, nil)
    59  
    60  	endEpoch := uint64(12)
    61  	transfers := []*types.TransferFunds{}
    62  	for i := 0; i < 10; i++ {
    63  		transfers = append(transfers, &types.TransferFunds{
    64  			Kind: types.TransferCommandKindRecurring,
    65  			Recurring: &types.RecurringTransfer{
    66  				TransferBase: &types.TransferBase{
    67  					ID:              fmt.Sprintf("TRANSFERID-%d", i),
    68  					From:            "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301",
    69  					FromAccountType: types.AccountTypeGeneral,
    70  					To:              crypto.RandomHash(),
    71  					ToAccountType:   types.AccountTypeGeneral,
    72  					Asset:           assetNameETH,
    73  					Amount:          num.NewUint(10),
    74  					Reference:       "someref",
    75  				},
    76  				StartEpoch: 10,
    77  				EndEpoch:   &endEpoch,
    78  				Factor:     num.MustDecimalFromString("1"),
    79  			},
    80  		})
    81  		require.NoError(t, e.TransferFunds(ctx, transfers[i]))
    82  	}
    83  	e.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
    84  
    85  	seenEvts := []events.Event{}
    86  	e.broker.EXPECT().SendBatch(gomock.Any()).DoAndReturn(func(evts []events.Event) {
    87  		seenEvts = append(seenEvts, evts...)
    88  	}).AnyTimes()
    89  
    90  	e.OnEpoch(context.Background(), types.Epoch{Seq: 15, Action: vega.EpochAction_EPOCH_ACTION_START})
    91  	e.OnEpoch(context.Background(), types.Epoch{Seq: 15, Action: vega.EpochAction_EPOCH_ACTION_END})
    92  
    93  	require.Equal(t, 10, len(seenEvts))
    94  	stoppedIDs := map[string]struct{}{}
    95  	for _, e2 := range seenEvts {
    96  		if e2.StreamMessage().GetTransfer().Status == types.TransferStatusDone {
    97  			stoppedIDs[e2.StreamMessage().GetTransfer().Id] = struct{}{}
    98  		}
    99  	}
   100  	require.Equal(t, 10, len(stoppedIDs))
   101  }
   102  
   103  func TestMaturation(t *testing.T) {
   104  	e := getTestEngine(t)
   105  
   106  	ctx := context.Background()
   107  
   108  	e.OnMinTransferQuantumMultiple(context.Background(), num.DecimalFromFloat(1))
   109  	e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(10)}), nil)
   110  	e.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   111  	fromAcc := types.Account{
   112  		Balance: num.NewUint(100000), // enough for the all
   113  	}
   114  	e.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).AnyTimes().Return(&fromAcc, nil)
   115  
   116  	endEpoch := uint64(12)
   117  	transfers := []*types.TransferFunds{}
   118  	for i := 0; i < 10; i++ {
   119  		transfers = append(transfers, &types.TransferFunds{
   120  			Kind: types.TransferCommandKindRecurring,
   121  			Recurring: &types.RecurringTransfer{
   122  				TransferBase: &types.TransferBase{
   123  					ID:              fmt.Sprintf("TRANSFERID-%d", i),
   124  					From:            "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301",
   125  					FromAccountType: types.AccountTypeGeneral,
   126  					To:              crypto.RandomHash(),
   127  					ToAccountType:   types.AccountTypeGeneral,
   128  					Asset:           assetNameETH,
   129  					Amount:          num.NewUint(10),
   130  					Reference:       "someref",
   131  				},
   132  				StartEpoch: 10,
   133  				EndEpoch:   &endEpoch,
   134  				Factor:     num.MustDecimalFromString("1"),
   135  			},
   136  		})
   137  		require.NoError(t, e.TransferFunds(ctx, transfers[i]))
   138  	}
   139  	e.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
   140  
   141  	seenEvts := []events.Event{}
   142  	e.broker.EXPECT().SendBatch(gomock.Any()).DoAndReturn(func(evts []events.Event) {
   143  		seenEvts = append(seenEvts, evts...)
   144  	}).AnyTimes()
   145  	e.OnEpoch(context.Background(), types.Epoch{Seq: 10, Action: vega.EpochAction_EPOCH_ACTION_START})
   146  	e.OnEpoch(context.Background(), types.Epoch{Seq: 10, Action: vega.EpochAction_EPOCH_ACTION_END})
   147  	e.OnEpoch(context.Background(), types.Epoch{Seq: 11, Action: vega.EpochAction_EPOCH_ACTION_START})
   148  	e.OnEpoch(context.Background(), types.Epoch{Seq: 11, Action: vega.EpochAction_EPOCH_ACTION_END})
   149  	e.OnEpoch(context.Background(), types.Epoch{Seq: 12, Action: vega.EpochAction_EPOCH_ACTION_START})
   150  	e.OnEpoch(context.Background(), types.Epoch{Seq: 12, Action: vega.EpochAction_EPOCH_ACTION_END})
   151  	e.OnEpoch(context.Background(), types.Epoch{Seq: 13, Action: vega.EpochAction_EPOCH_ACTION_START})
   152  	e.OnEpoch(context.Background(), types.Epoch{Seq: 13, Action: vega.EpochAction_EPOCH_ACTION_END})
   153  
   154  	require.Equal(t, 10, len(seenEvts))
   155  	stoppedIDs := map[string]struct{}{}
   156  	for _, e2 := range seenEvts {
   157  		if e2.StreamMessage().GetTransfer().Status == types.TransferStatusDone {
   158  			stoppedIDs[e2.StreamMessage().GetTransfer().Id] = struct{}{}
   159  		}
   160  	}
   161  	require.Equal(t, 10, len(stoppedIDs))
   162  }
   163  
   164  func testInvalidRecurringTransfersBadAmount(t *testing.T) {
   165  	e := getTestEngine(t)
   166  
   167  	ctx := context.Background()
   168  	transfer := &types.TransferFunds{
   169  		Kind: types.TransferCommandKindRecurring,
   170  		Recurring: &types.RecurringTransfer{
   171  			TransferBase: &types.TransferBase{
   172  				ID:              "TRANSFERID",
   173  				From:            "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301",
   174  				FromAccountType: types.AccountTypeGeneral,
   175  				To:              "0000000000000000000000000000000000000000000000000000000000000000",
   176  				ToAccountType:   types.AccountTypeGlobalReward,
   177  				Asset:           assetNameETH,
   178  				Amount:          num.NewUint(10),
   179  				Reference:       "someref",
   180  			},
   181  			StartEpoch: 10,
   182  			Factor:     num.MustDecimalFromString("0.9"),
   183  		},
   184  	}
   185  
   186  	e.OnMinTransferQuantumMultiple(context.Background(), num.DecimalFromFloat(1))
   187  	// asset exists
   188  	e.assets.EXPECT().Get(gomock.Any()).Times(1).Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil)
   189  	e.broker.EXPECT().Send(gomock.Any()).Times(1)
   190  
   191  	assert.EqualError(t,
   192  		e.TransferFunds(ctx, transfer),
   193  		"could not transfer funds, less than minimal amount requested to transfer",
   194  	)
   195  }
   196  
   197  func testInvalidRecurringTransfersInThePast(t *testing.T) {
   198  	e := getTestEngine(t)
   199  
   200  	// let's do a massive fee, easy to test
   201  	e.OnTransferFeeFactorUpdate(context.Background(), num.NewDecimalFromFloat(0.5))
   202  	e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_START})
   203  
   204  	var endEpoch13 uint64 = 11
   205  	ctx := context.Background()
   206  	transfer := &types.TransferFunds{
   207  		Kind: types.TransferCommandKindRecurring,
   208  		Recurring: &types.RecurringTransfer{
   209  			TransferBase: &types.TransferBase{
   210  				ID:              "TRANSFERID",
   211  				From:            "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301",
   212  				FromAccountType: types.AccountTypeGeneral,
   213  				To:              "0000000000000000000000000000000000000000000000000000000000000000",
   214  				ToAccountType:   types.AccountTypeGlobalReward,
   215  				Asset:           assetNameETH,
   216  				Amount:          num.NewUint(100),
   217  				Reference:       "someref",
   218  			},
   219  			StartEpoch: 6,
   220  			EndEpoch:   &endEpoch13,
   221  			Factor:     num.MustDecimalFromString("0.9"),
   222  		},
   223  	}
   224  
   225  	e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil)
   226  	e.broker.EXPECT().Send(gomock.Any()).Times(1)
   227  	assert.EqualError(t,
   228  		e.TransferFunds(ctx, transfer),
   229  		"start epoch in the past",
   230  	)
   231  
   232  	// now all should be fine, let's try to start another same transfer use the current epoch
   233  
   234  	transfer2 := &types.TransferFunds{
   235  		Kind: types.TransferCommandKindRecurring,
   236  		Recurring: &types.RecurringTransfer{
   237  			TransferBase: &types.TransferBase{
   238  				ID:              "TRANSFERID2",
   239  				From:            "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301",
   240  				FromAccountType: types.AccountTypeGeneral,
   241  				To:              "0000000000000000000000000000000000000000000000000000000000000000",
   242  				ToAccountType:   types.AccountTypeGlobalReward,
   243  				Asset:           assetNameETH,
   244  				Amount:          num.NewUint(50),
   245  				Reference:       "someotherref",
   246  			},
   247  			StartEpoch: 7,
   248  			Factor:     num.MustDecimalFromString("0.9"),
   249  		},
   250  	}
   251  
   252  	e.broker.EXPECT().Send(gomock.Any()).Times(1)
   253  	assert.NoError(t,
   254  		e.TransferFunds(ctx, transfer2),
   255  	)
   256  }
   257  
   258  func testInvalidRecurringTransfersDuplicates(t *testing.T) {
   259  	e := getTestEngine(t)
   260  
   261  	// let's do a massive fee, easy to test
   262  	e.OnTransferFeeFactorUpdate(context.Background(), num.NewDecimalFromFloat(0.5))
   263  	e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_START})
   264  	e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_END})
   265  
   266  	var endEpoch13 uint64 = 11
   267  	ctx := context.Background()
   268  	transfer := &types.TransferFunds{
   269  		Kind: types.TransferCommandKindRecurring,
   270  		Recurring: &types.RecurringTransfer{
   271  			TransferBase: &types.TransferBase{
   272  				ID:              "TRANSFERID",
   273  				From:            "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301",
   274  				FromAccountType: types.AccountTypeGeneral,
   275  				To:              "0000000000000000000000000000000000000000000000000000000000000000",
   276  				ToAccountType:   types.AccountTypeGlobalReward,
   277  				Asset:           assetNameETH,
   278  				Amount:          num.NewUint(100),
   279  				Reference:       "someref",
   280  			},
   281  			StartEpoch: 10,
   282  			EndEpoch:   &endEpoch13,
   283  			Factor:     num.MustDecimalFromString("0.9"),
   284  		},
   285  	}
   286  
   287  	e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil)
   288  	e.broker.EXPECT().Send(gomock.Any()).Times(1)
   289  	assert.NoError(t, e.TransferFunds(ctx, transfer))
   290  
   291  	// now all should be fine, let's try to start another same transfer
   292  
   293  	transfer2 := &types.TransferFunds{
   294  		Kind: types.TransferCommandKindRecurring,
   295  		Recurring: &types.RecurringTransfer{
   296  			TransferBase: &types.TransferBase{
   297  				ID:              "TRANSFERID2",
   298  				From:            "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301",
   299  				FromAccountType: types.AccountTypeGeneral,
   300  				To:              "0000000000000000000000000000000000000000000000000000000000000000",
   301  				ToAccountType:   types.AccountTypeGlobalReward,
   302  				Asset:           assetNameETH,
   303  				Amount:          num.NewUint(50),
   304  				Reference:       "someotherref",
   305  			},
   306  			StartEpoch: 15,
   307  			Factor:     num.MustDecimalFromString("0.9"),
   308  		},
   309  	}
   310  
   311  	e.broker.EXPECT().Send(gomock.Any()).Times(1)
   312  	assert.EqualError(t,
   313  		e.TransferFunds(ctx, transfer2),
   314  		banking.ErrCannotSubmitDuplicateRecurringTransferWithSameFromAndTo.Error(),
   315  	)
   316  
   317  	// same from/to different asset - should pass
   318  	transfer3 := &types.TransferFunds{
   319  		Kind: types.TransferCommandKindRecurring,
   320  		Recurring: &types.RecurringTransfer{
   321  			TransferBase: &types.TransferBase{
   322  				ID:              "TRANSFERID3",
   323  				From:            "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301",
   324  				FromAccountType: types.AccountTypeGeneral,
   325  				To:              "0000000000000000000000000000000000000000000000000000000000000000",
   326  				ToAccountType:   types.AccountTypeGlobalReward,
   327  				Asset:           "VEGA",
   328  				Amount:          num.NewUint(50),
   329  				Reference:       "someotherref",
   330  			},
   331  			StartEpoch: 15,
   332  			Factor:     num.MustDecimalFromString("0.9"),
   333  		},
   334  	}
   335  	e.broker.EXPECT().Send(gomock.Any()).Times(1)
   336  	assert.NoError(t, e.TransferFunds(ctx, transfer3))
   337  }
   338  
   339  func testForeverTransferCancelledNotEnoughFunds(t *testing.T) {
   340  	e := getTestEngine(t)
   341  
   342  	// let's do a massive fee, easy to test
   343  	e.OnTransferFeeFactorUpdate(context.Background(), num.NewDecimalFromFloat(0.5))
   344  	e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_START})
   345  	e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_END})
   346  
   347  	ctx := context.Background()
   348  	transfer := &types.TransferFunds{
   349  		Kind: types.TransferCommandKindRecurring,
   350  		Recurring: &types.RecurringTransfer{
   351  			TransferBase: &types.TransferBase{
   352  				ID:              "TRANSFERID",
   353  				From:            "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301",
   354  				FromAccountType: types.AccountTypeGeneral,
   355  				To:              "0000000000000000000000000000000000000000000000000000000000000000",
   356  				ToAccountType:   types.AccountTypeGlobalReward,
   357  				Asset:           assetNameETH,
   358  				Amount:          num.NewUint(100),
   359  				Reference:       "someref",
   360  			},
   361  			DispatchStrategy: &vega.DispatchStrategy{
   362  				Metric:      vega.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED,
   363  				EntityScope: vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   364  			},
   365  			StartEpoch: 10,
   366  			EndEpoch:   nil, // forever
   367  			Factor:     num.MustDecimalFromString("0.9"),
   368  		},
   369  	}
   370  
   371  	e.marketActivityTracker.EXPECT().CalculateMetricForIndividuals(gomock.Any(), gomock.Any()).AnyTimes().Return([]*types.PartyContributionScore{
   372  		{Party: "", Score: num.DecimalFromFloat(1), StakingBalance: num.UintZero(), OpenVolume: num.UintZero(), TotalFeesPaid: num.UintZero(), IsEligible: true},
   373  	})
   374  	e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil)
   375  	e.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   376  	assert.NoError(t, e.TransferFunds(ctx, transfer))
   377  
   378  	// now let's move epochs to see the others transfers
   379  	// first 2 epochs nothing happen
   380  	e.OnEpoch(context.Background(), types.Epoch{Seq: 8, Action: vega.EpochAction_EPOCH_ACTION_START})
   381  	e.OnEpoch(context.Background(), types.Epoch{Seq: 8, Action: vega.EpochAction_EPOCH_ACTION_END})
   382  	e.OnEpoch(context.Background(), types.Epoch{Seq: 9, Action: vega.EpochAction_EPOCH_ACTION_START})
   383  	e.OnEpoch(context.Background(), types.Epoch{Seq: 9, Action: vega.EpochAction_EPOCH_ACTION_END})
   384  	// now we are in business
   385  
   386  	fromAcc := types.Account{
   387  		Balance: num.NewUint(160), // enough for the first transfer
   388  	}
   389  
   390  	// asset exists
   391  	e.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).Times(2).Return(&fromAcc, nil)
   392  
   393  	// assert the calculation of fees and transfer request are correct
   394  	e.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
   395  		func(ctx context.Context,
   396  			transfers []*types.Transfer,
   397  			accountTypes []types.AccountType,
   398  			references []string,
   399  			feeTransfers []*types.Transfer,
   400  			feeTransfersAccountTypes []types.AccountType,
   401  		) ([]*types.LedgerMovement, error,
   402  		) {
   403  			t.Run("ensure transfers are correct", func(t *testing.T) {
   404  				// transfer is done fully instantly, we should have 2 transfer
   405  				assert.Len(t, transfers, 2)
   406  				assert.Equal(t, transfers[0].Owner, "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301")
   407  				assert.Equal(t, transfers[0].Amount.Amount, num.NewUint(100))
   408  				assert.Equal(t, transfers[0].Amount.Asset, assetNameETH)
   409  
   410  				// 1 account types too
   411  				assert.Len(t, accountTypes, 2)
   412  				assert.Equal(t, accountTypes[0], types.AccountTypeGeneral)
   413  			})
   414  
   415  			t.Run("ensure fee transfers are correct", func(t *testing.T) {
   416  				assert.Len(t, feeTransfers, 1)
   417  				assert.Equal(t, feeTransfers[0].Owner, "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301")
   418  				assert.Equal(t, feeTransfers[0].Amount.Amount, num.NewUint(50))
   419  				assert.Equal(t, feeTransfers[0].Amount.Asset, assetNameETH)
   420  
   421  				// then the fees account types
   422  				assert.Len(t, feeTransfersAccountTypes, 1)
   423  				assert.Equal(t, accountTypes[0], types.AccountTypeGeneral)
   424  			})
   425  
   426  			return nil, nil
   427  		})
   428  
   429  	e.OnEpoch(context.Background(), types.Epoch{Seq: 10, Action: vega.EpochAction_EPOCH_ACTION_START})
   430  	e.OnEpoch(context.Background(), types.Epoch{Seq: 10, Action: vega.EpochAction_EPOCH_ACTION_END})
   431  
   432  	fromAcc = types.Account{
   433  		Balance: num.NewUint(10), // not enough for the second transfer
   434  	}
   435  
   436  	// asset exists
   437  	e.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).Times(1).Return(&fromAcc, nil)
   438  
   439  	e.broker.EXPECT().SendBatch(gomock.Any()).DoAndReturn(func(evts []events.Event) {
   440  		t.Run("ensure transfer is stopped", func(t *testing.T) {
   441  			assert.Len(t, evts, 1)
   442  			e, ok := evts[0].(*events.TransferFunds)
   443  			assert.True(t, ok, "unexpected event from the bus")
   444  			assert.Equal(t, types.TransferStatusStopped, e.Proto().Status)
   445  			assert.Equal(t, "could not pay the fee for transfer: not enough funds to transfer", *e.Proto().Reason)
   446  		})
   447  	})
   448  
   449  	// ensure it's not called
   450  	e.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0)
   451  
   452  	e.OnEpoch(context.Background(), types.Epoch{Seq: 11, Action: vega.EpochAction_EPOCH_ACTION_START})
   453  	e.OnEpoch(context.Background(), types.Epoch{Seq: 11, Action: vega.EpochAction_EPOCH_ACTION_END})
   454  
   455  	// then nothing happen, we are done
   456  	e.OnEpoch(context.Background(), types.Epoch{Seq: 12, Action: vega.EpochAction_EPOCH_ACTION_START})
   457  	e.OnEpoch(context.Background(), types.Epoch{Seq: 12, Action: vega.EpochAction_EPOCH_ACTION_END})
   458  }
   459  
   460  func testValidRecurringTransfer(t *testing.T) {
   461  	e := getTestEngine(t)
   462  
   463  	// let's do a massive fee, easy to test
   464  	e.OnTransferFeeFactorUpdate(context.Background(), num.NewDecimalFromFloat(0.5))
   465  	e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_START})
   466  	e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_END})
   467  
   468  	var endEpoch13 uint64 = 11
   469  	ctx := context.Background()
   470  	transfer := &types.TransferFunds{
   471  		Kind: types.TransferCommandKindRecurring,
   472  		Recurring: &types.RecurringTransfer{
   473  			TransferBase: &types.TransferBase{
   474  				ID:              "TRANSFERID",
   475  				From:            "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301",
   476  				FromAccountType: types.AccountTypeGeneral,
   477  				To:              "0000000000000000000000000000000000000000000000000000000000000000",
   478  				ToAccountType:   types.AccountTypeGlobalReward,
   479  				Asset:           assetNameETH,
   480  				Amount:          num.NewUint(100),
   481  				Reference:       "someref",
   482  			},
   483  			StartEpoch: 10,
   484  			EndEpoch:   &endEpoch13,
   485  			Factor:     num.MustDecimalFromString("0.9"),
   486  		},
   487  	}
   488  
   489  	e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil)
   490  	e.broker.EXPECT().Send(gomock.Any()).Times(3)
   491  	assert.NoError(t, e.TransferFunds(ctx, transfer))
   492  
   493  	// now let's move epochs to see the others transfers
   494  	// first 2 epochs nothing happen
   495  	e.OnEpoch(context.Background(), types.Epoch{Seq: 8, Action: vega.EpochAction_EPOCH_ACTION_START})
   496  	e.OnEpoch(context.Background(), types.Epoch{Seq: 8, Action: vega.EpochAction_EPOCH_ACTION_END})
   497  	e.OnEpoch(context.Background(), types.Epoch{Seq: 9, Action: vega.EpochAction_EPOCH_ACTION_START})
   498  	e.OnEpoch(context.Background(), types.Epoch{Seq: 9, Action: vega.EpochAction_EPOCH_ACTION_END})
   499  	// now we are in business
   500  
   501  	fromAcc := types.Account{
   502  		Balance: num.NewUint(1000),
   503  	}
   504  
   505  	// asset exists
   506  	e.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).Times(1).Return(&fromAcc, nil)
   507  
   508  	// assert the calculation of fees and transfer request are correct
   509  	e.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
   510  		func(ctx context.Context,
   511  			transfers []*types.Transfer,
   512  			accountTypes []types.AccountType,
   513  			references []string,
   514  			feeTransfers []*types.Transfer,
   515  			feeTransfersAccountTypes []types.AccountType,
   516  		) ([]*types.LedgerMovement, error,
   517  		) {
   518  			t.Run("ensure transfers are correct", func(t *testing.T) {
   519  				// transfer is done fully instantly, we should have 2 transfer
   520  				assert.Len(t, transfers, 2)
   521  				assert.Equal(t, transfers[0].Owner, "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301")
   522  				assert.Equal(t, transfers[0].Amount.Amount, num.NewUint(100))
   523  				assert.Equal(t, transfers[0].Amount.Asset, assetNameETH)
   524  
   525  				// 1 account types too
   526  				assert.Len(t, accountTypes, 2)
   527  				assert.Equal(t, accountTypes[0], types.AccountTypeGeneral)
   528  			})
   529  
   530  			t.Run("ensure fee transfers are correct", func(t *testing.T) {
   531  				assert.Len(t, feeTransfers, 1)
   532  				assert.Equal(t, feeTransfers[0].Owner, "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301")
   533  				assert.Equal(t, feeTransfers[0].Amount.Amount, num.NewUint(50))
   534  				assert.Equal(t, feeTransfers[0].Amount.Asset, assetNameETH)
   535  
   536  				// then the fees account types
   537  				assert.Len(t, feeTransfersAccountTypes, 1)
   538  				assert.Equal(t, accountTypes[0], types.AccountTypeGeneral)
   539  			})
   540  
   541  			return nil, nil
   542  		})
   543  
   544  	e.OnEpoch(context.Background(), types.Epoch{Seq: 10, Action: vega.EpochAction_EPOCH_ACTION_START})
   545  	e.OnEpoch(context.Background(), types.Epoch{Seq: 10, Action: vega.EpochAction_EPOCH_ACTION_END})
   546  
   547  	// asset exists
   548  	e.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).Times(1).Return(&fromAcc, nil)
   549  
   550  	// assert the calculation of fees and transfer request are correct
   551  	e.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
   552  		func(ctx context.Context,
   553  			transfers []*types.Transfer,
   554  			accountTypes []types.AccountType,
   555  			references []string,
   556  			feeTransfers []*types.Transfer,
   557  			feeTransfersAccountTypes []types.AccountType,
   558  		) ([]*types.LedgerMovement, error,
   559  		) {
   560  			t.Run("ensure transfers are correct", func(t *testing.T) {
   561  				// transfer is done fully instantly, we should have 2 transfer
   562  				assert.Len(t, transfers, 2)
   563  				assert.Equal(t, transfers[0].Owner, "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301")
   564  				assert.Equal(t, transfers[0].Amount.Amount, num.NewUint(90))
   565  				assert.Equal(t, transfers[0].Amount.Asset, assetNameETH)
   566  
   567  				// 1 account types too
   568  				assert.Len(t, accountTypes, 2)
   569  				assert.Equal(t, accountTypes[0], types.AccountTypeGeneral)
   570  			})
   571  
   572  			t.Run("ensure fee transfers are correct", func(t *testing.T) {
   573  				assert.Len(t, feeTransfers, 1)
   574  				assert.Equal(t, feeTransfers[0].Owner, "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301")
   575  				assert.Equal(t, feeTransfers[0].Amount.Amount, num.NewUint(45))
   576  				assert.Equal(t, feeTransfers[0].Amount.Asset, assetNameETH)
   577  
   578  				// then the fees account types
   579  				assert.Len(t, feeTransfersAccountTypes, 1)
   580  				assert.Equal(t, accountTypes[0], types.AccountTypeGeneral)
   581  			})
   582  
   583  			return nil, nil
   584  		})
   585  
   586  	e.broker.EXPECT().SendBatch(gomock.Any()).DoAndReturn(func(evts []events.Event) {
   587  		t.Run("ensure transfer is done", func(t *testing.T) {
   588  			assert.Len(t, evts, 1)
   589  			e, ok := evts[0].(*events.TransferFunds)
   590  			assert.True(t, ok, "unexpected event from the bus")
   591  			assert.Equal(t, e.Proto().Status, types.TransferStatusDone)
   592  		})
   593  	})
   594  
   595  	e.OnEpoch(context.Background(), types.Epoch{Seq: 11, Action: vega.EpochAction_EPOCH_ACTION_START})
   596  	e.OnEpoch(context.Background(), types.Epoch{Seq: 11, Action: vega.EpochAction_EPOCH_ACTION_END})
   597  
   598  	// then nothing happen, we are done
   599  	e.OnEpoch(context.Background(), types.Epoch{Seq: 12, Action: vega.EpochAction_EPOCH_ACTION_START})
   600  	e.OnEpoch(context.Background(), types.Epoch{Seq: 12, Action: vega.EpochAction_EPOCH_ACTION_END})
   601  }
   602  
   603  func testRecurringTransferInvalidTransfers(t *testing.T) {
   604  	e := getTestEngine(t)
   605  
   606  	ctx := context.Background()
   607  	transfer := types.TransferFunds{
   608  		Kind:      types.TransferCommandKindRecurring,
   609  		Recurring: &types.RecurringTransfer{},
   610  	}
   611  
   612  	transferBase := types.TransferBase{
   613  		From:            "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301",
   614  		FromAccountType: types.AccountTypeGeneral,
   615  		To:              "2e05fd230f3c9f4eaf0bdc5bfb7ca0c9d00278afc44637aab60da76653d7ccf0",
   616  		ToAccountType:   types.AccountTypeGeneral,
   617  		Asset:           assetNameETH,
   618  		Amount:          num.NewUint(10),
   619  		Reference:       "someref",
   620  	}
   621  
   622  	// asset exists
   623  	e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil)
   624  
   625  	var baseCpy types.TransferBase
   626  
   627  	t.Run("invalid from account", func(t *testing.T) {
   628  		e.broker.EXPECT().Send(gomock.Any()).Times(1)
   629  		baseCpy := transferBase
   630  		transfer.Recurring.TransferBase = &baseCpy
   631  		transfer.Recurring.From = ""
   632  		assert.EqualError(t,
   633  			e.TransferFunds(ctx, &transfer),
   634  			types.ErrInvalidFromAccount.Error(),
   635  		)
   636  	})
   637  
   638  	t.Run("invalid to account", func(t *testing.T) {
   639  		e.broker.EXPECT().Send(gomock.Any()).Times(1)
   640  		baseCpy = transferBase
   641  		transfer.Recurring.TransferBase = &baseCpy
   642  		transfer.Recurring.To = ""
   643  		assert.EqualError(t,
   644  			e.TransferFunds(ctx, &transfer),
   645  			types.ErrInvalidToAccount.Error(),
   646  		)
   647  	})
   648  
   649  	t.Run("unsupported from account type", func(t *testing.T) {
   650  		e.broker.EXPECT().Send(gomock.Any()).Times(1)
   651  		baseCpy = transferBase
   652  		transfer.Recurring.TransferBase = &baseCpy
   653  		transfer.Recurring.FromAccountType = types.AccountTypeBond
   654  		assert.EqualError(t,
   655  			e.TransferFunds(ctx, &transfer),
   656  			types.ErrUnsupportedFromAccountType.Error(),
   657  		)
   658  	})
   659  
   660  	t.Run("unsuported to account type", func(t *testing.T) {
   661  		e.broker.EXPECT().Send(gomock.Any()).Times(1)
   662  		baseCpy = transferBase
   663  		transfer.Recurring.TransferBase = &baseCpy
   664  		transfer.Recurring.ToAccountType = types.AccountTypeBond
   665  		assert.EqualError(t,
   666  			e.TransferFunds(ctx, &transfer),
   667  			types.ErrUnsupportedToAccountType.Error(),
   668  		)
   669  	})
   670  
   671  	t.Run("zero funds transfer", func(t *testing.T) {
   672  		e.broker.EXPECT().Send(gomock.Any()).Times(1)
   673  		baseCpy = transferBase
   674  		transfer.Recurring.TransferBase = &baseCpy
   675  		transfer.Recurring.Amount = num.UintZero()
   676  		assert.EqualError(t,
   677  			e.TransferFunds(ctx, &transfer),
   678  			types.ErrCannotTransferZeroFunds.Error(),
   679  		)
   680  	})
   681  
   682  	var (
   683  		endEpoch100 uint64 = 100
   684  		endEpoch0   uint64
   685  		endEpoch1   uint64 = 1
   686  	)
   687  	// now testing the recurring specific stuff
   688  	baseCpy = transferBase
   689  	transfer.Recurring.TransferBase = &baseCpy
   690  	transfer.Recurring.EndEpoch = &endEpoch100
   691  	transfer.Recurring.StartEpoch = 90
   692  	transfer.Recurring.Factor = num.MustDecimalFromString("0.1")
   693  
   694  	t.Run("bad start time", func(t *testing.T) {
   695  		transfer.Recurring.StartEpoch = 0
   696  		e.broker.EXPECT().Send(gomock.Any()).Times(1)
   697  
   698  		assert.EqualError(t,
   699  			e.TransferFunds(ctx, &transfer),
   700  			types.ErrStartEpochIsZero.Error(),
   701  		)
   702  	})
   703  
   704  	t.Run("bad end time", func(t *testing.T) {
   705  		transfer.Recurring.StartEpoch = 90
   706  		transfer.Recurring.EndEpoch = &endEpoch0
   707  		e.broker.EXPECT().Send(gomock.Any()).Times(1)
   708  
   709  		assert.EqualError(t,
   710  			e.TransferFunds(ctx, &transfer),
   711  			types.ErrEndEpochIsZero.Error(),
   712  		)
   713  	})
   714  
   715  	t.Run("negative factor", func(t *testing.T) {
   716  		transfer.Recurring.EndEpoch = &endEpoch100
   717  		transfer.Recurring.Factor = num.MustDecimalFromString("-1")
   718  		e.broker.EXPECT().Send(gomock.Any()).Times(1)
   719  
   720  		assert.EqualError(t,
   721  			e.TransferFunds(ctx, &transfer),
   722  			types.ErrInvalidFactor.Error(),
   723  		)
   724  	})
   725  
   726  	t.Run("zero factor", func(t *testing.T) {
   727  		transfer.Recurring.Factor = num.MustDecimalFromString("0")
   728  		e.broker.EXPECT().Send(gomock.Any()).Times(1)
   729  
   730  		assert.EqualError(t,
   731  			e.TransferFunds(ctx, &transfer),
   732  			types.ErrInvalidFactor.Error(),
   733  		)
   734  	})
   735  
   736  	t.Run("start epoch after end epoch", func(t *testing.T) {
   737  		transfer.Recurring.Factor = num.MustDecimalFromString("1")
   738  		transfer.Recurring.EndEpoch = &endEpoch1
   739  		e.broker.EXPECT().Send(gomock.Any()).Times(1)
   740  
   741  		assert.EqualError(t,
   742  			e.TransferFunds(ctx, &transfer),
   743  			types.ErrStartEpochAfterEndEpoch.Error(),
   744  		)
   745  	})
   746  
   747  	t.Run("end epoch nil", func(t *testing.T) {
   748  		transfer.Recurring.EndEpoch = nil
   749  		e.broker.EXPECT().Send(gomock.Any()).Times(1)
   750  
   751  		assert.NoError(t,
   752  			e.TransferFunds(ctx, &transfer),
   753  		)
   754  	})
   755  }
   756  
   757  func TestMarketAssetMismatchRejectsTransfer(t *testing.T) {
   758  	eng := getTestEngine(t)
   759  
   760  	fromAcc := types.Account{
   761  		Balance: num.NewUint(1000),
   762  	}
   763  
   764  	eng.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil)
   765  	eng.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).AnyTimes().Return(&fromAcc, nil)
   766  	eng.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   767  	eng.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   768  
   769  	recurring := &types.TransferFunds{
   770  		Kind: types.TransferCommandKindRecurring,
   771  		Recurring: &types.RecurringTransfer{
   772  			TransferBase: &types.TransferBase{
   773  				From:            "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301",
   774  				FromAccountType: types.AccountTypeGeneral,
   775  				To:              "2e05fd230f3c9f4eaf0bdc5bfb7ca0c9d00278afc44637aab60da76653d7ccf0",
   776  				ToAccountType:   types.AccountTypeGeneral,
   777  				Asset:           assetNameETH,
   778  				Amount:          num.NewUint(10),
   779  				Reference:       "someref",
   780  			},
   781  			StartEpoch: 10,
   782  			EndEpoch:   nil, // forever
   783  			Factor:     num.MustDecimalFromString("0.9"),
   784  			DispatchStrategy: &vega.DispatchStrategy{
   785  				AssetForMetric:       "zohar",
   786  				Metric:               vega.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL,
   787  				Markets:              []string{"mmm"},
   788  				EntityScope:          vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   789  				IndividualScope:      vega.IndividualScope_INDIVIDUAL_SCOPE_IN_TEAM,
   790  				WindowLength:         1,
   791  				LockPeriod:           1,
   792  				DistributionStrategy: vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK,
   793  			},
   794  		},
   795  	}
   796  
   797  	// if in-scope market has a different asset it is rejected
   798  	eng.marketActivityTracker.EXPECT().MarketTrackedForAsset(gomock.Any(), gomock.Any()).Times(1).Return(false)
   799  	require.Error(t, eng.TransferFunds(context.Background(), recurring))
   800  }
   801  
   802  func TestDispatchStrategyRemoval(t *testing.T) {
   803  	e := getTestEngine(t)
   804  
   805  	dispatchStrat := &vega.DispatchStrategy{
   806  		AssetForMetric:       "zohar",
   807  		Metric:               vega.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL,
   808  		Markets:              []string{"mmm"},
   809  		EntityScope:          vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   810  		IndividualScope:      vega.IndividualScope_INDIVIDUAL_SCOPE_IN_TEAM,
   811  		WindowLength:         1,
   812  		LockPeriod:           1,
   813  		DistributionStrategy: vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK,
   814  	}
   815  
   816  	p, err := proto.Marshal(dispatchStrat)
   817  	require.NoError(t, err)
   818  	dsHash := hex.EncodeToString(crypto.Hash(p))
   819  
   820  	var endEpoch uint64 = 100
   821  	party := "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301"
   822  	ctx := context.Background()
   823  	transfer := &types.TransferFunds{
   824  		Kind: types.TransferCommandKindRecurring,
   825  		Recurring: &types.RecurringTransfer{
   826  			TransferBase: &types.TransferBase{
   827  				ID:              "TRANSFERID",
   828  				From:            party,
   829  				FromAccountType: types.AccountTypeGeneral,
   830  				To:              "0000000000000000000000000000000000000000000000000000000000000000",
   831  				ToAccountType:   types.AccountTypeGlobalReward,
   832  				Asset:           assetNameETH,
   833  				Amount:          num.NewUint(100),
   834  				Reference:       "someref",
   835  			},
   836  			StartEpoch:       8,
   837  			EndEpoch:         &endEpoch,
   838  			Factor:           num.MustDecimalFromString("0.9"),
   839  			DispatchStrategy: dispatchStrat,
   840  		},
   841  	}
   842  
   843  	e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil)
   844  	e.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   845  	e.marketActivityTracker.EXPECT().MarketTrackedForAsset(gomock.Any(), gomock.Any()).Times(1).Return(true)
   846  	assert.NoError(t, e.TransferFunds(ctx, transfer))
   847  
   848  	// it exists
   849  	assert.NotNil(t, e.GetDispatchStrategy(dsHash))
   850  
   851  	// now cancel
   852  	require.NoError(t, e.CancelTransferFunds(ctx,
   853  		&types.CancelTransferFunds{
   854  			Party:      party,
   855  			TransferID: "TRANSFERID",
   856  		},
   857  	),
   858  	)
   859  
   860  	// it does not exist (secretly it does but has ref-count 0)
   861  	assert.Nil(t, e.GetDispatchStrategy(dsHash))
   862  
   863  	// roll into the next epoch end
   864  	e.OnEpoch(context.Background(), types.Epoch{Seq: 8, Action: vega.EpochAction_EPOCH_ACTION_END})
   865  	e.OnEpoch(context.Background(), types.Epoch{Seq: 9, Action: vega.EpochAction_EPOCH_ACTION_START})
   866  
   867  	// still not there
   868  	assert.Nil(t, e.GetDispatchStrategy(dsHash))
   869  }