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 }