code.vegaprotocol.io/vega@v0.79.0/core/vesting/engine_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 vesting_test
    17  
    18  import (
    19  	"context"
    20  	"testing"
    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  	vegapb "code.vegaprotocol.io/vega/protos/vega"
    28  	eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
    29  
    30  	"github.com/golang/mock/gomock"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  func TestAutomatedStaking(t *testing.T) {
    36  	v := getTestEngine(t)
    37  
    38  	ctx := context.Background()
    39  
    40  	require.NoError(t, v.OnRewardVestingBaseRateUpdate(ctx, num.MustDecimalFromString("0.9")))
    41  	require.NoError(t, v.OnRewardVestingMinimumTransferUpdate(ctx, num.MustDecimalFromString("1")))
    42  	require.NoError(t, v.OnStakingAssetUpdate(ctx, "ETH"))
    43  
    44  	// this is not the most useful test, but at least we know that on payment for the
    45  	// staking asset, the staking account are being notified of the amounts as new stake deposits
    46  	// via the calls to the mocks
    47  	t.Run("On add reward for the staking asset, stake accounting is called", func(t *testing.T) {
    48  		// one call to add event, and one call to broadcast it
    49  		// one for the time
    50  		v.stakeAccounting.EXPECT().AddEvent(gomock.Any(), gomock.Any()).Times(1)
    51  		v.broker.EXPECT().Send(gomock.Any()).Times(1)
    52  		v.t.EXPECT().GetTimeNow().Times(1).Return(time.Unix(0, 0))
    53  		v.AddReward(ctx, "party1", "ETH", num.NewUint(1000), 0)
    54  	})
    55  }
    56  
    57  func TestDistributeAfterDelay(t *testing.T) {
    58  	v := getTestEngine(t)
    59  
    60  	ctx := context.Background()
    61  
    62  	// distribute 90% as the base rate,
    63  	// so first we distribute some, then we get under the minimum value, and all the rest
    64  	// is distributed
    65  	require.NoError(t, v.OnRewardVestingBaseRateUpdate(ctx, num.MustDecimalFromString("0.9")))
    66  	// this is multiplied by the quantum, so it will make it 100% of the quantum
    67  	require.NoError(t, v.OnRewardVestingMinimumTransferUpdate(ctx, num.MustDecimalFromString("1")))
    68  
    69  	require.NoError(t, v.OnBenefitTiersUpdate(ctx, &vegapb.VestingBenefitTiers{
    70  		Tiers: []*vegapb.VestingBenefitTier{
    71  			{
    72  				MinimumQuantumBalance: "200",
    73  				RewardMultiplier:      "1",
    74  			},
    75  			{
    76  				MinimumQuantumBalance: "350",
    77  				RewardMultiplier:      "2",
    78  			},
    79  			{
    80  				MinimumQuantumBalance: "500",
    81  				RewardMultiplier:      "3",
    82  			},
    83  		},
    84  	}))
    85  
    86  	v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1"))
    87  	v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
    88  
    89  	party := "party1"
    90  	vegaAsset := "VEGA"
    91  
    92  	v.parties.EXPECT().RelatedKeys(party).Return(nil, nil).AnyTimes()
    93  
    94  	v.col.InitVestedBalance(party, vegaAsset, num.NewUint(300))
    95  
    96  	epochSeq := uint64(1)
    97  
    98  	t.Run("No vesting stats and summary when no reward is being vested", func(t *testing.T) {
    99  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   100  			e, ok := evt.(*events.VestingStatsUpdated)
   101  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   102  			assert.Equal(t, eventspb.VestingStatsUpdated{
   103  				AtEpoch: epochSeq,
   104  				Stats:   []*eventspb.PartyVestingStats{},
   105  			}, e.Proto())
   106  		}).Times(1)
   107  
   108  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   109  			e, ok := evt.(*events.VestingBalancesSummary)
   110  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   111  			assert.Equal(t, eventspb.VestingBalancesSummary{
   112  				EpochSeq:              epochSeq,
   113  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
   114  			}, e.Proto())
   115  		}).Times(1)
   116  
   117  		v.OnEpochEvent(ctx, types.Epoch{
   118  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   119  			Seq:    epochSeq,
   120  		})
   121  	})
   122  
   123  	t.Run("Add a reward locked for 3 epochs", func(t *testing.T) {
   124  		v.AddReward(context.Background(), party, vegaAsset, num.NewUint(100), 3)
   125  	})
   126  
   127  	t.Run("Wait for 3 epochs", func(t *testing.T) {
   128  		for i := 0; i < 3; i++ {
   129  			epochSeq += 1
   130  
   131  			v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   132  				e, ok := evt.(*events.VestingStatsUpdated)
   133  				require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   134  				assert.Equal(t, eventspb.VestingStatsUpdated{
   135  					AtEpoch: epochSeq,
   136  					Stats: []*eventspb.PartyVestingStats{
   137  						{
   138  							PartyId:                     party,
   139  							RewardBonusMultiplier:       "1",
   140  							QuantumBalance:              "300",
   141  							SummedRewardBonusMultiplier: "1",
   142  							SummedQuantumBalance:        "300",
   143  						},
   144  					},
   145  				}, e.Proto())
   146  			}).Times(1)
   147  
   148  			v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   149  				e, ok := evt.(*events.VestingBalancesSummary)
   150  				require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   151  				assert.Equal(t, eventspb.VestingBalancesSummary{
   152  					EpochSeq: epochSeq,
   153  					PartiesVestingSummary: []*eventspb.PartyVestingSummary{
   154  						{
   155  							Party: party,
   156  							PartyLockedBalances: []*eventspb.PartyLockedBalance{
   157  								{
   158  									Asset:      vegaAsset,
   159  									UntilEpoch: 5,
   160  									Balance:    "100",
   161  								},
   162  							},
   163  							PartyVestingBalances: []*eventspb.PartyVestingBalance{},
   164  						},
   165  					},
   166  				}, e.Proto())
   167  			}).Times(1)
   168  
   169  			v.OnEpochEvent(ctx, types.Epoch{
   170  				Action: vegapb.EpochAction_EPOCH_ACTION_END,
   171  				Seq:    epochSeq,
   172  			})
   173  		}
   174  	})
   175  
   176  	t.Run("First reward payment", func(t *testing.T) {
   177  		epochSeq += 1
   178  
   179  		expectLedgerMovements(t, v)
   180  
   181  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   182  			e, ok := evt.(*events.VestingStatsUpdated)
   183  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   184  			assert.Equal(t, eventspb.VestingStatsUpdated{
   185  				AtEpoch: epochSeq,
   186  				Stats: []*eventspb.PartyVestingStats{
   187  					{
   188  						PartyId:                     party,
   189  						RewardBonusMultiplier:       "2",
   190  						QuantumBalance:              "390",
   191  						SummedRewardBonusMultiplier: "2",
   192  						SummedQuantumBalance:        "390",
   193  					},
   194  				},
   195  			}, e.Proto())
   196  		}).Times(1)
   197  
   198  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   199  			e, ok := evt.(*events.VestingBalancesSummary)
   200  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   201  			assert.Equal(t, eventspb.VestingBalancesSummary{
   202  				EpochSeq: epochSeq,
   203  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{
   204  					{
   205  						Party:               party,
   206  						PartyLockedBalances: []*eventspb.PartyLockedBalance{},
   207  						PartyVestingBalances: []*eventspb.PartyVestingBalance{
   208  							{
   209  								Asset:   vegaAsset,
   210  								Balance: "10",
   211  							},
   212  						},
   213  					},
   214  				},
   215  			}, e.Proto())
   216  		}).Times(1)
   217  
   218  		v.OnEpochEvent(ctx, types.Epoch{
   219  			Seq:    epochSeq,
   220  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   221  		})
   222  	})
   223  
   224  	t.Run("Second reward payment", func(t *testing.T) {
   225  		epochSeq += 1
   226  
   227  		expectLedgerMovements(t, v)
   228  
   229  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   230  			e, ok := evt.(*events.VestingStatsUpdated)
   231  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   232  			assert.Equal(t, eventspb.VestingStatsUpdated{
   233  				AtEpoch: epochSeq,
   234  				Stats: []*eventspb.PartyVestingStats{
   235  					{
   236  						PartyId:                     party,
   237  						RewardBonusMultiplier:       "2",
   238  						QuantumBalance:              "400",
   239  						SummedRewardBonusMultiplier: "2",
   240  						SummedQuantumBalance:        "400",
   241  					},
   242  				},
   243  			}, e.Proto())
   244  		}).Times(1)
   245  
   246  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   247  			e, ok := evt.(*events.VestingBalancesSummary)
   248  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   249  			assert.Equal(t, eventspb.VestingBalancesSummary{
   250  				EpochSeq:              epochSeq,
   251  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
   252  			}, e.Proto())
   253  		}).Times(1)
   254  
   255  		v.OnEpochEvent(ctx, types.Epoch{
   256  			Seq:    epochSeq,
   257  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   258  		})
   259  	})
   260  
   261  	t.Run("No vesting stats and summary when no reward is being vested anymore", func(t *testing.T) {
   262  		epochSeq += 1
   263  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   264  			e, ok := evt.(*events.VestingStatsUpdated)
   265  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   266  			assert.Equal(t, eventspb.VestingStatsUpdated{
   267  				AtEpoch: epochSeq,
   268  				Stats:   []*eventspb.PartyVestingStats{},
   269  			}, e.Proto())
   270  		}).Times(1)
   271  
   272  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   273  			e, ok := evt.(*events.VestingBalancesSummary)
   274  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   275  			assert.Equal(t, eventspb.VestingBalancesSummary{
   276  				EpochSeq:              epochSeq,
   277  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
   278  			}, e.Proto())
   279  		}).Times(1)
   280  
   281  		v.OnEpochEvent(ctx, types.Epoch{
   282  			Seq:    epochSeq,
   283  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   284  		})
   285  	})
   286  }
   287  
   288  func TestDistributeWithNoDelay(t *testing.T) {
   289  	v := getTestEngine(t)
   290  
   291  	ctx := context.Background()
   292  
   293  	// distribute 90% as the base rate,
   294  	// so first we distribute some, then we get under the minimum value, and all the rest
   295  	// is distributed
   296  	require.NoError(t, v.OnRewardVestingBaseRateUpdate(ctx, num.MustDecimalFromString("0.9")))
   297  	// this is multiplied by the quantum, so it will make it 100% of the quantum
   298  	require.NoError(t, v.OnRewardVestingMinimumTransferUpdate(ctx, num.MustDecimalFromString("1")))
   299  
   300  	require.NoError(t, v.OnBenefitTiersUpdate(ctx, &vegapb.VestingBenefitTiers{
   301  		Tiers: []*vegapb.VestingBenefitTier{
   302  			{
   303  				MinimumQuantumBalance: "200",
   304  				RewardMultiplier:      "1",
   305  			},
   306  			{
   307  				MinimumQuantumBalance: "350",
   308  				RewardMultiplier:      "2",
   309  			},
   310  			{
   311  				MinimumQuantumBalance: "500",
   312  				RewardMultiplier:      "3",
   313  			},
   314  		},
   315  	}))
   316  
   317  	// set the asvm to return always 1
   318  	v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1"))
   319  
   320  	// set asset to return proper quantum
   321  	v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
   322  
   323  	party := "party1"
   324  	vegaAsset := "VEGA"
   325  
   326  	v.parties.EXPECT().RelatedKeys(party).Return(nil, nil).AnyTimes()
   327  
   328  	v.col.InitVestedBalance(party, vegaAsset, num.NewUint(300))
   329  
   330  	epochSeq := uint64(1)
   331  
   332  	t.Run("No vesting stats and summary when no reward is being vested", func(t *testing.T) {
   333  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   334  			e, ok := evt.(*events.VestingStatsUpdated)
   335  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   336  			assert.Equal(t, eventspb.VestingStatsUpdated{
   337  				AtEpoch: epochSeq,
   338  				Stats:   []*eventspb.PartyVestingStats{},
   339  			}, e.Proto())
   340  		}).Times(1)
   341  
   342  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   343  			e, ok := evt.(*events.VestingBalancesSummary)
   344  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   345  			assert.Equal(t, eventspb.VestingBalancesSummary{
   346  				EpochSeq:              epochSeq,
   347  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
   348  			}, e.Proto())
   349  		}).Times(1)
   350  
   351  		v.OnEpochEvent(ctx, types.Epoch{
   352  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   353  			Seq:    epochSeq,
   354  		})
   355  	})
   356  
   357  	t.Run("Add a reward without epoch lock", func(t *testing.T) {
   358  		v.AddReward(context.Background(), party, vegaAsset, num.NewUint(100), 0)
   359  	})
   360  
   361  	t.Run("First reward payment", func(t *testing.T) {
   362  		epochSeq += 1
   363  
   364  		expectLedgerMovements(t, v)
   365  
   366  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   367  			e, ok := evt.(*events.VestingStatsUpdated)
   368  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   369  			assert.Equal(t, eventspb.VestingStatsUpdated{
   370  				AtEpoch: epochSeq,
   371  				Stats: []*eventspb.PartyVestingStats{
   372  					{
   373  						PartyId:                     party,
   374  						RewardBonusMultiplier:       "2",
   375  						QuantumBalance:              "390",
   376  						SummedRewardBonusMultiplier: "2",
   377  						SummedQuantumBalance:        "390",
   378  					},
   379  				},
   380  			}, e.Proto())
   381  		}).Times(1)
   382  
   383  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   384  			e, ok := evt.(*events.VestingBalancesSummary)
   385  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   386  			assert.Equal(t, eventspb.VestingBalancesSummary{
   387  				EpochSeq: epochSeq,
   388  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{
   389  					{
   390  						Party:               party,
   391  						PartyLockedBalances: []*eventspb.PartyLockedBalance{},
   392  						PartyVestingBalances: []*eventspb.PartyVestingBalance{
   393  							{
   394  								Asset:   vegaAsset,
   395  								Balance: "10",
   396  							},
   397  						},
   398  					},
   399  				},
   400  			}, e.Proto())
   401  		}).Times(1)
   402  
   403  		v.OnEpochEvent(ctx, types.Epoch{
   404  			Seq:    epochSeq,
   405  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   406  		})
   407  	})
   408  
   409  	t.Run("Second reward payment", func(t *testing.T) {
   410  		epochSeq += 1
   411  
   412  		expectLedgerMovements(t, v)
   413  
   414  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   415  			e, ok := evt.(*events.VestingStatsUpdated)
   416  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   417  			assert.Equal(t, eventspb.VestingStatsUpdated{
   418  				AtEpoch: epochSeq,
   419  				Stats: []*eventspb.PartyVestingStats{
   420  					{
   421  						PartyId:                     party,
   422  						RewardBonusMultiplier:       "2",
   423  						QuantumBalance:              "400",
   424  						SummedRewardBonusMultiplier: "2",
   425  						SummedQuantumBalance:        "400",
   426  					},
   427  				},
   428  			}, e.Proto())
   429  		}).Times(1)
   430  
   431  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   432  			e, ok := evt.(*events.VestingBalancesSummary)
   433  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   434  			assert.Equal(t, eventspb.VestingBalancesSummary{
   435  				EpochSeq:              epochSeq,
   436  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
   437  			}, e.Proto())
   438  		}).Times(1)
   439  
   440  		v.OnEpochEvent(ctx, types.Epoch{
   441  			Seq:    epochSeq,
   442  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   443  		})
   444  	})
   445  
   446  	t.Run("No vesting stats and summary when no reward is being vested anymore", func(t *testing.T) {
   447  		epochSeq += 1
   448  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   449  			e, ok := evt.(*events.VestingStatsUpdated)
   450  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   451  			assert.Equal(t, eventspb.VestingStatsUpdated{
   452  				AtEpoch: epochSeq,
   453  				Stats:   []*eventspb.PartyVestingStats{},
   454  			}, e.Proto())
   455  		}).Times(1)
   456  
   457  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   458  			e, ok := evt.(*events.VestingBalancesSummary)
   459  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   460  			assert.Equal(t, eventspb.VestingBalancesSummary{
   461  				EpochSeq:              epochSeq,
   462  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
   463  			}, e.Proto())
   464  		}).Times(1)
   465  
   466  		v.OnEpochEvent(ctx, types.Epoch{
   467  			Seq:    epochSeq,
   468  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   469  		})
   470  	})
   471  }
   472  
   473  func TestDistributeWithStreakRate(t *testing.T) {
   474  	v := getTestEngine(t)
   475  
   476  	ctx := context.Background()
   477  
   478  	// distribute 90% as the base rate,
   479  	// so first we distribute some, then we get under the minimum value, and all the rest
   480  	// is distributed
   481  	require.NoError(t, v.OnRewardVestingBaseRateUpdate(ctx, num.MustDecimalFromString("0.9")))
   482  	// this is multiplied by the quantum, so it will make it 100% of the quantum
   483  	require.NoError(t, v.OnRewardVestingMinimumTransferUpdate(ctx, num.MustDecimalFromString("1")))
   484  
   485  	require.NoError(t, v.OnBenefitTiersUpdate(ctx, &vegapb.VestingBenefitTiers{
   486  		Tiers: []*vegapb.VestingBenefitTier{
   487  			{
   488  				MinimumQuantumBalance: "200",
   489  				RewardMultiplier:      "1",
   490  			},
   491  			{
   492  				MinimumQuantumBalance: "350",
   493  				RewardMultiplier:      "2",
   494  			},
   495  			{
   496  				MinimumQuantumBalance: "500",
   497  				RewardMultiplier:      "3",
   498  			},
   499  		},
   500  	}))
   501  
   502  	v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1.1"))
   503  	v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
   504  
   505  	party := "party1"
   506  	vegaAsset := "VEGA"
   507  
   508  	v.parties.EXPECT().RelatedKeys(party).Return(nil, nil).AnyTimes()
   509  
   510  	v.col.InitVestedBalance(party, vegaAsset, num.NewUint(300))
   511  
   512  	epochSeq := uint64(1)
   513  
   514  	t.Run("No vesting stats and summary when no reward is being vested", func(t *testing.T) {
   515  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   516  			e, ok := evt.(*events.VestingStatsUpdated)
   517  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   518  			assert.Equal(t, eventspb.VestingStatsUpdated{
   519  				AtEpoch: epochSeq,
   520  				Stats:   []*eventspb.PartyVestingStats{},
   521  			}, e.Proto())
   522  		}).Times(1)
   523  
   524  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   525  			e, ok := evt.(*events.VestingBalancesSummary)
   526  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   527  			assert.Equal(t, eventspb.VestingBalancesSummary{
   528  				EpochSeq:              epochSeq,
   529  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
   530  			}, e.Proto())
   531  		}).Times(1)
   532  
   533  		v.OnEpochEvent(ctx, types.Epoch{
   534  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   535  			Seq:    epochSeq,
   536  		})
   537  	})
   538  
   539  	t.Run("Add a reward without epoch lock", func(t *testing.T) {
   540  		v.AddReward(context.Background(), party, vegaAsset, num.NewUint(100), 0)
   541  	})
   542  
   543  	t.Run("First reward payment", func(t *testing.T) {
   544  		epochSeq += 1
   545  
   546  		expectLedgerMovements(t, v)
   547  
   548  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   549  			e, ok := evt.(*events.VestingStatsUpdated)
   550  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   551  			assert.Equal(t, eventspb.VestingStatsUpdated{
   552  				AtEpoch: epochSeq,
   553  				Stats: []*eventspb.PartyVestingStats{
   554  					{
   555  						PartyId:                     party,
   556  						RewardBonusMultiplier:       "2",
   557  						QuantumBalance:              "399",
   558  						SummedRewardBonusMultiplier: "2",
   559  						SummedQuantumBalance:        "399",
   560  					},
   561  				},
   562  			}, e.Proto())
   563  		}).Times(1)
   564  
   565  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   566  			e, ok := evt.(*events.VestingBalancesSummary)
   567  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   568  			assert.Equal(t, eventspb.VestingBalancesSummary{
   569  				EpochSeq: epochSeq,
   570  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{
   571  					{
   572  						Party:               party,
   573  						PartyLockedBalances: []*eventspb.PartyLockedBalance{},
   574  						PartyVestingBalances: []*eventspb.PartyVestingBalance{
   575  							{
   576  								Asset:   vegaAsset,
   577  								Balance: "1",
   578  							},
   579  						},
   580  					},
   581  				},
   582  			}, e.Proto())
   583  		}).Times(1)
   584  
   585  		v.OnEpochEvent(ctx, types.Epoch{
   586  			Seq:    epochSeq,
   587  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   588  		})
   589  	})
   590  
   591  	t.Run("Second reward payment", func(t *testing.T) {
   592  		epochSeq += 1
   593  
   594  		expectLedgerMovements(t, v)
   595  
   596  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   597  			e, ok := evt.(*events.VestingStatsUpdated)
   598  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   599  			assert.Equal(t, eventspb.VestingStatsUpdated{
   600  				AtEpoch: epochSeq,
   601  				Stats: []*eventspb.PartyVestingStats{
   602  					{
   603  						PartyId:                     party,
   604  						RewardBonusMultiplier:       "2",
   605  						QuantumBalance:              "400",
   606  						SummedRewardBonusMultiplier: "2",
   607  						SummedQuantumBalance:        "400",
   608  					},
   609  				},
   610  			}, e.Proto())
   611  		}).Times(1)
   612  
   613  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   614  			e, ok := evt.(*events.VestingBalancesSummary)
   615  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   616  			assert.Equal(t, eventspb.VestingBalancesSummary{
   617  				EpochSeq:              epochSeq,
   618  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
   619  			}, e.Proto())
   620  		}).Times(1)
   621  
   622  		v.OnEpochEvent(ctx, types.Epoch{
   623  			Seq:    epochSeq,
   624  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   625  		})
   626  	})
   627  
   628  	t.Run("No vesting stats and summary when no reward is being vested anymore", func(t *testing.T) {
   629  		epochSeq += 1
   630  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   631  			e, ok := evt.(*events.VestingStatsUpdated)
   632  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   633  			assert.Equal(t, eventspb.VestingStatsUpdated{
   634  				AtEpoch: epochSeq,
   635  				Stats:   []*eventspb.PartyVestingStats{},
   636  			}, e.Proto())
   637  		}).Times(1)
   638  
   639  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   640  			e, ok := evt.(*events.VestingBalancesSummary)
   641  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   642  			assert.Equal(t, eventspb.VestingBalancesSummary{
   643  				EpochSeq:              epochSeq,
   644  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
   645  			}, e.Proto())
   646  		}).Times(1)
   647  
   648  		v.OnEpochEvent(ctx, types.Epoch{
   649  			Seq:    epochSeq,
   650  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   651  		})
   652  	})
   653  }
   654  
   655  func TestDistributeMultipleAfterDelay(t *testing.T) {
   656  	v := getTestEngine(t)
   657  
   658  	ctx := context.Background()
   659  
   660  	// distribute 90% as the base rate,
   661  	// so first we distribute some, then we get under the minimum value, and all the rest
   662  	// is distributed
   663  	require.NoError(t, v.OnRewardVestingBaseRateUpdate(ctx, num.MustDecimalFromString("0.9")))
   664  	// this is multiplied by the quantum, so it will make it 100% of the quantum
   665  	require.NoError(t, v.OnRewardVestingMinimumTransferUpdate(ctx, num.MustDecimalFromString("1")))
   666  
   667  	require.NoError(t, v.OnBenefitTiersUpdate(ctx, &vegapb.VestingBenefitTiers{
   668  		Tiers: []*vegapb.VestingBenefitTier{
   669  			{
   670  				MinimumQuantumBalance: "200",
   671  				RewardMultiplier:      "1",
   672  			},
   673  			{
   674  				MinimumQuantumBalance: "350",
   675  				RewardMultiplier:      "2",
   676  			},
   677  			{
   678  				MinimumQuantumBalance: "500",
   679  				RewardMultiplier:      "3",
   680  			},
   681  		},
   682  	}))
   683  
   684  	v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1"))
   685  	v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
   686  
   687  	party := "party1"
   688  	vegaAsset := "VEGA"
   689  
   690  	v.parties.EXPECT().RelatedKeys(party).Return(nil, nil).AnyTimes()
   691  
   692  	v.col.InitVestedBalance(party, vegaAsset, num.NewUint(300))
   693  
   694  	epochSeq := uint64(1)
   695  
   696  	t.Run("No vesting stats and summary when no reward is being vested", func(t *testing.T) {
   697  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   698  			e, ok := evt.(*events.VestingStatsUpdated)
   699  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   700  			assert.Equal(t, eventspb.VestingStatsUpdated{
   701  				AtEpoch: epochSeq,
   702  				Stats:   []*eventspb.PartyVestingStats{},
   703  			}, e.Proto())
   704  		}).Times(1)
   705  
   706  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   707  			e, ok := evt.(*events.VestingBalancesSummary)
   708  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   709  			assert.Equal(t, eventspb.VestingBalancesSummary{
   710  				EpochSeq:              epochSeq,
   711  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
   712  			}, e.Proto())
   713  		}).Times(1)
   714  
   715  		v.OnEpochEvent(ctx, types.Epoch{
   716  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   717  			Seq:    epochSeq,
   718  		})
   719  	})
   720  
   721  	t.Run("Add a reward locked for 2 epochs", func(t *testing.T) {
   722  		v.AddReward(context.Background(), party, vegaAsset, num.NewUint(100), 2)
   723  	})
   724  
   725  	t.Run("Add another reward locked for 1 epoch", func(t *testing.T) {
   726  		v.AddReward(context.Background(), party, vegaAsset, num.NewUint(100), 1)
   727  	})
   728  
   729  	t.Run("Wait for 1 epoch", func(t *testing.T) {
   730  		epochSeq += 1
   731  
   732  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   733  			e, ok := evt.(*events.VestingStatsUpdated)
   734  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   735  			assert.Equal(t, eventspb.VestingStatsUpdated{
   736  				AtEpoch: epochSeq,
   737  				Stats: []*eventspb.PartyVestingStats{
   738  					{
   739  						PartyId:                     party,
   740  						RewardBonusMultiplier:       "1",
   741  						QuantumBalance:              "300",
   742  						SummedRewardBonusMultiplier: "1",
   743  						SummedQuantumBalance:        "300",
   744  					},
   745  				},
   746  			}, e.Proto())
   747  		}).Times(1)
   748  
   749  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   750  			e, ok := evt.(*events.VestingBalancesSummary)
   751  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   752  			assert.Equal(t, eventspb.VestingBalancesSummary{
   753  				EpochSeq: epochSeq,
   754  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{
   755  					{
   756  						Party: party,
   757  						PartyLockedBalances: []*eventspb.PartyLockedBalance{
   758  							{
   759  								Asset:      vegaAsset,
   760  								UntilEpoch: 3,
   761  								Balance:    "100",
   762  							},
   763  							{
   764  								Asset:      vegaAsset,
   765  								UntilEpoch: 4,
   766  								Balance:    "100",
   767  							},
   768  						},
   769  						PartyVestingBalances: []*eventspb.PartyVestingBalance{},
   770  					},
   771  				},
   772  			}, e.Proto())
   773  		}).Times(1)
   774  
   775  		v.OnEpochEvent(ctx, types.Epoch{
   776  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   777  			Seq:    epochSeq,
   778  		})
   779  	})
   780  
   781  	t.Run("First reward payment", func(t *testing.T) {
   782  		epochSeq += 1
   783  
   784  		expectLedgerMovements(t, v)
   785  
   786  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   787  			e, ok := evt.(*events.VestingStatsUpdated)
   788  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   789  			assert.Equal(t, eventspb.VestingStatsUpdated{
   790  				AtEpoch: epochSeq,
   791  				Stats: []*eventspb.PartyVestingStats{
   792  					{
   793  						PartyId:                     party,
   794  						RewardBonusMultiplier:       "2",
   795  						QuantumBalance:              "390",
   796  						SummedRewardBonusMultiplier: "2",
   797  						SummedQuantumBalance:        "390",
   798  					},
   799  				},
   800  			}, e.Proto())
   801  		}).Times(1)
   802  
   803  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   804  			e, ok := evt.(*events.VestingBalancesSummary)
   805  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   806  			assert.Equal(t, eventspb.VestingBalancesSummary{
   807  				EpochSeq: epochSeq,
   808  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{
   809  					{
   810  						Party: party,
   811  						PartyLockedBalances: []*eventspb.PartyLockedBalance{
   812  							{
   813  								Asset:      vegaAsset,
   814  								UntilEpoch: 4,
   815  								Balance:    "100",
   816  							},
   817  						},
   818  						PartyVestingBalances: []*eventspb.PartyVestingBalance{
   819  							{
   820  								Asset:   vegaAsset,
   821  								Balance: "10",
   822  							},
   823  						},
   824  					},
   825  				},
   826  			}, e.Proto())
   827  		}).Times(1)
   828  
   829  		v.OnEpochEvent(ctx, types.Epoch{
   830  			Seq:    epochSeq,
   831  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   832  		})
   833  	})
   834  
   835  	t.Run("Second reward payment", func(t *testing.T) {
   836  		epochSeq += 1
   837  
   838  		expectLedgerMovements(t, v)
   839  
   840  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   841  			e, ok := evt.(*events.VestingStatsUpdated)
   842  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   843  			assert.Equal(t, eventspb.VestingStatsUpdated{
   844  				AtEpoch: epochSeq,
   845  				Stats: []*eventspb.PartyVestingStats{
   846  					{
   847  						PartyId:                     party,
   848  						RewardBonusMultiplier:       "2",
   849  						QuantumBalance:              "489",
   850  						SummedRewardBonusMultiplier: "2",
   851  						SummedQuantumBalance:        "489",
   852  					},
   853  				},
   854  			}, e.Proto())
   855  		}).Times(1)
   856  
   857  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   858  			e, ok := evt.(*events.VestingBalancesSummary)
   859  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   860  			assert.Equal(t, eventspb.VestingBalancesSummary{
   861  				EpochSeq: epochSeq,
   862  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{
   863  					{
   864  						Party:               party,
   865  						PartyLockedBalances: []*eventspb.PartyLockedBalance{},
   866  						PartyVestingBalances: []*eventspb.PartyVestingBalance{
   867  							{
   868  								Asset:   vegaAsset,
   869  								Balance: "11",
   870  							},
   871  						},
   872  					},
   873  				},
   874  			}, e.Proto())
   875  		}).Times(1)
   876  
   877  		v.OnEpochEvent(ctx, types.Epoch{
   878  			Seq:    epochSeq,
   879  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   880  		})
   881  	})
   882  
   883  	t.Run("Third reward payment", func(t *testing.T) {
   884  		epochSeq += 1
   885  
   886  		expectLedgerMovements(t, v)
   887  
   888  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   889  			e, ok := evt.(*events.VestingStatsUpdated)
   890  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   891  			assert.Equal(t, eventspb.VestingStatsUpdated{
   892  				AtEpoch: epochSeq,
   893  				Stats: []*eventspb.PartyVestingStats{
   894  					{
   895  						PartyId:                     party,
   896  						RewardBonusMultiplier:       "2",
   897  						QuantumBalance:              "499",
   898  						SummedRewardBonusMultiplier: "2",
   899  						SummedQuantumBalance:        "499",
   900  					},
   901  				},
   902  			}, e.Proto())
   903  		}).Times(1)
   904  
   905  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   906  			e, ok := evt.(*events.VestingBalancesSummary)
   907  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   908  			assert.Equal(t, eventspb.VestingBalancesSummary{
   909  				EpochSeq: epochSeq,
   910  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{
   911  					{
   912  						Party:               party,
   913  						PartyLockedBalances: []*eventspb.PartyLockedBalance{},
   914  						PartyVestingBalances: []*eventspb.PartyVestingBalance{
   915  							{
   916  								Asset:   vegaAsset,
   917  								Balance: "1",
   918  							},
   919  						},
   920  					},
   921  				},
   922  			}, e.Proto())
   923  		}).Times(1)
   924  
   925  		v.OnEpochEvent(ctx, types.Epoch{
   926  			Seq:    epochSeq,
   927  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   928  		})
   929  	})
   930  
   931  	t.Run("Fourth reward payment", func(t *testing.T) {
   932  		epochSeq += 1
   933  
   934  		expectLedgerMovements(t, v)
   935  
   936  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   937  			e, ok := evt.(*events.VestingStatsUpdated)
   938  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   939  			assert.Equal(t, eventspb.VestingStatsUpdated{
   940  				AtEpoch: epochSeq,
   941  				Stats: []*eventspb.PartyVestingStats{
   942  					{
   943  						PartyId:                     party,
   944  						RewardBonusMultiplier:       "3",
   945  						QuantumBalance:              "500",
   946  						SummedRewardBonusMultiplier: "3",
   947  						SummedQuantumBalance:        "500",
   948  					},
   949  				},
   950  			}, e.Proto())
   951  		}).Times(1)
   952  
   953  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   954  			e, ok := evt.(*events.VestingBalancesSummary)
   955  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   956  			assert.Equal(t, eventspb.VestingBalancesSummary{
   957  				EpochSeq:              epochSeq,
   958  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
   959  			}, e.Proto())
   960  		}).Times(1)
   961  
   962  		v.OnEpochEvent(ctx, types.Epoch{
   963  			Seq:    epochSeq,
   964  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   965  		})
   966  	})
   967  
   968  	t.Run("No vesting stats and summary when no reward is being vested anymore", func(t *testing.T) {
   969  		epochSeq += 1
   970  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   971  			e, ok := evt.(*events.VestingStatsUpdated)
   972  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
   973  			assert.Equal(t, eventspb.VestingStatsUpdated{
   974  				AtEpoch: epochSeq,
   975  				Stats:   []*eventspb.PartyVestingStats{},
   976  			}, e.Proto())
   977  		}).Times(1)
   978  
   979  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   980  			e, ok := evt.(*events.VestingBalancesSummary)
   981  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
   982  			assert.Equal(t, eventspb.VestingBalancesSummary{
   983  				EpochSeq:              epochSeq,
   984  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
   985  			}, e.Proto())
   986  		}).Times(1)
   987  
   988  		v.OnEpochEvent(ctx, types.Epoch{
   989  			Seq:    epochSeq,
   990  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
   991  		})
   992  	})
   993  }
   994  
   995  func TestDistributeWithRelatedKeys(t *testing.T) {
   996  	v := getTestEngine(t)
   997  
   998  	ctx := context.Background()
   999  
  1000  	// distribute 90% as the base rate,
  1001  	// so first we distribute some, then we get under the minimum value, and all the rest
  1002  	// is distributed
  1003  	require.NoError(t, v.OnRewardVestingBaseRateUpdate(ctx, num.MustDecimalFromString("0.9")))
  1004  	// this is multiplied by the quantum, so it will make it 100% of the quantum
  1005  	require.NoError(t, v.OnRewardVestingMinimumTransferUpdate(ctx, num.MustDecimalFromString("1")))
  1006  
  1007  	require.NoError(t, v.OnBenefitTiersUpdate(ctx, &vegapb.VestingBenefitTiers{
  1008  		Tiers: []*vegapb.VestingBenefitTier{
  1009  			{
  1010  				MinimumQuantumBalance: "200",
  1011  				RewardMultiplier:      "1",
  1012  			},
  1013  			{
  1014  				MinimumQuantumBalance: "350",
  1015  				RewardMultiplier:      "2",
  1016  			},
  1017  			{
  1018  				MinimumQuantumBalance: "500",
  1019  				RewardMultiplier:      "3",
  1020  			},
  1021  		},
  1022  	}))
  1023  
  1024  	// set the asvm to return always 1
  1025  	v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1"))
  1026  
  1027  	// set asset to return proper quantum
  1028  	v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
  1029  
  1030  	party := "party1"
  1031  	partyID := types.PartyID(party)
  1032  	vegaAsset := "VEGA"
  1033  	derivedKeys := []string{"derived1", "derived2", "derived3"}
  1034  
  1035  	v.parties.EXPECT().RelatedKeys(party).Return(&partyID, derivedKeys).AnyTimes()
  1036  
  1037  	v.col.InitVestedBalance(party, vegaAsset, num.NewUint(300))
  1038  
  1039  	for _, key := range derivedKeys {
  1040  		v.col.InitVestedBalance(key, vegaAsset, num.NewUint(100))
  1041  		v.parties.EXPECT().RelatedKeys(key).Return(&partyID, derivedKeys).AnyTimes()
  1042  	}
  1043  
  1044  	epochSeq := uint64(1)
  1045  
  1046  	t.Run("No vesting stats and summary when no reward is being vested", func(t *testing.T) {
  1047  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
  1048  			e, ok := evt.(*events.VestingStatsUpdated)
  1049  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
  1050  			assert.Equal(t, eventspb.VestingStatsUpdated{
  1051  				AtEpoch: epochSeq,
  1052  				Stats:   []*eventspb.PartyVestingStats{},
  1053  			}, e.Proto())
  1054  		}).Times(1)
  1055  
  1056  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
  1057  			e, ok := evt.(*events.VestingBalancesSummary)
  1058  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
  1059  			assert.Equal(t, eventspb.VestingBalancesSummary{
  1060  				EpochSeq:              epochSeq,
  1061  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
  1062  			}, e.Proto())
  1063  		}).Times(1)
  1064  
  1065  		v.OnEpochEvent(ctx, types.Epoch{
  1066  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
  1067  			Seq:    epochSeq,
  1068  		})
  1069  	})
  1070  
  1071  	t.Run("Add a reward without epoch lock", func(t *testing.T) {
  1072  		v.AddReward(context.Background(), party, vegaAsset, num.NewUint(100), 0)
  1073  
  1074  		for _, key := range derivedKeys {
  1075  			v.AddReward(context.Background(), key, vegaAsset, num.NewUint(50), 0)
  1076  		}
  1077  	})
  1078  
  1079  	t.Run("First reward payment", func(t *testing.T) {
  1080  		epochSeq += 1
  1081  
  1082  		expectLedgerMovements(t, v)
  1083  
  1084  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
  1085  			e, ok := evt.(*events.VestingStatsUpdated)
  1086  
  1087  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
  1088  			assert.Equal(t, eventspb.VestingStatsUpdated{
  1089  				AtEpoch: epochSeq,
  1090  				Stats: []*eventspb.PartyVestingStats{
  1091  					{
  1092  						PartyId:                     derivedKeys[0],
  1093  						RewardBonusMultiplier:       "1",
  1094  						QuantumBalance:              "145",
  1095  						SummedRewardBonusMultiplier: "3",
  1096  						SummedQuantumBalance:        "825",
  1097  					},
  1098  					{
  1099  						PartyId:                     derivedKeys[1],
  1100  						RewardBonusMultiplier:       "1",
  1101  						QuantumBalance:              "145",
  1102  						SummedRewardBonusMultiplier: "3",
  1103  						SummedQuantumBalance:        "825",
  1104  					},
  1105  					{
  1106  						PartyId:                     derivedKeys[2],
  1107  						RewardBonusMultiplier:       "1",
  1108  						QuantumBalance:              "145",
  1109  						SummedRewardBonusMultiplier: "3",
  1110  						SummedQuantumBalance:        "825",
  1111  					},
  1112  					{
  1113  						PartyId:                     party,
  1114  						RewardBonusMultiplier:       "2",
  1115  						QuantumBalance:              "390",
  1116  						SummedRewardBonusMultiplier: "3",
  1117  						SummedQuantumBalance:        "825",
  1118  					},
  1119  				},
  1120  			}, e.Proto())
  1121  		}).Times(1)
  1122  
  1123  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
  1124  			e, ok := evt.(*events.VestingBalancesSummary)
  1125  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
  1126  			assert.Equal(t, eventspb.VestingBalancesSummary{
  1127  				EpochSeq: epochSeq,
  1128  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{
  1129  					{
  1130  						Party:               derivedKeys[0],
  1131  						PartyLockedBalances: []*eventspb.PartyLockedBalance{},
  1132  						PartyVestingBalances: []*eventspb.PartyVestingBalance{
  1133  							{
  1134  								Asset:   vegaAsset,
  1135  								Balance: "5",
  1136  							},
  1137  						},
  1138  					},
  1139  					{
  1140  						Party:               derivedKeys[1],
  1141  						PartyLockedBalances: []*eventspb.PartyLockedBalance{},
  1142  						PartyVestingBalances: []*eventspb.PartyVestingBalance{
  1143  							{
  1144  								Asset:   vegaAsset,
  1145  								Balance: "5",
  1146  							},
  1147  						},
  1148  					},
  1149  					{
  1150  						Party:               derivedKeys[2],
  1151  						PartyLockedBalances: []*eventspb.PartyLockedBalance{},
  1152  						PartyVestingBalances: []*eventspb.PartyVestingBalance{
  1153  							{
  1154  								Asset:   vegaAsset,
  1155  								Balance: "5",
  1156  							},
  1157  						},
  1158  					},
  1159  					{
  1160  						Party:               party,
  1161  						PartyLockedBalances: []*eventspb.PartyLockedBalance{},
  1162  						PartyVestingBalances: []*eventspb.PartyVestingBalance{
  1163  							{
  1164  								Asset:   vegaAsset,
  1165  								Balance: "10",
  1166  							},
  1167  						},
  1168  					},
  1169  				},
  1170  			}, e.Proto())
  1171  		}).Times(1)
  1172  
  1173  		v.OnEpochEvent(ctx, types.Epoch{
  1174  			Seq:    epochSeq,
  1175  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
  1176  		})
  1177  	})
  1178  
  1179  	t.Run("Second reward payment", func(t *testing.T) {
  1180  		epochSeq += 1
  1181  
  1182  		expectLedgerMovements(t, v)
  1183  
  1184  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
  1185  			e, ok := evt.(*events.VestingStatsUpdated)
  1186  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
  1187  			assert.Equal(t, eventspb.VestingStatsUpdated{
  1188  				AtEpoch: epochSeq,
  1189  				Stats: []*eventspb.PartyVestingStats{
  1190  					{
  1191  						PartyId:                     derivedKeys[0],
  1192  						RewardBonusMultiplier:       "1",
  1193  						QuantumBalance:              "150",
  1194  						SummedRewardBonusMultiplier: "3",
  1195  						SummedQuantumBalance:        "850",
  1196  					},
  1197  					{
  1198  						PartyId:                     derivedKeys[1],
  1199  						RewardBonusMultiplier:       "1",
  1200  						QuantumBalance:              "150",
  1201  						SummedRewardBonusMultiplier: "3",
  1202  						SummedQuantumBalance:        "850",
  1203  					},
  1204  					{
  1205  						PartyId:                     derivedKeys[2],
  1206  						RewardBonusMultiplier:       "1",
  1207  						QuantumBalance:              "150",
  1208  						SummedRewardBonusMultiplier: "3",
  1209  						SummedQuantumBalance:        "850",
  1210  					},
  1211  					{
  1212  						PartyId:                     party,
  1213  						RewardBonusMultiplier:       "2",
  1214  						QuantumBalance:              "400",
  1215  						SummedRewardBonusMultiplier: "3",
  1216  						SummedQuantumBalance:        "850",
  1217  					},
  1218  				},
  1219  			}, e.Proto())
  1220  		}).Times(1)
  1221  
  1222  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
  1223  			e, ok := evt.(*events.VestingBalancesSummary)
  1224  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
  1225  			assert.Equal(t, eventspb.VestingBalancesSummary{
  1226  				EpochSeq:              epochSeq,
  1227  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
  1228  			}, e.Proto())
  1229  		}).Times(1)
  1230  
  1231  		v.OnEpochEvent(ctx, types.Epoch{
  1232  			Seq:    epochSeq,
  1233  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
  1234  		})
  1235  	})
  1236  
  1237  	t.Run("No vesting stats and summary when no reward is being vested anymore", func(t *testing.T) {
  1238  		epochSeq += 1
  1239  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
  1240  			e, ok := evt.(*events.VestingStatsUpdated)
  1241  			require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
  1242  			assert.Equal(t, eventspb.VestingStatsUpdated{
  1243  				AtEpoch: epochSeq,
  1244  				Stats:   []*eventspb.PartyVestingStats{},
  1245  			}, e.Proto())
  1246  		}).Times(1)
  1247  
  1248  		v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
  1249  			e, ok := evt.(*events.VestingBalancesSummary)
  1250  			require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
  1251  			assert.Equal(t, eventspb.VestingBalancesSummary{
  1252  				EpochSeq:              epochSeq,
  1253  				PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
  1254  			}, e.Proto())
  1255  		}).Times(1)
  1256  
  1257  		v.OnEpochEvent(ctx, types.Epoch{
  1258  			Seq:    epochSeq,
  1259  			Action: vegapb.EpochAction_EPOCH_ACTION_END,
  1260  		})
  1261  	})
  1262  }
  1263  
  1264  func TestGetRewardBonusMultiplier(t *testing.T) {
  1265  	v := getTestEngine(t)
  1266  
  1267  	ctx := context.Background()
  1268  
  1269  	// distribute 90% as the base rate,
  1270  	// so first we distribute some, then we get under the minimum value, and all the rest
  1271  	// is distributed
  1272  	require.NoError(t, v.OnRewardVestingBaseRateUpdate(ctx, num.MustDecimalFromString("0.9")))
  1273  	// this is multiplied by the quantum, so it will make it 100% of the quantum
  1274  	require.NoError(t, v.OnRewardVestingMinimumTransferUpdate(ctx, num.MustDecimalFromString("1")))
  1275  
  1276  	require.NoError(t, v.OnBenefitTiersUpdate(ctx, &vegapb.VestingBenefitTiers{
  1277  		Tiers: []*vegapb.VestingBenefitTier{
  1278  			{
  1279  				MinimumQuantumBalance: "200",
  1280  				RewardMultiplier:      "1",
  1281  			},
  1282  			{
  1283  				MinimumQuantumBalance: "500",
  1284  				RewardMultiplier:      "2",
  1285  			},
  1286  			{
  1287  				MinimumQuantumBalance: "1200",
  1288  				RewardMultiplier:      "3",
  1289  			},
  1290  		},
  1291  	}))
  1292  
  1293  	party := "party1"
  1294  	partyID := types.PartyID(party)
  1295  	vegaAsset := "VEGA"
  1296  	derivedKeys := []string{"derived1", "derived2", "derived3", "derived4"}
  1297  
  1298  	v.parties.EXPECT().RelatedKeys(party).Return(&partyID, derivedKeys).AnyTimes()
  1299  
  1300  	v.col.InitVestedBalance(party, vegaAsset, num.NewUint(500))
  1301  
  1302  	for _, key := range derivedKeys {
  1303  		v.col.InitVestedBalance(key, vegaAsset, num.NewUint(250))
  1304  		v.parties.EXPECT().RelatedKeys(key).Return(&partyID, derivedKeys).AnyTimes()
  1305  	}
  1306  
  1307  	for _, key := range append(derivedKeys, party) {
  1308  		_, summed := v.GetSingleAndSummedRewardBonusMultipliers(key)
  1309  		require.Equal(t, num.DecimalFromInt64(1500), summed.QuantumBalance)
  1310  		require.Equal(t, num.DecimalFromInt64(3), summed.Multiplier)
  1311  	}
  1312  
  1313  	// check that we only called the GetVestingQuantumBalance once for each key
  1314  	// later calls should be cached
  1315  	require.Equal(t, 5, v.col.GetVestingQuantumBalanceCallCount())
  1316  
  1317  	for _, key := range append(derivedKeys, party) {
  1318  		_, summed := v.GetSingleAndSummedRewardBonusMultipliers(key)
  1319  		require.Equal(t, num.DecimalFromInt64(1500), summed.QuantumBalance)
  1320  		require.Equal(t, num.DecimalFromInt64(3), summed.Multiplier)
  1321  	}
  1322  
  1323  	// all the calls above should be served from cache
  1324  	require.Equal(t, 5, v.col.GetVestingQuantumBalanceCallCount())
  1325  
  1326  	v.broker.EXPECT().Send(gomock.Any()).AnyTimes()
  1327  
  1328  	// now we simulate the end of the epoch
  1329  	// it will reset cache for reward bonus multipliers
  1330  	v.OnEpochEvent(ctx, types.Epoch{
  1331  		Action: vegapb.EpochAction_EPOCH_ACTION_END,
  1332  		Seq:    1,
  1333  	})
  1334  
  1335  	v.col.ResetVestingQuantumBalanceCallCount()
  1336  
  1337  	for _, key := range append(derivedKeys, party) {
  1338  		_, summed := v.GetSingleAndSummedRewardBonusMultipliers(key)
  1339  		require.Equal(t, num.DecimalFromInt64(1500), summed.QuantumBalance)
  1340  		require.Equal(t, num.DecimalFromInt64(3), summed.Multiplier)
  1341  	}
  1342  
  1343  	// now it's called 5 times again because the cache gets reset at the end of the epoch
  1344  	require.Equal(t, 5, v.col.GetVestingQuantumBalanceCallCount())
  1345  
  1346  	v.OnEpochEvent(ctx, types.Epoch{
  1347  		Action: vegapb.EpochAction_EPOCH_ACTION_END,
  1348  		Seq:    1,
  1349  	})
  1350  
  1351  	v.col.ResetVestingQuantumBalanceCallCount()
  1352  
  1353  	for _, key := range append(derivedKeys, party) {
  1354  		single, summed := v.GetSingleAndSummedRewardBonusMultipliers(key)
  1355  		require.Equal(t, num.DecimalFromInt64(1500), summed.QuantumBalance)
  1356  		require.Equal(t, num.DecimalFromInt64(3), summed.Multiplier)
  1357  
  1358  		if key == party {
  1359  			require.Equal(t, num.DecimalFromInt64(500), single.QuantumBalance)
  1360  			require.Equal(t, num.DecimalFromInt64(2), single.Multiplier)
  1361  		} else {
  1362  			require.Equal(t, num.DecimalFromInt64(250), single.QuantumBalance)
  1363  			require.Equal(t, num.DecimalFromInt64(1), single.Multiplier)
  1364  		}
  1365  	}
  1366  
  1367  	// now it's called 5 times again because the cache gets reset at the end of the epoch
  1368  	require.Equal(t, 5, v.col.GetVestingQuantumBalanceCallCount())
  1369  }
  1370  
  1371  // LedgerMovements is the result of a mock, so it doesn't really make sense to
  1372  // verify data consistency.
  1373  func expectLedgerMovements(t *testing.T, v *testEngine) {
  1374  	t.Helper()
  1375  
  1376  	v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
  1377  		e, ok := evt.(*events.LedgerMovements)
  1378  		require.True(t, ok, "Event should be a LedgerMovements, but is %T", evt)
  1379  		assert.Equal(t, eventspb.LedgerMovements{LedgerMovements: []*vegapb.LedgerMovement{}}, e.Proto())
  1380  	}).Times(1)
  1381  }