code.vegaprotocol.io/vega@v0.79.0/core/referral/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 referral_test
    17  
    18  import (
    19  	"context"
    20  	"testing"
    21  	"time"
    22  
    23  	"code.vegaprotocol.io/vega/core/referral"
    24  	"code.vegaprotocol.io/vega/core/types"
    25  	"code.vegaprotocol.io/vega/libs/num"
    26  	vgrand "code.vegaprotocol.io/vega/libs/rand"
    27  	vgtest "code.vegaprotocol.io/vega/libs/test"
    28  
    29  	"github.com/golang/mock/gomock"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  func TestReferralSet(t *testing.T) {
    35  	te := newEngine(t)
    36  
    37  	ctx := vgtest.VegaContext(vgrand.RandomStr(5), vgtest.RandomI64())
    38  
    39  	setID := newSetID(t)
    40  	setID2 := newSetID(t)
    41  	referrer := newPartyID(t)
    42  	referrer2 := newPartyID(t)
    43  	referee1 := newPartyID(t)
    44  
    45  	require.NoError(t, te.engine.OnReferralProgramMinStakedVegaTokensUpdate(context.Background(), num.NewUint(100)))
    46  
    47  	t.Run("querying for a non existing set return false", func(t *testing.T) {
    48  		require.ErrorIs(t, referral.ErrUnknownSetID, te.engine.PartyOwnsReferralSet(referrer, setID))
    49  	})
    50  
    51  	t.Run("cannot join a non-existing set", func(t *testing.T) {
    52  		err := te.engine.ApplyReferralCode(ctx, referee1, setID)
    53  		assert.EqualError(t, err, referral.ErrUnknownReferralCode(setID).Error())
    54  	})
    55  
    56  	t.Run("can create a set for the first time", func(t *testing.T) {
    57  		te.staking.EXPECT().GetAvailableBalance(string(referrer)).Return(num.NewUint(10001), nil).Times(1)
    58  		te.broker.EXPECT().Send(gomock.Any()).Times(1)
    59  		te.timeSvc.EXPECT().GetTimeNow().Times(1)
    60  
    61  		assert.NoError(t, te.engine.CreateReferralSet(ctx, referrer, setID))
    62  
    63  		// check ownership query
    64  		require.NoError(t, te.engine.PartyOwnsReferralSet(referrer, setID))
    65  		require.Error(t, referral.ErrPartyDoesNotOwnReferralSet(referrer2), te.engine.PartyOwnsReferralSet(referrer2, setID))
    66  	})
    67  
    68  	t.Run("cannot create a set multiple times", func(t *testing.T) {
    69  		assert.EqualError(t, te.engine.CreateReferralSet(ctx, referrer, setID),
    70  			referral.ErrIsAlreadyAReferrer(referrer).Error(),
    71  		)
    72  	})
    73  
    74  	t.Run("can join an existing set", func(t *testing.T) {
    75  		te.broker.EXPECT().Send(gomock.Any()).Times(1)
    76  		te.timeSvc.EXPECT().GetTimeNow().Times(1)
    77  		assert.NoError(t, te.engine.ApplyReferralCode(ctx, referee1, setID))
    78  	})
    79  
    80  	t.Run("cannot create a set when being a referee", func(t *testing.T) {
    81  		assert.EqualError(t, te.engine.CreateReferralSet(ctx, referee1, setID),
    82  			referral.ErrIsAlreadyAReferee(referee1).Error(),
    83  		)
    84  	})
    85  
    86  	t.Run("cannot become a referee twice for the same set", func(t *testing.T) {
    87  		assert.EqualError(t, te.engine.ApplyReferralCode(ctx, referee1, setID),
    88  			referral.ErrIsAlreadyAReferee(referee1).Error(),
    89  		)
    90  	})
    91  
    92  	t.Run("can create a second referrer", func(t *testing.T) {
    93  		te.staking.EXPECT().GetAvailableBalance(string(referrer2)).Return(num.NewUint(10001), nil).Times(1)
    94  		te.broker.EXPECT().Send(gomock.Any()).Times(1)
    95  		te.timeSvc.EXPECT().GetTimeNow().Times(1)
    96  		assert.NoError(t, te.engine.CreateReferralSet(ctx, referrer2, setID2))
    97  	})
    98  
    99  	t.Run("cannot switch set if initial set still have an OK staking balance", func(t *testing.T) {
   100  		te.staking.EXPECT().GetAvailableBalance(string(referrer)).Times(1).Return(num.NewUint(100), nil)
   101  		assert.EqualError(t, te.engine.ApplyReferralCode(ctx, referee1, setID2),
   102  			referral.ErrIsAlreadyAReferee(referee1).Error(),
   103  		)
   104  	})
   105  
   106  	t.Run("can switch set if initial set have an insufficient staking balance", func(t *testing.T) {
   107  		te.staking.EXPECT().GetAvailableBalance(string(referrer)).Times(1).Return(num.NewUint(99), nil)
   108  		te.broker.EXPECT().Send(gomock.Any()).Times(1)
   109  		te.timeSvc.EXPECT().GetTimeNow().Times(1)
   110  		assert.NoError(t, te.engine.ApplyReferralCode(ctx, referee1, setID2))
   111  	})
   112  }
   113  
   114  func TestUpdatingReferralProgramSucceeds(t *testing.T) {
   115  	ctx := vgtest.VegaContext(vgrand.RandomStr(5), vgtest.RandomI64())
   116  
   117  	te := newEngine(t)
   118  
   119  	require.True(t, te.engine.HasProgramEnded(), "There is no program yet, so the engine should behave as a program ended.")
   120  
   121  	program1 := &types.ReferralProgram{
   122  		EndOfProgramTimestamp: time.Now().Add(24 * time.Hour),
   123  		WindowLength:          10,
   124  		BenefitTiers:          []*types.BenefitTier{},
   125  	}
   126  
   127  	// Set the first program.
   128  	te.engine.UpdateProgram(program1)
   129  
   130  	require.True(t, te.engine.HasProgramEnded(), "The program should start only on the next epoch")
   131  
   132  	// Simulating end of epoch.
   133  	// The program should be applied.
   134  	expectReferralProgramStartedEvent(t, te)
   135  	lastEpochStartTime := program1.EndOfProgramTimestamp.Add(-2 * time.Hour)
   136  	nextEpoch(t, ctx, te, lastEpochStartTime)
   137  
   138  	require.False(t, te.engine.HasProgramEnded(), "The program should have started.")
   139  
   140  	// Simulating end of epoch.
   141  	// The program should have reached its end.
   142  	expectReferralProgramEndedEvent(t, te)
   143  	lastEpochStartTime = program1.EndOfProgramTimestamp.Add(1 * time.Second)
   144  	nextEpoch(t, ctx, te, lastEpochStartTime)
   145  
   146  	require.True(t, te.engine.HasProgramEnded(), "The program should have reached its ending.")
   147  
   148  	program2 := &types.ReferralProgram{
   149  		EndOfProgramTimestamp: lastEpochStartTime.Add(10 * time.Hour),
   150  		WindowLength:          10,
   151  		BenefitTiers:          []*types.BenefitTier{},
   152  	}
   153  
   154  	// Set second the program.
   155  	te.engine.UpdateProgram(program2)
   156  
   157  	require.True(t, te.engine.HasProgramEnded(), "The program should start only on the next epoch")
   158  
   159  	program3 := &types.ReferralProgram{
   160  		// Ending the program before the second one to show the engine replace the
   161  		// the previous program by this one
   162  		EndOfProgramTimestamp: program2.EndOfProgramTimestamp.Add(-5 * time.Hour),
   163  		WindowLength:          10,
   164  		BenefitTiers:          []*types.BenefitTier{},
   165  	}
   166  
   167  	// Override the second program by a third.
   168  	te.engine.UpdateProgram(program3)
   169  
   170  	// Simulating end of epoch.
   171  	// The third program should have started.
   172  	expectReferralProgramStartedEvent(t, te)
   173  	lastEpochStartTime = program3.EndOfProgramTimestamp.Add(-1 * time.Second)
   174  	nextEpoch(t, ctx, te, lastEpochStartTime)
   175  
   176  	require.False(t, te.engine.HasProgramEnded(), "The program should have started.")
   177  
   178  	program4 := &types.ReferralProgram{
   179  		EndOfProgramTimestamp: lastEpochStartTime.Add(10 * time.Hour),
   180  		WindowLength:          10,
   181  		BenefitTiers:          []*types.BenefitTier{},
   182  	}
   183  
   184  	// Update to replace the third program by the fourth one.
   185  	te.engine.UpdateProgram(program4)
   186  
   187  	// Simulating end of epoch.
   188  	// The current program should have been updated by the fourth one.
   189  	expectReferralProgramUpdatedEvent(t, te)
   190  	lastEpochStartTime = program4.EndOfProgramTimestamp.Add(-1 * time.Second)
   191  	nextEpoch(t, ctx, te, lastEpochStartTime)
   192  
   193  	program5 := &types.ReferralProgram{
   194  		EndOfProgramTimestamp: lastEpochStartTime.Add(10 * time.Hour),
   195  		WindowLength:          10,
   196  		BenefitTiers:          []*types.BenefitTier{},
   197  		StakingTiers:          []*types.StakingTier{},
   198  	}
   199  
   200  	// Update with new program.
   201  	te.engine.UpdateProgram(program5)
   202  
   203  	require.False(t, te.engine.HasProgramEnded(), "The fourth program should still be up")
   204  
   205  	// Simulating end of epoch.
   206  	// The fifth program should have ended before it even started.
   207  	gomock.InOrder(
   208  		expectReferralProgramUpdatedEvent(t, te),
   209  		expectReferralProgramEndedEvent(t, te),
   210  	)
   211  	lastEpochStartTime = program5.EndOfProgramTimestamp.Add(1 * time.Second)
   212  	nextEpoch(t, ctx, te, lastEpochStartTime)
   213  
   214  	require.True(t, te.engine.HasProgramEnded(), "The program should have ended before it even started")
   215  }
   216  
   217  func TestGettingRewardMultiplier(t *testing.T) {
   218  	ctx := vgtest.VegaContext(vgrand.RandomStr(5), vgtest.RandomI64())
   219  
   220  	te := newEngine(t)
   221  	require.NoError(t, te.engine.OnReferralProgramMinStakedVegaTokensUpdate(context.Background(), num.NewUint(100)))
   222  	require.NoError(t, te.engine.OnReferralProgramMaxReferralRewardProportionUpdate(context.Background(), num.MustDecimalFromString("0.5")))
   223  	maxVolumeParams := num.UintFromUint64(2000)
   224  
   225  	// Cap the notional volume.
   226  	require.NoError(t, te.engine.OnReferralProgramMaxPartyNotionalVolumeByQuantumPerEpochUpdate(ctx, maxVolumeParams))
   227  
   228  	program1 := &types.ReferralProgram{
   229  		EndOfProgramTimestamp: time.Now().Add(24 * time.Hour),
   230  		WindowLength:          2,
   231  		BenefitTiers: []*types.BenefitTier{
   232  			{
   233  				MinimumEpochs:                     num.UintFromUint64(2),
   234  				MinimumRunningNotionalTakerVolume: num.UintFromUint64(1000),
   235  				ReferralRewardFactors: types.Factors{
   236  					Maker:     num.DecimalFromFloat(0.001),
   237  					Infra:     num.DecimalFromFloat(0.001),
   238  					Liquidity: num.DecimalFromFloat(0.001),
   239  				},
   240  				ReferralDiscountFactors: types.Factors{
   241  					Maker:     num.DecimalFromFloat(0.002),
   242  					Infra:     num.DecimalFromFloat(0.002),
   243  					Liquidity: num.DecimalFromFloat(0.002),
   244  				},
   245  			},
   246  			{
   247  				MinimumEpochs:                     num.UintFromUint64(1),
   248  				MinimumRunningNotionalTakerVolume: num.UintFromUint64(1000),
   249  				ReferralRewardFactors: types.Factors{
   250  					Maker:     num.DecimalFromFloat(0.0005),
   251  					Infra:     num.DecimalFromFloat(0.0005),
   252  					Liquidity: num.DecimalFromFloat(0.0005),
   253  				},
   254  				ReferralDiscountFactors: types.Factors{
   255  					Maker:     num.DecimalFromFloat(0.001),
   256  					Infra:     num.DecimalFromFloat(0.001),
   257  					Liquidity: num.DecimalFromFloat(0.001),
   258  				},
   259  			},
   260  			{
   261  				MinimumEpochs:                     num.UintFromUint64(3),
   262  				MinimumRunningNotionalTakerVolume: num.UintFromUint64(1000),
   263  				ReferralRewardFactors: types.Factors{
   264  					Maker:     num.DecimalFromFloat(0.0007),
   265  					Infra:     num.DecimalFromFloat(0.0007),
   266  					Liquidity: num.DecimalFromFloat(0.0007),
   267  				},
   268  				ReferralDiscountFactors: types.Factors{
   269  					Maker:     num.DecimalFromFloat(0.002),
   270  					Infra:     num.DecimalFromFloat(0.002),
   271  					Liquidity: num.DecimalFromFloat(0.002),
   272  				},
   273  			},
   274  			{
   275  				MinimumEpochs:                     num.UintFromUint64(3),
   276  				MinimumRunningNotionalTakerVolume: num.UintFromUint64(3000),
   277  				ReferralRewardFactors: types.Factors{
   278  					Maker:     num.DecimalFromFloat(0.01),
   279  					Infra:     num.DecimalFromFloat(0.01),
   280  					Liquidity: num.DecimalFromFloat(0.01),
   281  				},
   282  				ReferralDiscountFactors: types.Factors{
   283  					Maker:     num.DecimalFromFloat(0.02),
   284  					Infra:     num.DecimalFromFloat(0.02),
   285  					Liquidity: num.DecimalFromFloat(0.02),
   286  				},
   287  			},
   288  			{
   289  				MinimumEpochs:                     num.UintFromUint64(4),
   290  				MinimumRunningNotionalTakerVolume: num.UintFromUint64(4000),
   291  				ReferralRewardFactors: types.Factors{
   292  					Maker:     num.DecimalFromFloat(0.1),
   293  					Infra:     num.DecimalFromFloat(0.1),
   294  					Liquidity: num.DecimalFromFloat(0.1),
   295  				},
   296  				ReferralDiscountFactors: types.Factors{
   297  					Maker:     num.DecimalFromFloat(0.2),
   298  					Infra:     num.DecimalFromFloat(0.2),
   299  					Liquidity: num.DecimalFromFloat(0.2),
   300  				},
   301  			},
   302  		},
   303  		StakingTiers: []*types.StakingTier{
   304  			{
   305  				MinimumStakedTokens:      num.NewUint(1000),
   306  				ReferralRewardMultiplier: num.MustDecimalFromString("1.5"),
   307  			},
   308  			{
   309  				MinimumStakedTokens:      num.NewUint(10000),
   310  				ReferralRewardMultiplier: num.MustDecimalFromString("2"),
   311  			},
   312  			{
   313  				MinimumStakedTokens:      num.NewUint(100000),
   314  				ReferralRewardMultiplier: num.MustDecimalFromString("2.5"),
   315  			},
   316  		},
   317  	}
   318  
   319  	// Set the first program.
   320  	te.engine.UpdateProgram(program1)
   321  
   322  	setID1 := newSetID(t)
   323  	referrer1 := newPartyID(t)
   324  	referee1 := newPartyID(t)
   325  
   326  	// When the epoch starts, the new program should start.
   327  	expectReferralProgramStartedEvent(t, te)
   328  	lastEpochStartTime := program1.EndOfProgramTimestamp.Add(-2 * time.Hour)
   329  	nextEpoch(t, ctx, te, lastEpochStartTime)
   330  
   331  	te.broker.EXPECT().Send(gomock.Any()).Times(2)
   332  	te.timeSvc.EXPECT().GetTimeNow().Return(time.Now()).Times(2)
   333  
   334  	te.staking.EXPECT().GetAvailableBalance(string(referrer1)).Return(num.NewUint(10001), nil).Times(1)
   335  
   336  	assert.NoError(t, te.engine.CreateReferralSet(ctx, referrer1, setID1))
   337  	assert.NoError(t, te.engine.ApplyReferralCode(ctx, referee1, setID1))
   338  
   339  	// When the epoch ends, the running volume for set members should be
   340  	// computed.
   341  	te.staking.EXPECT().GetAvailableBalance(string(referrer1)).Return(num.NewUint(10001), nil).Times(1)
   342  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer1)).Return(num.UintFromUint64(1500)).Times(1)
   343  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee1)).Return(num.UintFromUint64(2000)).Times(1)
   344  
   345  	// When the epoch starts, the new program should start.
   346  	lastEpochStartTime = program1.EndOfProgramTimestamp.Add(-2 * time.Hour)
   347  	expectReferralSetStatsUpdatedEvent(t, te, 1)
   348  	nextEpoch(t, ctx, te, lastEpochStartTime)
   349  
   350  	assert.Equal(t, "0.01", te.engine.RewardsFactorForParty(referee1).Infra.String())
   351  	assert.Equal(t, "2", te.engine.RewardsMultiplierForParty(referee1).String())
   352  	assert.Equal(t, "0.02", te.engine.RewardsFactorsMultiplierAppliedForParty(referee1).Infra.String())
   353  
   354  	// When the epoch ends, the running volume for set members should be
   355  	// computed.
   356  	// Makes the set not eligible for rewards anymore by simulating staking balance
   357  	// at 0.
   358  	te.staking.EXPECT().GetAvailableBalance(string(referrer1)).Return(num.NewUint(0), nil).Times(1)
   359  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer1)).Return(num.UintFromUint64(1500)).Times(1)
   360  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee1)).Return(num.UintFromUint64(2000)).Times(1)
   361  
   362  	// When the epoch starts, the new program should start.
   363  	lastEpochStartTime = program1.EndOfProgramTimestamp.Add(-1 * time.Hour)
   364  	expectReferralSetStatsUpdatedEvent(t, te, 1)
   365  	nextEpoch(t, ctx, te, lastEpochStartTime)
   366  
   367  	assert.Equal(t, "0", te.engine.RewardsFactorForParty(referee1).Infra.String())
   368  	assert.Equal(t, "1", te.engine.RewardsMultiplierForParty(referee1).String())
   369  	assert.Equal(t, "0", te.engine.RewardsFactorsMultiplierAppliedForParty(referee1).Infra.String())
   370  }
   371  
   372  func TestGettingRewardAndDiscountFactors(t *testing.T) {
   373  	ctx := vgtest.VegaContext(vgrand.RandomStr(5), vgtest.RandomI64())
   374  
   375  	te := newEngine(t)
   376  	require.NoError(t, te.engine.OnReferralProgramMinStakedVegaTokensUpdate(context.Background(), num.NewUint(100)))
   377  
   378  	setID1 := newSetID(t)
   379  	setID2 := newSetID(t)
   380  	referrer1 := newPartyID(t)
   381  	referrer2 := newPartyID(t)
   382  	referee1 := newPartyID(t)
   383  	referee2 := newPartyID(t)
   384  	referee3 := newPartyID(t)
   385  	maxVolumeParams := num.UintFromUint64(2000)
   386  
   387  	// Cap the notional volume.
   388  	require.NoError(t, te.engine.OnReferralProgramMaxPartyNotionalVolumeByQuantumPerEpochUpdate(ctx, maxVolumeParams))
   389  
   390  	program1 := &types.ReferralProgram{
   391  		EndOfProgramTimestamp: time.Now().Add(24 * time.Hour),
   392  		WindowLength:          2,
   393  		BenefitTiers: []*types.BenefitTier{
   394  			{
   395  				MinimumEpochs:                     num.UintFromUint64(2),
   396  				MinimumRunningNotionalTakerVolume: num.UintFromUint64(1000),
   397  				ReferralRewardFactors: types.Factors{
   398  					Infra:     num.DecimalFromFloat(0.001),
   399  					Maker:     num.DecimalFromFloat(0.001),
   400  					Liquidity: num.DecimalFromFloat(0.001),
   401  				},
   402  				ReferralDiscountFactors: types.Factors{
   403  					Infra:     num.DecimalFromFloat(0.002),
   404  					Maker:     num.DecimalFromFloat(0.002),
   405  					Liquidity: num.DecimalFromFloat(0.002),
   406  				},
   407  			}, {
   408  				MinimumEpochs:                     num.UintFromUint64(3),
   409  				MinimumRunningNotionalTakerVolume: num.UintFromUint64(3000),
   410  				ReferralRewardFactors: types.Factors{
   411  					Infra:     num.DecimalFromFloat(0.01),
   412  					Maker:     num.DecimalFromFloat(0.01),
   413  					Liquidity: num.DecimalFromFloat(0.01),
   414  				},
   415  				ReferralDiscountFactors: types.Factors{
   416  					Infra:     num.DecimalFromFloat(0.02),
   417  					Maker:     num.DecimalFromFloat(0.02),
   418  					Liquidity: num.DecimalFromFloat(0.02),
   419  				},
   420  			}, {
   421  				MinimumEpochs:                     num.UintFromUint64(4),
   422  				MinimumRunningNotionalTakerVolume: num.UintFromUint64(4000),
   423  				ReferralRewardFactors: types.Factors{
   424  					Infra:     num.DecimalFromFloat(0.1),
   425  					Maker:     num.DecimalFromFloat(0.1),
   426  					Liquidity: num.DecimalFromFloat(0.1),
   427  				},
   428  				ReferralDiscountFactors: types.Factors{
   429  					Infra:     num.DecimalFromFloat(0.2),
   430  					Maker:     num.DecimalFromFloat(0.2),
   431  					Liquidity: num.DecimalFromFloat(0.2),
   432  				},
   433  			},
   434  		},
   435  		StakingTiers: []*types.StakingTier{},
   436  	}
   437  
   438  	// Set the first program.
   439  	te.engine.UpdateProgram(program1)
   440  
   441  	// When the epoch starts, the new program should start.
   442  	expectReferralProgramStartedEvent(t, te)
   443  	lastEpochStartTime := program1.EndOfProgramTimestamp.Add(-2 * time.Hour)
   444  	nextEpoch(t, ctx, te, lastEpochStartTime)
   445  
   446  	// Setting up the referral sets.
   447  	te.broker.EXPECT().Send(gomock.Any()).Times(4)
   448  	te.timeSvc.EXPECT().GetTimeNow().Return(time.Now()).Times(4)
   449  
   450  	te.staking.EXPECT().GetAvailableBalance(string(referrer1)).Times(1).Return(num.NewUint(100), nil)
   451  	assert.NoError(t, te.engine.CreateReferralSet(ctx, referrer1, setID1))
   452  
   453  	te.staking.EXPECT().GetAvailableBalance(string(referrer2)).Times(1).Return(num.NewUint(100), nil)
   454  	assert.NoError(t, te.engine.CreateReferralSet(ctx, referrer2, setID2))
   455  
   456  	assert.NoError(t, te.engine.ApplyReferralCode(ctx, referee1, setID1))
   457  	assert.NoError(t, te.engine.ApplyReferralCode(ctx, referee3, setID2))
   458  
   459  	// When the epoch ends, the running volume for set members should be
   460  	// computed.
   461  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer1)).Return(num.UintFromUint64(800)).Times(1)
   462  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer2)).Return(num.UintFromUint64(10000)).Times(1)
   463  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee1)).Return(num.UintFromUint64(100)).Times(1)
   464  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee3)).Return(num.UintFromUint64(0)).Times(1)
   465  	te.staking.EXPECT().GetAvailableBalance(string(referrer1)).Times(1).Return(num.NewUint(100), nil)
   466  	te.staking.EXPECT().GetAvailableBalance(string(referrer2)).Times(1).Return(num.NewUint(100), nil)
   467  
   468  	expectReferralSetStatsUpdatedEvent(t, te, 2)
   469  	lastEpochStartTime = program1.EndOfProgramTimestamp.Add(-1*time.Hour - 50*time.Minute)
   470  	nextEpoch(t, ctx, te, lastEpochStartTime)
   471  
   472  	// Looking for factors for party without a set.
   473  	// => No reward nor discount factor.
   474  	loneWolfParty := newPartyID(t)
   475  	assert.Equal(t, num.DecimalZero().String(), te.engine.RewardsFactorForParty(loneWolfParty).Infra.String())
   476  	assert.Equal(t, num.DecimalZero().String(), te.engine.ReferralDiscountFactorsForParty(loneWolfParty).Infra.String())
   477  
   478  	// Looking for factors for referrer 1.
   479  	// Factors only apply to referees' trades.
   480  	// => No reward nor discount factor.
   481  	assert.Equal(t, num.DecimalZero().String(), te.engine.RewardsFactorForParty(referrer1).Infra.String())
   482  	assert.Equal(t, num.DecimalZero().String(), te.engine.ReferralDiscountFactorsForParty(referrer1).Infra.String())
   483  
   484  	// Looking for factors for referrer 2.
   485  	// Factors only apply to referees' trades.
   486  	// => No reward nor discount factor.
   487  	assert.Equal(t, num.DecimalZero().String(), te.engine.RewardsFactorForParty(referrer2).Infra.String())
   488  	assert.Equal(t, num.DecimalZero().String(), te.engine.ReferralDiscountFactorsForParty(referrer2).Infra.String())
   489  
   490  	// Looking for rewards factor for referee 1.
   491  	// His set has not enough notional volume to reach tier 1.
   492  	// He is not a member for long enough.
   493  	// => No reward nor discount factor.
   494  	assert.Equal(t, num.DecimalZero().String(), te.engine.RewardsFactorForParty(referee1).Infra.String())
   495  	assert.Equal(t, num.DecimalZero().String(), te.engine.ReferralDiscountFactorsForParty(referee1).Infra.String())
   496  
   497  	// Looking for reward factor for referee 3.
   498  	// His set has enough notional volume to reach tier 1.
   499  	// He is not a member for long enough.
   500  	// => Tier 1 reward factor.
   501  	// => No discount factor.
   502  	assert.Equal(t, "0.001", te.engine.RewardsFactorForParty(referee3).Infra.String())
   503  	assert.Equal(t, num.DecimalZero().String(), te.engine.ReferralDiscountFactorsForParty(referee3).Infra.String())
   504  
   505  	// Adding a new referee.
   506  	te.broker.EXPECT().Send(gomock.Any()).Times(1)
   507  	te.timeSvc.EXPECT().GetTimeNow().Return(time.Now()).Times(1)
   508  	assert.NoError(t, te.engine.ApplyReferralCode(ctx, referee2, setID2))
   509  
   510  	// When the epoch ends, the running volume for set members should be
   511  	// computed.
   512  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer1)).Return(num.UintFromUint64(1900)).Times(1)
   513  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee1)).Return(num.UintFromUint64(1000)).Times(1)
   514  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer2)).Return(num.UintFromUint64(1000)).Times(1)
   515  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee3)).Return(num.UintFromUint64(0)).Times(1)
   516  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee2)).Return(num.UintFromUint64(1000)).Times(1)
   517  	te.staking.EXPECT().GetAvailableBalance(string(referrer1)).Times(1).Return(num.NewUint(100), nil)
   518  	te.staking.EXPECT().GetAvailableBalance(string(referrer2)).Times(1).Return(num.NewUint(100), nil)
   519  
   520  	// When the epoch starts, the new program should start.
   521  	expectReferralSetStatsUpdatedEvent(t, te, 2)
   522  	lastEpochStartTime = program1.EndOfProgramTimestamp.Add(-1 * time.Hour)
   523  	nextEpoch(t, ctx, te, lastEpochStartTime)
   524  
   525  	// Looking for rewards factor for referee 1.
   526  	// His set has enough notional volume to reach tier 2.
   527  	// He is a member for long enough to reach tier 1.
   528  	// => Tier 2 reward factor.
   529  	// => Tier 1 discount factor.
   530  	assert.Equal(t, "0.01", te.engine.RewardsFactorForParty(referee1).Infra.String())
   531  	assert.Equal(t, "0.002", te.engine.ReferralDiscountFactorsForParty(referee1).Infra.String())
   532  
   533  	// Looking for reward factor for referee 2.
   534  	// His set has enough notional volume to reach tier 3.
   535  	// He is not a member for long enough.
   536  	// => Tier 3 reward factor.
   537  	// => No discount factor.
   538  	assert.Equal(t, "0.1", te.engine.RewardsFactorForParty(referee2).Infra.String())
   539  	assert.Equal(t, num.DecimalZero().String(), te.engine.ReferralDiscountFactorsForParty(referee2).Infra.String())
   540  
   541  	// Looking for reward factor for referee 3.
   542  	// His set has enough notional volume to reach tier 3.
   543  	// He is a member for long enough to reach tier 1.
   544  	// => Tier 3 reward factor.
   545  	// => Tier 1 discount factor.
   546  	assert.Equal(t, "0.1", te.engine.RewardsFactorForParty(referee3).Infra.String())
   547  	assert.Equal(t, "0.002", te.engine.ReferralDiscountFactorsForParty(referee3).Infra.String())
   548  
   549  	// When the epoch ends, the running volume for set members should be
   550  	// computed.
   551  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer1)).Return(num.UintFromUint64(10)).Times(1)
   552  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee1)).Return(num.UintFromUint64(10)).Times(1)
   553  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer2)).Return(num.UintFromUint64(500)).Times(1)
   554  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee3)).Return(num.UintFromUint64(0)).Times(1)
   555  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee2)).Return(num.UintFromUint64(500)).Times(1)
   556  	te.staking.EXPECT().GetAvailableBalance(string(referrer1)).Times(1).Return(num.NewUint(100), nil)
   557  	te.staking.EXPECT().GetAvailableBalance(string(referrer2)).Times(1).Return(num.NewUint(100), nil)
   558  
   559  	// When the epoch starts, the new program should start.
   560  	expectReferralSetStatsUpdatedEvent(t, te, 2)
   561  	lastEpochStartTime = program1.EndOfProgramTimestamp.Add(-1 * time.Hour)
   562  	nextEpoch(t, ctx, te, lastEpochStartTime)
   563  
   564  	// Because the window length is set to 2, the first notional volumes are now
   565  	// ignored in the running volume computation.
   566  
   567  	// Looking for rewards factor for referee 1.
   568  	// His set has enough notional volume to reach tier 1.
   569  	// He is a member for long enough to reach tier 1.
   570  	// => Tier 1 reward factor.
   571  	// => Tier 1 discount factor.
   572  	assert.Equal(t, "0.001", te.engine.RewardsFactorForParty(referee1).Infra.String())
   573  	assert.Equal(t, "0.002", te.engine.ReferralDiscountFactorsForParty(referee1).Infra.String())
   574  
   575  	// Looking for reward factor for referee 2.
   576  	// His set has enough notional volume to reach tier 3.
   577  	// He is a member for long enough to reach tier 1.
   578  	// => Tier 2 reward factor.
   579  	// => Tier 1 discount factor.
   580  	assert.Equal(t, "0.01", te.engine.RewardsFactorForParty(referee2).Infra.String())
   581  	assert.Equal(t, "0.002", te.engine.ReferralDiscountFactorsForParty(referee2).Infra.String())
   582  
   583  	// Looking for reward factor for referee 3.
   584  	// His set has enough notional volume to reach tier 2.
   585  	// He is a member for long enough to reach tier 2.
   586  	// => Tier 2 reward factor.
   587  	// => Tier 2 discount factor.
   588  	assert.Equal(t, "0.01", te.engine.RewardsFactorForParty(referee3).Infra.String())
   589  	assert.Equal(t, "0.02", te.engine.ReferralDiscountFactorsForParty(referee3).Infra.String())
   590  
   591  	// When the epoch ends, the running volume for set members should be
   592  	// computed.
   593  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer1)).Return(num.UintFromUint64(10000)).Times(1)
   594  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee1)).Return(num.UintFromUint64(10000)).Times(1)
   595  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer2)).Return(num.UintFromUint64(10000)).Times(1)
   596  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee3)).Return(num.UintFromUint64(0)).Times(1)
   597  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee2)).Return(num.UintFromUint64(10000)).Times(1)
   598  	// But, the sets are not eligible anymore.
   599  	te.staking.EXPECT().GetAvailableBalance(string(referrer1)).Times(1).Return(num.NewUint(0), nil)
   600  	te.staking.EXPECT().GetAvailableBalance(string(referrer2)).Times(1).Return(num.NewUint(0), nil)
   601  
   602  	expectReferralSetStatsUpdatedEvent(t, te, 2)
   603  	lastEpochStartTime = program1.EndOfProgramTimestamp.Add(-45 * time.Minute)
   604  	nextEpoch(t, ctx, te, lastEpochStartTime)
   605  
   606  	// The sets are not eligible anymore, no more reward and discount factors.
   607  	assert.Equal(t, num.DecimalZero().String(), te.engine.RewardsFactorForParty(referrer1).Infra.String())
   608  	assert.Equal(t, num.DecimalZero().String(), te.engine.ReferralDiscountFactorsForParty(referee1).Infra.String())
   609  	assert.Equal(t, num.DecimalZero().String(), te.engine.RewardsFactorForParty(referrer2).Infra.String())
   610  	assert.Equal(t, num.DecimalZero().String(), te.engine.ReferralDiscountFactorsForParty(referee3).Infra.String())
   611  	assert.Equal(t, num.DecimalZero().String(), te.engine.ReferralDiscountFactorsForParty(referee2).Infra.String())
   612  
   613  	// When the epoch ends, the running volume for set members should be
   614  	// computed.
   615  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer1)).Return(num.UintFromUint64(10000)).Times(1)
   616  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee1)).Return(num.UintFromUint64(10000)).Times(1)
   617  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer2)).Return(num.UintFromUint64(10000)).Times(1)
   618  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee3)).Return(num.UintFromUint64(0)).Times(1)
   619  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee2)).Return(num.UintFromUint64(10000)).Times(1)
   620  	// And the sets are eligible once again.
   621  	te.staking.EXPECT().GetAvailableBalance(string(referrer1)).Times(1).Return(num.NewUint(100), nil)
   622  	te.staking.EXPECT().GetAvailableBalance(string(referrer2)).Times(1).Return(num.NewUint(100), nil)
   623  
   624  	expectReferralSetStatsUpdatedEvent(t, te, 2)
   625  	lastEpochStartTime = program1.EndOfProgramTimestamp.Add(-30 * time.Minute)
   626  	nextEpoch(t, ctx, te, lastEpochStartTime)
   627  
   628  	// Looking for rewards factor for referee 1.
   629  	// His set has enough notional volume to reach tier 3.
   630  	// He is a member for long enough to reach tier 3.
   631  	// => Tier 3 reward factor.
   632  	// => Tier 3 discount factor.
   633  	assert.Equal(t, "0.1", te.engine.RewardsFactorForParty(referee1).Infra.String())
   634  	assert.Equal(t, "0.2", te.engine.ReferralDiscountFactorsForParty(referee1).Infra.String())
   635  
   636  	// Looking for reward factor for referee 2.
   637  	// His set has enough notional volume to reach tier 3.
   638  	// He is a member for long enough to reach tier 3.
   639  	// => Tier 3 reward factor.
   640  	// => Tier 3 discount factor.
   641  	assert.Equal(t, "0.1", te.engine.RewardsFactorForParty(referee2).Infra.String())
   642  	assert.Equal(t, "0.2", te.engine.ReferralDiscountFactorsForParty(referee2).Infra.String())
   643  
   644  	// Looking for reward factor for referee 3.
   645  	// His set has enough notional volume to reach tier 3.
   646  	// He is a member for long enough to reach tier 3.
   647  	// => Tier 3 reward factor.
   648  	// => Tier 3 discount factor.
   649  	assert.Equal(t, "0.1", te.engine.RewardsFactorForParty(referee3).Infra.String())
   650  	assert.Equal(t, "0.2", te.engine.ReferralDiscountFactorsForParty(referee3).Infra.String())
   651  
   652  	// When the epoch ends, the running volume for set members should be
   653  	// computed.
   654  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer1)).Return(num.UintFromUint64(10000)).Times(1)
   655  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee1)).Return(num.UintFromUint64(10000)).Times(1)
   656  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referrer2)).Return(num.UintFromUint64(10000)).Times(1)
   657  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee3)).Return(num.UintFromUint64(0)).Times(1)
   658  	te.marketActivityTracker.EXPECT().NotionalTakerVolumeForParty(string(referee2)).Return(num.UintFromUint64(10000)).Times(1)
   659  	te.staking.EXPECT().GetAvailableBalance(string(referrer1)).Times(1).Return(num.NewUint(100), nil)
   660  	te.staking.EXPECT().GetAvailableBalance(string(referrer2)).Times(1).Return(num.NewUint(100), nil)
   661  
   662  	// When the epoch starts, the current program should end.
   663  	gomock.InOrder(
   664  		expectReferralSetStatsUpdatedEvent(t, te, 2),
   665  		expectReferralProgramEndedEvent(t, te),
   666  	)
   667  	lastEpochStartTime = program1.EndOfProgramTimestamp.Add(1 * time.Hour)
   668  	nextEpoch(t, ctx, te, lastEpochStartTime)
   669  
   670  	// Program has ended, no more reward and discount factors.
   671  	assert.Equal(t, num.DecimalZero().String(), te.engine.RewardsFactorForParty(referrer1).Infra.String())
   672  	assert.Equal(t, num.DecimalZero().String(), te.engine.ReferralDiscountFactorsForParty(referee1).Infra.String())
   673  	assert.Equal(t, num.DecimalZero().String(), te.engine.RewardsFactorForParty(referrer2).Infra.String())
   674  	assert.Equal(t, num.DecimalZero().String(), te.engine.ReferralDiscountFactorsForParty(referee3).Infra.String())
   675  	assert.Equal(t, num.DecimalZero().String(), te.engine.ReferralDiscountFactorsForParty(referee2).Infra.String())
   676  }