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 }