diff --git a/CHANGELOG.md b/CHANGELOG.md
index a98b02a00a..f9ca1de97c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,7 +16,7 @@
### 🐛 Fixes
-- [](https://github.com/vegaprotocol/vega/issues/xxx)
+- [11066](https://github.com/vegaprotocol/vega/issues/11066) - Ensure vesting statistics match vesting accounts numbers.
## 0.76.1
diff --git a/core/collateral/engine.go b/core/collateral/engine.go
index e69042d4d8..f7b788dd32 100644
--- a/core/collateral/engine.go
+++ b/core/collateral/engine.go
@@ -219,8 +219,8 @@ func (e *Engine) GetPartyBalance(party string) *num.Uint {
return num.UintZero()
}
-func (e *Engine) GetAllVestingQuantumBalance(party string) *num.Uint {
- balance := num.UintZero()
+func (e *Engine) GetAllVestingQuantumBalance(party string) num.Decimal {
+ balance := num.DecimalZero()
for asset, details := range e.enabledAssets {
// vesting balance
@@ -229,14 +229,14 @@ func (e *Engine) GetAllVestingQuantumBalance(party string) *num.Uint {
quantum = details.Details.Quantum
}
if acc, ok := e.accs[e.accountID(noMarket, party, asset, types.AccountTypeVestingRewards)]; ok {
- quantumBalance, _ := num.UintFromDecimal(acc.Balance.ToDecimal().Div(quantum))
- balance.AddSum(quantumBalance)
+ quantumBalance := acc.Balance.ToDecimal().Div(quantum)
+ balance = balance.Add(quantumBalance)
}
// vested balance
if acc, ok := e.accs[e.accountID(noMarket, party, asset, types.AccountTypeVestedRewards)]; ok {
- quantumBalance, _ := num.UintFromDecimal(acc.Balance.ToDecimal().Div(quantum))
- balance.AddSum(quantumBalance)
+ quantumBalance := acc.Balance.ToDecimal().Div(quantum)
+ balance = balance.Add(quantumBalance)
}
}
@@ -863,9 +863,7 @@ func (e *Engine) TransferRewards(ctx context.Context, rewardAccountID string, tr
return responses, nil
}
-func (e *Engine) TransferVestedRewards(
- ctx context.Context, transfers []*types.Transfer,
-) ([]*types.LedgerMovement, error) {
+func (e *Engine) TransferVestedRewards(ctx context.Context, transfers []*types.Transfer) ([]*types.LedgerMovement, error) {
if len(transfers) == 0 {
return nil, nil
}
@@ -3640,10 +3638,10 @@ func (e *Engine) CreatePartyGeneralAccount(ctx context.Context, partyID, asset s
return generalID, nil
}
-// GetOrCreatePartyVestingAccount create the general account for a party.
+// GetOrCreatePartyVestingRewardAccount create the general account for a party.
func (e *Engine) GetOrCreatePartyVestingRewardAccount(ctx context.Context, partyID, asset string) *types.Account {
if !e.AssetExists(asset) {
- e.log.Panic("trying to use a nonexisting asset for reward accounts, something went very wrong somewhere",
+ e.log.Panic("trying to use a non-existent asset for reward accounts, something went very wrong somewhere",
logging.String("asset-id", asset))
}
@@ -3673,7 +3671,7 @@ func (e *Engine) GetPartyVestedRewardAccount(partyID, asset string) (*types.Acco
return e.GetAccountByID(vested)
}
-// GetOrCreatePartyVestedAccount create the general account for a party.
+// GetOrCreatePartyVestedRewardAccount create the general account for a party.
func (e *Engine) GetOrCreatePartyVestedRewardAccount(ctx context.Context, partyID, asset string) *types.Account {
if !e.AssetExists(asset) {
e.log.Panic("trying to use a nonexisting asset for reward accounts, something went very wrong somewhere",
diff --git a/core/collateral/engine_test.go b/core/collateral/engine_test.go
index 6878a6f351..d418e8a015 100644
--- a/core/collateral/engine_test.go
+++ b/core/collateral/engine_test.go
@@ -149,7 +149,7 @@ func TestGetAllVestingQuantumBalance(t *testing.T) {
party := "party1"
balance := eng.GetAllVestingQuantumBalance(party)
- assert.Equal(t, int(balance.Uint64()), 0)
+ assert.Equal(t, balance.String(), "0")
assetT := types.Asset{
ID: "USDC",
@@ -179,7 +179,7 @@ func TestGetAllVestingQuantumBalance(t *testing.T) {
)
balance = eng.GetAllVestingQuantumBalance(party)
- assert.Equal(t, int(balance.Uint64()), 100)
+ assert.Equal(t, balance.String(), "100")
// add some more of the other account
// now add some balance to an asset
@@ -190,7 +190,7 @@ func TestGetAllVestingQuantumBalance(t *testing.T) {
)
balance = eng.GetAllVestingQuantumBalance(party)
- assert.Equal(t, int(balance.Uint64()), 103)
+ assert.Equal(t, balance.String(), "103.1666666666666667")
}
func testClearFeeAccounts(t *testing.T) {
diff --git a/core/rewards/engine.go b/core/rewards/engine.go
index 2d06ea1c28..7950d4b9d7 100644
--- a/core/rewards/engine.go
+++ b/core/rewards/engine.go
@@ -89,7 +89,7 @@ type Teams interface {
type Vesting interface {
AddReward(party, asset string, amount *num.Uint, lockedForEpochs uint64)
- GetRewardBonusMultiplier(party string) (*num.Uint, num.Decimal)
+ GetRewardBonusMultiplier(party string) (num.Decimal, num.Decimal)
}
type ActivityStreak interface {
diff --git a/core/rewards/mocks/mocks.go b/core/rewards/mocks/mocks.go
index 171e9bd3e4..0ffd8bf8f4 100644
--- a/core/rewards/mocks/mocks.go
+++ b/core/rewards/mocks/mocks.go
@@ -374,10 +374,10 @@ func (mr *MockVestingMockRecorder) AddReward(arg0, arg1, arg2, arg3 interface{})
}
// GetRewardBonusMultiplier mocks base method.
-func (m *MockVesting) GetRewardBonusMultiplier(arg0 string) (*num.Uint, decimal.Decimal) {
+func (m *MockVesting) GetRewardBonusMultiplier(arg0 string) (decimal.Decimal, decimal.Decimal) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetRewardBonusMultiplier", arg0)
- ret0, _ := ret[0].(*num.Uint)
+ ret0, _ := ret[0].(decimal.Decimal)
ret1, _ := ret[1].(decimal.Decimal)
return ret0, ret1
}
diff --git a/core/vesting/vesting.go b/core/vesting/engine.go
similarity index 87%
rename from core/vesting/vesting.go
rename to core/vesting/engine.go
index 333c92e951..81e7852976 100644
--- a/core/vesting/vesting.go
+++ b/core/vesting/engine.go
@@ -32,15 +32,12 @@ import (
"golang.org/x/exp/slices"
)
-//go:generate go run github.com/golang/mock/mockgen -destination mocks/mocks.go -package mocks code.vegaprotocol.io/vega/core/vesting Collateral,ActivityStreakVestingMultiplier,Broker,Assets
+//go:generate go run github.com/golang/mock/mockgen -destination mocks/mocks.go -package mocks code.vegaprotocol.io/vega/core/vesting ActivityStreakVestingMultiplier,Assets
type Collateral interface {
- TransferVestedRewards(
- ctx context.Context, transfers []*types.Transfer,
- ) ([]*types.LedgerMovement, error)
+ TransferVestedRewards(ctx context.Context, transfers []*types.Transfer) ([]*types.LedgerMovement, error)
GetVestingRecovery() map[string]map[string]*num.Uint
- GetAllVestingQuantumBalance(party string) *num.Uint
- GetVestingAccounts() []*types.Account
+ GetAllVestingQuantumBalance(party string) num.Decimal
}
type ActivityStreakVestingMultiplier interface {
@@ -111,9 +108,7 @@ func (e *Engine) OnCheckpointLoaded() {
}
}
-func (e *Engine) OnBenefitTiersUpdate(
- _ context.Context, v interface{},
-) error {
+func (e *Engine) OnBenefitTiersUpdate(_ context.Context, v interface{}) error {
tiers, err := types.VestingBenefitTiersFromUntypedProto(v)
if err != nil {
return err
@@ -126,31 +121,27 @@ func (e *Engine) OnBenefitTiersUpdate(
return nil
}
-func (e *Engine) OnRewardVestingBaseRateUpdate(
- _ context.Context, baseRate num.Decimal,
-) error {
+func (e *Engine) OnRewardVestingBaseRateUpdate(_ context.Context, baseRate num.Decimal) error {
e.baseRate = baseRate
return nil
}
-func (e *Engine) OnRewardVestingMinimumTransferUpdate(
- _ context.Context, minimumTransfer num.Decimal,
-) error {
+func (e *Engine) OnRewardVestingMinimumTransferUpdate(_ context.Context, minimumTransfer num.Decimal) error {
e.minTransfer = minimumTransfer
return nil
}
func (e *Engine) OnEpochEvent(ctx context.Context, epoch types.Epoch) {
if epoch.Action == proto.EpochAction_EPOCH_ACTION_END {
- e.broadcastRewardBonusMultipliers(ctx, epoch.Seq)
e.moveLocked()
e.distributeVested(ctx)
- e.clearup()
+ e.broadcastVestingStatsUpdate(ctx, epoch.Seq)
e.broadcastSummary(ctx, epoch.Seq)
+ e.clearState()
}
}
-func (e *Engine) OnEpochRestore(ctx context.Context, epoch types.Epoch) {
+func (e *Engine) OnEpochRestore(_ context.Context, epoch types.Epoch) {
e.epochSeq = epoch.Seq
}
@@ -161,24 +152,20 @@ func (e *Engine) AddReward(
) {
// no locktime, just increase the amount in vesting
if lockedForEpochs == 0 {
- e.increaseVestingBalance(
- party, asset, amount,
- )
+ e.increaseVestingBalance(party, asset, amount)
return
}
- e.increaseLockedForAsset(
- party, asset, amount, lockedForEpochs,
- )
+ e.increaseLockedForAsset(party, asset, amount, lockedForEpochs)
}
-func (e *Engine) GetRewardBonusMultiplier(party string) (*num.Uint, num.Decimal) {
+func (e *Engine) GetRewardBonusMultiplier(party string) (num.Decimal, num.Decimal) {
quantumBalance := e.c.GetAllVestingQuantumBalance(party)
multiplier := num.DecimalOne()
for _, b := range e.benefitTiers {
- if quantumBalance.LT(b.MinimumQuantumBalance) {
+ if quantumBalance.LessThan(num.DecimalFromUint(b.MinimumQuantumBalance)) {
break
}
@@ -234,7 +221,7 @@ func (e *Engine) increaseVestingBalance(
partyRewards.Vesting[asset] = vesting
}
-// checkLocked will move around locked funds.
+// moveLocked will move around locked funds.
// if the lock for epoch reach 0, the full amount
// is added to the vesting amount for the asset.
func (e *Engine) moveLocked() {
@@ -272,9 +259,7 @@ func (e *Engine) distributeVested(ctx context.Context) {
sort.Strings(assets)
for _, asset := range assets {
balance := rewards.Vesting[asset]
- transfer := e.makeTransfer(
- party, asset, balance.Clone(),
- )
+ transfer := e.makeTransfer(party, asset, balance.Clone())
// we are clearing the account,
// we can delete it.
@@ -289,7 +274,7 @@ func (e *Engine) distributeVested(ctx context.Context) {
}
// nothing to be done
- if len(transfers) <= 0 {
+ if len(transfers) == 0 {
return
}
@@ -343,10 +328,9 @@ func (e *Engine) makeTransfer(
return transfer
}
-// just remove party entries once they are not needed anymore.
-func (e *Engine) clearup() {
+func (e *Engine) clearState() {
for party, v := range e.state {
- if len(v.Locked) <= 0 && len(v.Vesting) <= 0 {
+ if len(v.Locked) == 0 && len(v.Vesting) == 0 {
delete(e.state, party)
}
}
@@ -358,13 +342,11 @@ func (e *Engine) broadcastSummary(ctx context.Context, seq uint64) {
PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
}
- parties := make([]string, 0, len(e.state))
- for k := range e.state {
- parties = append(parties, k)
- }
- sort.Strings(parties)
-
for p, pRewards := range e.state {
+ if len(pRewards.Vesting) == 0 && len(pRewards.Locked) == 0 {
+ continue
+ }
+
pSummary := &eventspb.PartyVestingSummary{
Party: p,
PartyLockedBalances: []*eventspb.PartyLockedBalance{},
@@ -416,7 +398,7 @@ func (e *Engine) broadcastSummary(ctx context.Context, seq uint64) {
e.broker.Send(events.NewVestingBalancesSummaryEvent(ctx, evt))
}
-func (e *Engine) broadcastRewardBonusMultipliers(ctx context.Context, seq uint64) {
+func (e *Engine) broadcastVestingStatsUpdate(ctx context.Context, seq uint64) {
evt := &eventspb.VestingStatsUpdated{
AtEpoch: seq,
Stats: make([]*eventspb.PartyVestingStats, 0, len(e.state)),
@@ -427,6 +409,8 @@ func (e *Engine) broadcastRewardBonusMultipliers(ctx context.Context, seq uint64
for _, party := range parties {
quantumBalance, multiplier := e.GetRewardBonusMultiplier(party)
+ // To avoid excessively large decimals.
+ quantumBalance.Round(2)
evt.Stats = append(evt.Stats, &eventspb.PartyVestingStats{
PartyId: party,
RewardBonusMultiplier: multiplier.String(),
diff --git a/core/vesting/engine_test.go b/core/vesting/engine_test.go
new file mode 100644
index 0000000000..fcaeb17e10
--- /dev/null
+++ b/core/vesting/engine_test.go
@@ -0,0 +1,950 @@
+// Copyright (C) 2023 Gobalsky Labs Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package vesting_test
+
+import (
+ "context"
+ "testing"
+
+ "code.vegaprotocol.io/vega/core/assets"
+ "code.vegaprotocol.io/vega/core/events"
+ "code.vegaprotocol.io/vega/core/types"
+ "code.vegaprotocol.io/vega/libs/num"
+ vegapb "code.vegaprotocol.io/vega/protos/vega"
+ eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
+
+ "github.com/golang/mock/gomock"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestDistributeAfterDelay(t *testing.T) {
+ v := getTestEngine(t)
+
+ ctx := context.Background()
+
+ // distribute 90% as the base rate,
+ // so first we distribute some, then we get under the minimum value, and all the rest
+ // is distributed
+ require.NoError(t, v.OnRewardVestingBaseRateUpdate(ctx, num.MustDecimalFromString("0.9")))
+ // this is multiplied by the quantum, so it will make it 100% of the quantum
+ require.NoError(t, v.OnRewardVestingMinimumTransferUpdate(ctx, num.MustDecimalFromString("1")))
+
+ require.NoError(t, v.OnBenefitTiersUpdate(ctx, &vegapb.VestingBenefitTiers{
+ Tiers: []*vegapb.VestingBenefitTier{
+ {
+ MinimumQuantumBalance: "200",
+ RewardMultiplier: "1",
+ },
+ {
+ MinimumQuantumBalance: "350",
+ RewardMultiplier: "2",
+ },
+ {
+ MinimumQuantumBalance: "500",
+ RewardMultiplier: "3",
+ },
+ },
+ }))
+
+ v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1"))
+ v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
+
+ party := "party1"
+ vegaAsset := "VEGA"
+
+ v.col.InitVestedBalance(party, vegaAsset, num.NewUint(300))
+
+ epochSeq := uint64(1)
+
+ t.Run("No vesting stats and summary when no reward is being vested", func(t *testing.T) {
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ Seq: epochSeq,
+ })
+ })
+
+ t.Run("Add a reward locked for 3 epochs", func(t *testing.T) {
+ v.AddReward(party, vegaAsset, num.NewUint(100), 3)
+ })
+
+ t.Run("Wait for 3 epochs", func(t *testing.T) {
+ for i := 0; i < 3; i++ {
+ epochSeq += 1
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{
+ {
+ PartyId: party,
+ RewardBonusMultiplier: "1",
+ QuantumBalance: "300",
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{
+ {
+ Party: party,
+ PartyLockedBalances: []*eventspb.PartyLockedBalance{
+ {
+ Asset: vegaAsset,
+ UntilEpoch: 5,
+ Balance: "100",
+ },
+ },
+ PartyVestingBalances: []*eventspb.PartyVestingBalance{},
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ Seq: epochSeq,
+ })
+ }
+ })
+
+ t.Run("First reward payment", func(t *testing.T) {
+ epochSeq += 1
+
+ expectLedgerMovements(t, v)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{
+ {
+ PartyId: party,
+ RewardBonusMultiplier: "2",
+ QuantumBalance: "390",
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{
+ {
+ Party: party,
+ PartyLockedBalances: []*eventspb.PartyLockedBalance{},
+ PartyVestingBalances: []*eventspb.PartyVestingBalance{
+ {
+ Asset: vegaAsset,
+ Balance: "10",
+ },
+ },
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+
+ t.Run("Second reward payment", func(t *testing.T) {
+ epochSeq += 1
+
+ expectLedgerMovements(t, v)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{
+ {
+ PartyId: party,
+ RewardBonusMultiplier: "2",
+ QuantumBalance: "400",
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+
+ t.Run("No vesting stats and summary when no reward is being vested anymore", func(t *testing.T) {
+ epochSeq += 1
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+}
+
+func TestDistributeWithNoDelay(t *testing.T) {
+ v := getTestEngine(t)
+
+ ctx := context.Background()
+
+ // distribute 90% as the base rate,
+ // so first we distribute some, then we get under the minimum value, and all the rest
+ // is distributed
+ require.NoError(t, v.OnRewardVestingBaseRateUpdate(ctx, num.MustDecimalFromString("0.9")))
+ // this is multiplied by the quantum, so it will make it 100% of the quantum
+ require.NoError(t, v.OnRewardVestingMinimumTransferUpdate(ctx, num.MustDecimalFromString("1")))
+
+ require.NoError(t, v.OnBenefitTiersUpdate(ctx, &vegapb.VestingBenefitTiers{
+ Tiers: []*vegapb.VestingBenefitTier{
+ {
+ MinimumQuantumBalance: "200",
+ RewardMultiplier: "1",
+ },
+ {
+ MinimumQuantumBalance: "350",
+ RewardMultiplier: "2",
+ },
+ {
+ MinimumQuantumBalance: "500",
+ RewardMultiplier: "3",
+ },
+ },
+ }))
+
+ // set the asvm to return always 1
+ v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1"))
+
+ // set asset to return proper quantum
+ v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
+
+ party := "party1"
+ vegaAsset := "VEGA"
+
+ v.col.InitVestedBalance(party, vegaAsset, num.NewUint(300))
+
+ epochSeq := uint64(1)
+
+ t.Run("No vesting stats and summary when no reward is being vested", func(t *testing.T) {
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ Seq: epochSeq,
+ })
+ })
+
+ t.Run("Add a reward without epoch lock", func(t *testing.T) {
+ v.AddReward(party, vegaAsset, num.NewUint(100), 0)
+ })
+
+ t.Run("First reward payment", func(t *testing.T) {
+ epochSeq += 1
+
+ expectLedgerMovements(t, v)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{
+ {
+ PartyId: party,
+ RewardBonusMultiplier: "2",
+ QuantumBalance: "390",
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{
+ {
+ Party: party,
+ PartyLockedBalances: []*eventspb.PartyLockedBalance{},
+ PartyVestingBalances: []*eventspb.PartyVestingBalance{
+ {
+ Asset: vegaAsset,
+ Balance: "10",
+ },
+ },
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+
+ t.Run("Second reward payment", func(t *testing.T) {
+ epochSeq += 1
+
+ expectLedgerMovements(t, v)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{
+ {
+ PartyId: party,
+ RewardBonusMultiplier: "2",
+ QuantumBalance: "400",
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+
+ t.Run("No vesting stats and summary when no reward is being vested anymore", func(t *testing.T) {
+ epochSeq += 1
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+}
+
+func TestDistributeWithStreakRate(t *testing.T) {
+ v := getTestEngine(t)
+
+ ctx := context.Background()
+
+ // distribute 90% as the base rate,
+ // so first we distribute some, then we get under the minimum value, and all the rest
+ // is distributed
+ require.NoError(t, v.OnRewardVestingBaseRateUpdate(ctx, num.MustDecimalFromString("0.9")))
+ // this is multiplied by the quantum, so it will make it 100% of the quantum
+ require.NoError(t, v.OnRewardVestingMinimumTransferUpdate(ctx, num.MustDecimalFromString("1")))
+
+ require.NoError(t, v.OnBenefitTiersUpdate(ctx, &vegapb.VestingBenefitTiers{
+ Tiers: []*vegapb.VestingBenefitTier{
+ {
+ MinimumQuantumBalance: "200",
+ RewardMultiplier: "1",
+ },
+ {
+ MinimumQuantumBalance: "350",
+ RewardMultiplier: "2",
+ },
+ {
+ MinimumQuantumBalance: "500",
+ RewardMultiplier: "3",
+ },
+ },
+ }))
+
+ v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1.1"))
+ v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
+
+ party := "party1"
+ vegaAsset := "VEGA"
+
+ v.col.InitVestedBalance(party, vegaAsset, num.NewUint(300))
+
+ epochSeq := uint64(1)
+
+ t.Run("No vesting stats and summary when no reward is being vested", func(t *testing.T) {
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ Seq: epochSeq,
+ })
+ })
+
+ t.Run("Add a reward without epoch lock", func(t *testing.T) {
+ v.AddReward(party, vegaAsset, num.NewUint(100), 0)
+ })
+
+ t.Run("First reward payment", func(t *testing.T) {
+ epochSeq += 1
+
+ expectLedgerMovements(t, v)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{
+ {
+ PartyId: party,
+ RewardBonusMultiplier: "2",
+ QuantumBalance: "399",
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{
+ {
+ Party: party,
+ PartyLockedBalances: []*eventspb.PartyLockedBalance{},
+ PartyVestingBalances: []*eventspb.PartyVestingBalance{
+ {
+ Asset: vegaAsset,
+ Balance: "1",
+ },
+ },
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+
+ t.Run("Second reward payment", func(t *testing.T) {
+ epochSeq += 1
+
+ expectLedgerMovements(t, v)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{
+ {
+ PartyId: party,
+ RewardBonusMultiplier: "2",
+ QuantumBalance: "400",
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+
+ t.Run("No vesting stats and summary when no reward is being vested anymore", func(t *testing.T) {
+ epochSeq += 1
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+}
+
+func TestDistributeMultipleAfterDelay(t *testing.T) {
+ v := getTestEngine(t)
+
+ ctx := context.Background()
+
+ // distribute 90% as the base rate,
+ // so first we distribute some, then we get under the minimum value, and all the rest
+ // is distributed
+ require.NoError(t, v.OnRewardVestingBaseRateUpdate(ctx, num.MustDecimalFromString("0.9")))
+ // this is multiplied by the quantum, so it will make it 100% of the quantum
+ require.NoError(t, v.OnRewardVestingMinimumTransferUpdate(ctx, num.MustDecimalFromString("1")))
+
+ require.NoError(t, v.OnBenefitTiersUpdate(ctx, &vegapb.VestingBenefitTiers{
+ Tiers: []*vegapb.VestingBenefitTier{
+ {
+ MinimumQuantumBalance: "200",
+ RewardMultiplier: "1",
+ },
+ {
+ MinimumQuantumBalance: "350",
+ RewardMultiplier: "2",
+ },
+ {
+ MinimumQuantumBalance: "500",
+ RewardMultiplier: "3",
+ },
+ },
+ }))
+
+ v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1"))
+ v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
+
+ party := "party1"
+ vegaAsset := "VEGA"
+
+ v.col.InitVestedBalance(party, vegaAsset, num.NewUint(300))
+
+ epochSeq := uint64(1)
+
+ t.Run("No vesting stats and summary when no reward is being vested", func(t *testing.T) {
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ Seq: epochSeq,
+ })
+ })
+
+ t.Run("Add a reward locked for 2 epochs", func(t *testing.T) {
+ v.AddReward(party, vegaAsset, num.NewUint(100), 2)
+ })
+
+ t.Run("Add another reward locked for 1 epoch", func(t *testing.T) {
+ v.AddReward(party, vegaAsset, num.NewUint(100), 1)
+ })
+
+ t.Run("Wait for 1 epoch", func(t *testing.T) {
+ epochSeq += 1
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{
+ {
+ PartyId: party,
+ RewardBonusMultiplier: "1",
+ QuantumBalance: "300",
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{
+ {
+ Party: party,
+ PartyLockedBalances: []*eventspb.PartyLockedBalance{
+ {
+ Asset: vegaAsset,
+ UntilEpoch: 3,
+ Balance: "100",
+ },
+ {
+ Asset: vegaAsset,
+ UntilEpoch: 4,
+ Balance: "100",
+ },
+ },
+ PartyVestingBalances: []*eventspb.PartyVestingBalance{},
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ Seq: epochSeq,
+ })
+ })
+
+ t.Run("First reward payment", func(t *testing.T) {
+ epochSeq += 1
+
+ expectLedgerMovements(t, v)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{
+ {
+ PartyId: party,
+ RewardBonusMultiplier: "2",
+ QuantumBalance: "390",
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{
+ {
+ Party: party,
+ PartyLockedBalances: []*eventspb.PartyLockedBalance{
+ {
+ Asset: vegaAsset,
+ UntilEpoch: 4,
+ Balance: "100",
+ },
+ },
+ PartyVestingBalances: []*eventspb.PartyVestingBalance{
+ {
+ Asset: vegaAsset,
+ Balance: "10",
+ },
+ },
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+
+ t.Run("Second reward payment", func(t *testing.T) {
+ epochSeq += 1
+
+ expectLedgerMovements(t, v)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{
+ {
+ PartyId: party,
+ RewardBonusMultiplier: "2",
+ QuantumBalance: "489",
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{
+ {
+ Party: party,
+ PartyLockedBalances: []*eventspb.PartyLockedBalance{},
+ PartyVestingBalances: []*eventspb.PartyVestingBalance{
+ {
+ Asset: vegaAsset,
+ Balance: "11",
+ },
+ },
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+
+ t.Run("Third reward payment", func(t *testing.T) {
+ epochSeq += 1
+
+ expectLedgerMovements(t, v)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{
+ {
+ PartyId: party,
+ RewardBonusMultiplier: "2",
+ QuantumBalance: "499",
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{
+ {
+ Party: party,
+ PartyLockedBalances: []*eventspb.PartyLockedBalance{},
+ PartyVestingBalances: []*eventspb.PartyVestingBalance{
+ {
+ Asset: vegaAsset,
+ Balance: "1",
+ },
+ },
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+
+ t.Run("Fourth reward payment", func(t *testing.T) {
+ epochSeq += 1
+
+ expectLedgerMovements(t, v)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{
+ {
+ PartyId: party,
+ RewardBonusMultiplier: "3",
+ QuantumBalance: "500",
+ },
+ },
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+
+ t.Run("No vesting stats and summary when no reward is being vested anymore", func(t *testing.T) {
+ epochSeq += 1
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingStatsUpdated)
+ require.True(t, ok, "Event should be a VestingStatsUpdated, but is %T", evt)
+ assert.Equal(t, eventspb.VestingStatsUpdated{
+ AtEpoch: epochSeq,
+ Stats: []*eventspb.PartyVestingStats{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.VestingBalancesSummary)
+ require.True(t, ok, "Event should be a VestingBalancesSummary, but is %T", evt)
+ assert.Equal(t, eventspb.VestingBalancesSummary{
+ EpochSeq: epochSeq,
+ PartiesVestingSummary: []*eventspb.PartyVestingSummary{},
+ }, e.Proto())
+ }).Times(1)
+
+ v.OnEpochEvent(ctx, types.Epoch{
+ Seq: epochSeq,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ })
+ })
+}
+
+// LedgerMovements is the result of a mock, so it doesn't really make sense to
+// verify data consistency.
+func expectLedgerMovements(t *testing.T, v *testEngine) {
+ t.Helper()
+
+ v.broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
+ e, ok := evt.(*events.LedgerMovements)
+ require.True(t, ok, "Event should be a LedgerMovements, but is %T", evt)
+ assert.Equal(t, eventspb.LedgerMovements{LedgerMovements: []*vegapb.LedgerMovement{}}, e.Proto())
+ }).Times(1)
+}
diff --git a/core/vesting/helper_for_test.go b/core/vesting/helper_for_test.go
new file mode 100644
index 0000000000..485ed78454
--- /dev/null
+++ b/core/vesting/helper_for_test.go
@@ -0,0 +1,216 @@
+// Copyright (C) 2023 Gobalsky Labs Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package vesting_test
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "code.vegaprotocol.io/vega/core/assets/common"
+ bmocks "code.vegaprotocol.io/vega/core/broker/mocks"
+ "code.vegaprotocol.io/vega/core/integration/stubs"
+ "code.vegaprotocol.io/vega/core/snapshot"
+ "code.vegaprotocol.io/vega/core/stats"
+ "code.vegaprotocol.io/vega/core/types"
+ "code.vegaprotocol.io/vega/core/vesting"
+ "code.vegaprotocol.io/vega/core/vesting/mocks"
+ "code.vegaprotocol.io/vega/libs/num"
+ "code.vegaprotocol.io/vega/logging"
+ "code.vegaprotocol.io/vega/paths"
+ vegapb "code.vegaprotocol.io/vega/protos/vega"
+
+ "github.com/golang/mock/gomock"
+ "github.com/stretchr/testify/require"
+)
+
+type testEngine struct {
+ *vesting.Engine
+
+ ctrl *gomock.Controller
+ col *collateralMock
+ asvm *mocks.MockActivityStreakVestingMultiplier
+ broker *bmocks.MockBroker
+ assets *mocks.MockAssets
+}
+
+func getTestEngine(t *testing.T) *testEngine {
+ t.Helper()
+ ctrl := gomock.NewController(t)
+ logger := logging.NewTestLogger()
+ col := newCollateralMock(t)
+ broker := bmocks.NewMockBroker(ctrl)
+ asvm := mocks.NewMockActivityStreakVestingMultiplier(ctrl)
+ assets := mocks.NewMockAssets(ctrl)
+
+ return &testEngine{
+ Engine: vesting.New(
+ logger, col, asvm, broker, assets,
+ ),
+ ctrl: ctrl,
+ broker: broker,
+ col: col,
+ asvm: asvm,
+ assets: assets,
+ }
+}
+
+type testSnapshotEngine struct {
+ engine *vesting.SnapshotEngine
+
+ ctrl *gomock.Controller
+ col *collateralMock
+ asvm *mocks.MockActivityStreakVestingMultiplier
+ broker *bmocks.MockBroker
+ assets *mocks.MockAssets
+
+ currentEpoch uint64
+}
+
+func newEngine(t *testing.T) *testSnapshotEngine {
+ t.Helper()
+ ctrl := gomock.NewController(t)
+ col := newCollateralMock(t)
+ asvm := mocks.NewMockActivityStreakVestingMultiplier(ctrl)
+ broker := bmocks.NewMockBroker(ctrl)
+ assets := mocks.NewMockAssets(ctrl)
+
+ return &testSnapshotEngine{
+ engine: vesting.NewSnapshotEngine(
+ logging.NewTestLogger(), col, asvm, broker, assets,
+ ),
+ ctrl: ctrl,
+ col: col,
+ asvm: asvm,
+ broker: broker,
+ assets: assets,
+ currentEpoch: 10,
+ }
+}
+
+type collateralMock struct {
+ vestedAccountAmount map[string]map[string]*num.Uint
+}
+
+func (c *collateralMock) InitVestedBalance(party, asset string, balance *num.Uint) {
+ c.vestedAccountAmount[party] = map[string]*num.Uint{
+ asset: balance,
+ }
+}
+
+func (c *collateralMock) TransferVestedRewards(_ context.Context, transfers []*types.Transfer) ([]*types.LedgerMovement, error) {
+ for _, transfer := range transfers {
+ vestedAccount, ok := c.vestedAccountAmount[transfer.Owner]
+ if !ok {
+ vestedAccount = map[string]*num.Uint{}
+ c.vestedAccountAmount[transfer.Owner] = map[string]*num.Uint{}
+ }
+
+ amount, ok := vestedAccount[transfer.Amount.Asset]
+ if !ok {
+ amount = num.UintZero()
+ vestedAccount[transfer.Amount.Asset] = amount
+ }
+
+ amount.AddSum(transfer.Amount.Amount)
+ }
+ return []*types.LedgerMovement{}, nil
+}
+
+func (c *collateralMock) GetVestingRecovery() map[string]map[string]*num.Uint {
+ // Only used for checkpoint.
+ return nil
+}
+
+// GetAllVestingQuantumBalance is a custom implementation used to ensure
+// the vesting engine account for benefit tiers during computation.
+// Using this implementation saves us from mocking this at every call to
+// `OnEpochEvent()` with consistent results.
+func (c *collateralMock) GetAllVestingQuantumBalance(party string) num.Decimal {
+ vestedAccount, ok := c.vestedAccountAmount[party]
+ if !ok {
+ return num.DecimalZero()
+ }
+
+ balance := num.DecimalZero()
+ for _, n := range vestedAccount {
+ balance = balance.Add(num.DecimalFromUint(n))
+ }
+
+ return balance
+}
+
+func newCollateralMock(t *testing.T) *collateralMock {
+ t.Helper()
+
+ return &collateralMock{
+ vestedAccountAmount: make(map[string]map[string]*num.Uint),
+ }
+}
+
+type dummyAsset struct {
+ quantum uint64
+}
+
+func (d dummyAsset) Type() *types.Asset {
+ return &types.Asset{
+ Details: &types.AssetDetails{
+ Quantum: num.DecimalFromInt64(int64(d.quantum)),
+ },
+ }
+}
+
+func (dummyAsset) GetAssetClass() common.AssetClass { return common.ERC20 }
+func (dummyAsset) IsValid() bool { return true }
+func (dummyAsset) SetPendingListing() {}
+func (dummyAsset) SetRejected() {}
+func (dummyAsset) SetEnabled() {}
+func (dummyAsset) SetValid() {}
+func (dummyAsset) String() string { return "" }
+
+func newSnapshotEngine(t *testing.T, vegaPath paths.Paths, now time.Time, engine *vesting.SnapshotEngine) *snapshot.Engine {
+ t.Helper()
+
+ log := logging.NewTestLogger()
+ timeService := stubs.NewTimeStub()
+ timeService.SetTime(now)
+ statsData := stats.New(log, stats.NewDefaultConfig())
+ config := snapshot.DefaultConfig()
+
+ snapshotEngine, err := snapshot.NewEngine(vegaPath, config, log, timeService, statsData.Blockchain)
+ require.NoError(t, err)
+
+ snapshotEngine.AddProviders(engine)
+
+ return snapshotEngine
+}
+
+func nextEpoch(ctx context.Context, t *testing.T, te *testSnapshotEngine, startEpochTime time.Time) {
+ t.Helper()
+
+ te.engine.OnEpochEvent(ctx, types.Epoch{
+ Seq: te.currentEpoch,
+ Action: vegapb.EpochAction_EPOCH_ACTION_END,
+ EndTime: startEpochTime.Add(-1 * time.Second),
+ })
+
+ te.currentEpoch += 1
+ te.engine.OnEpochEvent(ctx, types.Epoch{
+ Seq: te.currentEpoch,
+ Action: vegapb.EpochAction_EPOCH_ACTION_START,
+ StartTime: startEpochTime,
+ })
+}
diff --git a/core/vesting/mocks/mocks.go b/core/vesting/mocks/mocks.go
index e455b24170..ef3e5e11c0 100644
--- a/core/vesting/mocks/mocks.go
+++ b/core/vesting/mocks/mocks.go
@@ -1,101 +1,17 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: code.vegaprotocol.io/vega/core/vesting (interfaces: Collateral,ActivityStreakVestingMultiplier,Broker,Assets)
+// Source: code.vegaprotocol.io/vega/core/vesting (interfaces: ActivityStreakVestingMultiplier,Assets)
// Package mocks is a generated GoMock package.
package mocks
import (
- context "context"
reflect "reflect"
assets "code.vegaprotocol.io/vega/core/assets"
- events "code.vegaprotocol.io/vega/core/events"
- types "code.vegaprotocol.io/vega/core/types"
- num "code.vegaprotocol.io/vega/libs/num"
gomock "github.com/golang/mock/gomock"
decimal "github.com/shopspring/decimal"
)
-// MockCollateral is a mock of Collateral interface.
-type MockCollateral struct {
- ctrl *gomock.Controller
- recorder *MockCollateralMockRecorder
-}
-
-// MockCollateralMockRecorder is the mock recorder for MockCollateral.
-type MockCollateralMockRecorder struct {
- mock *MockCollateral
-}
-
-// NewMockCollateral creates a new mock instance.
-func NewMockCollateral(ctrl *gomock.Controller) *MockCollateral {
- mock := &MockCollateral{ctrl: ctrl}
- mock.recorder = &MockCollateralMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockCollateral) EXPECT() *MockCollateralMockRecorder {
- return m.recorder
-}
-
-// GetAllVestingQuantumBalance mocks base method.
-func (m *MockCollateral) GetAllVestingQuantumBalance(arg0 string) *num.Uint {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetAllVestingQuantumBalance", arg0)
- ret0, _ := ret[0].(*num.Uint)
- return ret0
-}
-
-// GetAllVestingQuantumBalance indicates an expected call of GetAllVestingQuantumBalance.
-func (mr *MockCollateralMockRecorder) GetAllVestingQuantumBalance(arg0 interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllVestingQuantumBalance", reflect.TypeOf((*MockCollateral)(nil).GetAllVestingQuantumBalance), arg0)
-}
-
-// GetVestingAccounts mocks base method.
-func (m *MockCollateral) GetVestingAccounts() []*types.Account {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetVestingAccounts")
- ret0, _ := ret[0].([]*types.Account)
- return ret0
-}
-
-// GetVestingAccounts indicates an expected call of GetVestingAccounts.
-func (mr *MockCollateralMockRecorder) GetVestingAccounts() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVestingAccounts", reflect.TypeOf((*MockCollateral)(nil).GetVestingAccounts))
-}
-
-// GetVestingRecovery mocks base method.
-func (m *MockCollateral) GetVestingRecovery() map[string]map[string]*num.Uint {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetVestingRecovery")
- ret0, _ := ret[0].(map[string]map[string]*num.Uint)
- return ret0
-}
-
-// GetVestingRecovery indicates an expected call of GetVestingRecovery.
-func (mr *MockCollateralMockRecorder) GetVestingRecovery() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVestingRecovery", reflect.TypeOf((*MockCollateral)(nil).GetVestingRecovery))
-}
-
-// TransferVestedRewards mocks base method.
-func (m *MockCollateral) TransferVestedRewards(arg0 context.Context, arg1 []*types.Transfer) ([]*types.LedgerMovement, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "TransferVestedRewards", arg0, arg1)
- ret0, _ := ret[0].([]*types.LedgerMovement)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// TransferVestedRewards indicates an expected call of TransferVestedRewards.
-func (mr *MockCollateralMockRecorder) TransferVestedRewards(arg0, arg1 interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransferVestedRewards", reflect.TypeOf((*MockCollateral)(nil).TransferVestedRewards), arg0, arg1)
-}
-
// MockActivityStreakVestingMultiplier is a mock of ActivityStreakVestingMultiplier interface.
type MockActivityStreakVestingMultiplier struct {
ctrl *gomock.Controller
@@ -133,41 +49,6 @@ func (mr *MockActivityStreakVestingMultiplierMockRecorder) GetRewardsVestingMult
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRewardsVestingMultiplier", reflect.TypeOf((*MockActivityStreakVestingMultiplier)(nil).GetRewardsVestingMultiplier), arg0)
}
-// MockBroker is a mock of Broker interface.
-type MockBroker struct {
- ctrl *gomock.Controller
- recorder *MockBrokerMockRecorder
-}
-
-// MockBrokerMockRecorder is the mock recorder for MockBroker.
-type MockBrokerMockRecorder struct {
- mock *MockBroker
-}
-
-// NewMockBroker creates a new mock instance.
-func NewMockBroker(ctrl *gomock.Controller) *MockBroker {
- mock := &MockBroker{ctrl: ctrl}
- mock.recorder = &MockBrokerMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockBroker) EXPECT() *MockBrokerMockRecorder {
- return m.recorder
-}
-
-// Send mocks base method.
-func (m *MockBroker) Send(arg0 events.Event) {
- m.ctrl.T.Helper()
- m.ctrl.Call(m, "Send", arg0)
-}
-
-// Send indicates an expected call of Send.
-func (mr *MockBrokerMockRecorder) Send(arg0 interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockBroker)(nil).Send), arg0)
-}
-
// MockAssets is a mock of Assets interface.
type MockAssets struct {
ctrl *gomock.Controller
diff --git a/core/vesting/vesting_snapshot.go b/core/vesting/snapshot.go
similarity index 98%
rename from core/vesting/vesting_snapshot.go
rename to core/vesting/snapshot.go
index 0265014c90..ea7b9c3261 100644
--- a/core/vesting/vesting_snapshot.go
+++ b/core/vesting/snapshot.go
@@ -96,8 +96,7 @@ func (e *SnapshotEngine) loadStateFromSnapshot(_ context.Context, state *snapsho
e.log.Panic("uint256 in snapshot underflow",
logging.String("value", epochBalance.Balance))
}
- e.increaseLockedForAsset(
- entry.Party, locked.Asset, balance, epochBalance.Epoch)
+ e.increaseLockedForAsset(entry.Party, locked.Asset, balance, epochBalance.Epoch)
}
}
}
diff --git a/core/vesting/snapshot_test.go b/core/vesting/snapshot_test.go
new file mode 100644
index 0000000000..6ee3573d96
--- /dev/null
+++ b/core/vesting/snapshot_test.go
@@ -0,0 +1,164 @@
+// Copyright (C) 2023 Gobalsky Labs Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package vesting_test
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "code.vegaprotocol.io/vega/core/assets"
+ "code.vegaprotocol.io/vega/core/types"
+ "code.vegaprotocol.io/vega/libs/num"
+ vgtest "code.vegaprotocol.io/vega/libs/test"
+ "code.vegaprotocol.io/vega/paths"
+ vegapb "code.vegaprotocol.io/vega/protos/vega"
+
+ "github.com/golang/mock/gomock"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestSnapshotEngine(t *testing.T) {
+ ctx := vgtest.VegaContext("chainid", 100)
+
+ vegaPath := paths.New(t.TempDir())
+ now := time.Now()
+
+ te1 := newEngine(t)
+ snapshotEngine1 := newSnapshotEngine(t, vegaPath, now, te1.engine)
+ closeSnapshotEngine1 := vgtest.OnlyOnce(snapshotEngine1.Close)
+ defer closeSnapshotEngine1()
+
+ require.NoError(t, snapshotEngine1.Start(ctx))
+
+ setupMocks(t, te1)
+ setupNetParams(ctx, t, te1)
+
+ te1.engine.AddReward("party1", "eth", num.NewUint(100), 4)
+ te1.engine.AddReward("party1", "btc", num.NewUint(150), 1)
+ te1.engine.AddReward("party1", "eth", num.NewUint(200), 0)
+
+ nextEpoch(ctx, t, te1, time.Now())
+
+ te1.engine.AddReward("party2", "btc", num.NewUint(100), 2)
+ te1.engine.AddReward("party3", "btc", num.NewUint(100), 0)
+
+ nextEpoch(ctx, t, te1, time.Now())
+
+ te1.engine.AddReward("party4", "eth", num.NewUint(100), 1)
+ te1.engine.AddReward("party5", "doge", num.NewUint(100), 0)
+
+ // Take a snapshot.
+ hash1, err := snapshotEngine1.SnapshotNow(ctx)
+ require.NoError(t, err)
+ snapshottedEpoch := te1.currentEpoch
+
+ // This is what must be replayed after snapshot restoration.
+ replayFn := func(te *testSnapshotEngine) {
+ te.engine.AddReward("party6", "doge", num.NewUint(100), 3)
+
+ nextEpoch(ctx, t, te, time.Now())
+
+ te.engine.AddReward("party7", "eth", num.NewUint(100), 2)
+ te.engine.AddReward("party8", "vega", num.NewUint(100), 10)
+
+ nextEpoch(ctx, t, te, time.Now())
+ }
+
+ replayFn(te1)
+
+ state1 := map[string][]byte{}
+ for _, key := range te1.engine.Keys() {
+ state, additionalProvider, err := te1.engine.GetState(key)
+ require.NoError(t, err)
+ assert.Empty(t, additionalProvider)
+ state1[key] = state
+ }
+
+ closeSnapshotEngine1()
+
+ // Reload the engine using the previous snapshot.
+
+ te2 := newEngine(t)
+ snapshotEngine2 := newSnapshotEngine(t, vegaPath, now, te2.engine)
+ defer snapshotEngine2.Close()
+
+ setupMocks(t, te2)
+ setupNetParams(ctx, t, te2)
+
+ // Ensure the engine's epoch (and test helpers) starts at the same epoch the
+ // first engine has been snapshotted.
+ te2.currentEpoch = snapshottedEpoch
+ te2.engine.OnEpochRestore(ctx, types.Epoch{
+ Seq: snapshottedEpoch,
+ Action: vegapb.EpochAction_EPOCH_ACTION_START,
+ })
+
+ // This triggers the state restoration from the local snapshot.
+ require.NoError(t, snapshotEngine2.Start(ctx))
+
+ // Comparing the hash after restoration, to ensure it produces the same result.
+ hash2, _, _ := snapshotEngine2.Info()
+ require.Equal(t, hash1, hash2)
+
+ // Replaying the same commands after snapshot has been taken with first engine.
+ replayFn(te2)
+
+ state2 := map[string][]byte{}
+ for _, key := range te2.engine.Keys() {
+ state, additionalProvider, err := te2.engine.GetState(key)
+ require.NoError(t, err)
+ assert.Empty(t, additionalProvider)
+ state2[key] = state
+ }
+
+ for key := range state1 {
+ assert.Equalf(t, state1[key], state2[key], "Key %q does not have the same data", key)
+ }
+}
+
+func setupNetParams(ctx context.Context, t *testing.T, te *testSnapshotEngine) {
+ t.Helper()
+
+ require.NoError(t, te.engine.OnBenefitTiersUpdate(ctx, &vegapb.VestingBenefitTiers{
+ Tiers: []*vegapb.VestingBenefitTier{
+ {
+ MinimumQuantumBalance: "10000",
+ RewardMultiplier: "1.5",
+ },
+ {
+ MinimumQuantumBalance: "100000",
+ RewardMultiplier: "2",
+ },
+ {
+ MinimumQuantumBalance: "500000",
+ RewardMultiplier: "2.5",
+ },
+ },
+ }))
+
+ require.NoError(t, te.engine.OnRewardVestingBaseRateUpdate(ctx, num.MustDecimalFromString("0.9")))
+ require.NoError(t, te.engine.OnRewardVestingMinimumTransferUpdate(ctx, num.MustDecimalFromString("1")))
+}
+
+func setupMocks(t *testing.T, te *testSnapshotEngine) {
+ t.Helper()
+
+ te.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1"))
+ te.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
+ te.broker.EXPECT().Send(gomock.Any()).AnyTimes()
+}
diff --git a/core/vesting/vesting_snapshot_test.go b/core/vesting/vesting_snapshot_test.go
deleted file mode 100644
index ae91f49a1a..0000000000
--- a/core/vesting/vesting_snapshot_test.go
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (C) 2023 Gobalsky Labs Limited
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-package vesting_test
-
-import (
- "context"
- "testing"
-
- "code.vegaprotocol.io/vega/core/assets"
- "code.vegaprotocol.io/vega/core/types"
- "code.vegaprotocol.io/vega/core/vesting"
- "code.vegaprotocol.io/vega/core/vesting/mocks"
- "code.vegaprotocol.io/vega/libs/num"
- "code.vegaprotocol.io/vega/libs/proto"
- "code.vegaprotocol.io/vega/logging"
- vegapb "code.vegaprotocol.io/vega/protos/vega"
- snapshotpb "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
-
- "github.com/golang/mock/gomock"
- "github.com/stretchr/testify/assert"
-)
-
-type testSnapshotEngine struct {
- *vesting.SnapshotEngine
-
- ctrl *gomock.Controller
- col *mocks.MockCollateral
- asvm *mocks.MockActivityStreakVestingMultiplier
- broker *mocks.MockBroker
- assets *mocks.MockAssets
-}
-
-func getTestSnapshotEngine(t *testing.T) *testSnapshotEngine {
- t.Helper()
- ctrl := gomock.NewController(t)
- col := mocks.NewMockCollateral(ctrl)
- asvm := mocks.NewMockActivityStreakVestingMultiplier(ctrl)
- broker := mocks.NewMockBroker(ctrl)
- assets := mocks.NewMockAssets(ctrl)
-
- return &testSnapshotEngine{
- SnapshotEngine: vesting.NewSnapshotEngine(
- logging.NewTestLogger(), col, asvm, broker, assets,
- ),
- ctrl: ctrl,
- col: col,
- asvm: asvm,
- broker: broker,
- assets: assets,
- }
-}
-
-func TestSnapshot(t *testing.T) {
- v1 := getTestSnapshotEngine(t)
- setDefaults(t, v1)
-
- // set couple of rewards
- v1.AddReward("party1", "eth", num.NewUint(100), 4)
- v1.AddReward("party1", "btc", num.NewUint(150), 1)
- v1.AddReward("party1", "eth", num.NewUint(200), 0)
- v1.AddReward("party2", "btc", num.NewUint(100), 2)
- v1.AddReward("party3", "btc", num.NewUint(100), 0)
- v1.AddReward("party4", "eth", num.NewUint(100), 1)
- v1.AddReward("party5", "doge", num.NewUint(100), 0)
- v1.AddReward("party5", "btc", num.NewUint(1420), 1)
- v1.AddReward("party6", "doge", num.NewUint(100), 3)
- v1.AddReward("party7", "eth", num.NewUint(100), 2)
- v1.AddReward("party8", "vega", num.NewUint(100), 10)
-
- state1, _, err := v1.GetState(vesting.VestingKey)
- assert.NoError(t, err)
- assert.NotNil(t, state1)
-
- ppayload := &snapshotpb.Payload{}
- err = proto.Unmarshal(state1, ppayload)
- assert.NoError(t, err)
-
- v2 := getTestSnapshotEngine(t)
- setDefaults(t, v2)
- _, err = v2.LoadState(context.Background(), types.PayloadFromProto(ppayload))
- assert.NoError(t, err)
-
- // now assert the v2 produce the same state
- state2, _, err := v2.GetState(vesting.VestingKey)
- assert.NoError(t, err)
- assert.NotNil(t, state2)
-
- assert.Equal(t, state1, state2)
-
- // now move a couple of epoch for good measure
- epochsForward(t, v1)
- epochsForward(t, v2)
-
- // now assert the v2 produce the same state
- state1, _, err = v1.GetState(vesting.VestingKey)
- assert.NoError(t, err)
- assert.NotNil(t, state1)
- state2, _, err = v2.GetState(vesting.VestingKey)
- assert.NoError(t, err)
- assert.NotNil(t, state2)
-
- assert.Equal(t, state1, state2)
-}
-
-func epochsForward(t *testing.T, v *testSnapshotEngine) {
- t.Helper()
-
- // expect at least 3 transfers and events call, 1 per epoch move
- v.col.EXPECT().TransferVestedRewards(gomock.Any(), gomock.Any()).Times(3).Return(nil, nil)
- v.col.EXPECT().GetAllVestingQuantumBalance(gomock.Any()).AnyTimes().Return(num.UintZero())
- v.broker.EXPECT().Send(gomock.Any()).Times(9)
-
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-}
-
-func setDefaults(t *testing.T, v *testSnapshotEngine) {
- t.Helper()
- v.OnRewardVestingBaseRateUpdate(context.Background(), num.MustDecimalFromString("0.9"))
- v.OnRewardVestingMinimumTransferUpdate(context.Background(), num.MustDecimalFromString("1"))
- v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1"))
- v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(
- assets.NewAsset(dummyAsset{quantum: 10}), nil,
- )
-}
diff --git a/core/vesting/vesting_test.go b/core/vesting/vesting_test.go
deleted file mode 100644
index 8396ae1992..0000000000
--- a/core/vesting/vesting_test.go
+++ /dev/null
@@ -1,468 +0,0 @@
-// Copyright (C) 2023 Gobalsky Labs Limited
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-package vesting_test
-
-import (
- "context"
- "testing"
-
- "code.vegaprotocol.io/vega/core/assets"
- "code.vegaprotocol.io/vega/core/assets/common"
- "code.vegaprotocol.io/vega/core/types"
- "code.vegaprotocol.io/vega/core/vesting"
- "code.vegaprotocol.io/vega/core/vesting/mocks"
- "code.vegaprotocol.io/vega/libs/num"
- "code.vegaprotocol.io/vega/logging"
- vegapb "code.vegaprotocol.io/vega/protos/vega"
-
- "github.com/golang/mock/gomock"
- "github.com/stretchr/testify/assert"
-)
-
-type testEngine struct {
- *vesting.Engine
-
- ctrl *gomock.Controller
- col *mocks.MockCollateral
- asvm *mocks.MockActivityStreakVestingMultiplier
- broker *mocks.MockBroker
- assets *mocks.MockAssets
-}
-
-func getTestEngine(t *testing.T) *testEngine {
- t.Helper()
- ctrl := gomock.NewController(t)
- col := mocks.NewMockCollateral(ctrl)
- asvm := mocks.NewMockActivityStreakVestingMultiplier(ctrl)
- broker := mocks.NewMockBroker(ctrl)
- assets := mocks.NewMockAssets(ctrl)
-
- return &testEngine{
- Engine: vesting.New(
- logging.NewTestLogger(), col, asvm, broker, assets,
- ),
- ctrl: ctrl,
- col: col,
- asvm: asvm,
- broker: broker,
- assets: assets,
- }
-}
-
-func TestRewardMultiplier(t *testing.T) {
- v := getTestEngine(t)
-
- // set benefits tiers
- err := v.OnBenefitTiersUpdate(context.Background(), &vegapb.VestingBenefitTiers{
- Tiers: []*vegapb.VestingBenefitTier{
- {
- MinimumQuantumBalance: "10000",
- RewardMultiplier: "1.5",
- },
- {
- MinimumQuantumBalance: "100000",
- RewardMultiplier: "2",
- },
- {
- MinimumQuantumBalance: "500000",
- RewardMultiplier: "2.5",
- },
- },
- })
-
- assert.NoError(t, err)
-
- v.col.EXPECT().GetAllVestingQuantumBalance("party1").Times(1).Return(num.UintZero())
- quantumBalance, bonus := v.GetRewardBonusMultiplier("party1")
- assert.Equal(t, num.DecimalOne(), bonus)
- assert.Equal(t, num.UintZero(), quantumBalance)
-
- v.col.EXPECT().GetAllVestingQuantumBalance("party1").Times(1).Return(num.NewUint(10001))
- quantumBalance, bonus = v.GetRewardBonusMultiplier("party1")
- assert.Equal(t, num.MustDecimalFromString("1.5"), bonus)
- assert.Equal(t, num.MustUintFromString("10001", 10), quantumBalance)
-
- v.col.EXPECT().GetAllVestingQuantumBalance("party1").Times(1).Return(num.NewUint(100001))
- quantumBalance, bonus = v.GetRewardBonusMultiplier("party1")
- assert.Equal(t, num.MustDecimalFromString("2"), bonus)
- assert.Equal(t, num.MustUintFromString("100001", 10), quantumBalance)
-
- v.col.EXPECT().GetAllVestingQuantumBalance("party1").Times(1).Return(num.NewUint(500001))
- quantumBalance, bonus = v.GetRewardBonusMultiplier("party1")
- assert.Equal(t, num.MustDecimalFromString("2.5"), bonus)
- assert.Equal(t, num.MustUintFromString("500001", 10), quantumBalance)
-}
-
-func TestDistributeAfterDelay(t *testing.T) {
- v := getTestEngine(t)
-
- // distribute 90% as the base rate,
- // so first we distribute some, then we get under the minimum value, and all the rest
- // is distributed
- v.OnRewardVestingBaseRateUpdate(context.Background(), num.MustDecimalFromString("0.9"))
- // this is multiplied by the quantume, so it will make it 100% of the quantum
- v.OnRewardVestingMinimumTransferUpdate(context.Background(), num.MustDecimalFromString("1"))
-
- v.col.EXPECT().GetAllVestingQuantumBalance(gomock.Any()).AnyTimes().Return(num.UintZero())
-
- // set the asvm to return always 1
- v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1"))
-
- // set asset to return proper quantum
- v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
- v.broker.EXPECT().Send(gomock.Any()).Times(2)
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // Add a reward to be locked for 3 epochs then
- // we add a 100 of the reward.
- // it will be paid in 2 times, first 90,
- // then the remain 10,
- // and it'll be all
- v.AddReward("party1", "eth", num.NewUint(100), 3)
- v.broker.EXPECT().Send(gomock.Any()).Times(2)
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
- v.broker.EXPECT().Send(gomock.Any()).Times(2)
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
- v.broker.EXPECT().Send(gomock.Any()).Times(2)
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // now we expect 1 call to the collateral for the transfer of 90, for the transfer of the 90
- v.col.EXPECT().TransferVestedRewards(gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
- func(ctx context.Context, transfers []*types.Transfer) ([]*types.LedgerMovements, error) {
- assert.Len(t, transfers, 1)
- assert.Equal(t, int(transfers[0].Amount.Amount.Uint64()), 90)
- assert.Equal(t, transfers[0].Owner, "party1")
- assert.Equal(t, int(transfers[0].MinAmount.Uint64()), 90)
- assert.Equal(t, transfers[0].Amount.Asset, "eth")
- return nil, nil
- },
- )
- // one call to the broker
- v.broker.EXPECT().Send(gomock.Any()).Times(3)
-
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // now we expect 1 call to the collateral for the transfer of 10, for the transfer of the 90, which is the whole remaining thing
- v.col.EXPECT().TransferVestedRewards(gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
- func(ctx context.Context, transfers []*types.Transfer) ([]*types.LedgerMovements, error) {
- assert.Len(t, transfers, 1)
- assert.Equal(t, int(transfers[0].Amount.Amount.Uint64()), 10)
- assert.Equal(t, transfers[0].Owner, "party1")
- assert.Equal(t, int(transfers[0].MinAmount.Uint64()), 10)
- assert.Equal(t, transfers[0].Amount.Asset, "eth")
- return nil, nil
- },
- )
- // one call to the broker
- v.broker.EXPECT().Send(gomock.Any()).Times(3)
-
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // try it again and nothing happen
- v.broker.EXPECT().Send(gomock.Any()).Times(2)
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-}
-
-func TestDistributeWithNoDelay(t *testing.T) {
- v := getTestEngine(t)
-
- // distribute 90% as the base rate,
- // so first we distribute some, then we get under the minimum value, and all the rest
- // is distributed
- v.OnRewardVestingBaseRateUpdate(context.Background(), num.MustDecimalFromString("0.9"))
- // this is multiplied by the quantume, so it will make it 100% of the quantum
- v.OnRewardVestingMinimumTransferUpdate(context.Background(), num.MustDecimalFromString("1"))
-
- v.col.EXPECT().GetAllVestingQuantumBalance(gomock.Any()).AnyTimes().Return(num.UintZero())
-
- // set the asvm to return always 1
- v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1"))
-
- // set asset to return proper quantum
- v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
- v.broker.EXPECT().Send(gomock.Any()).Times(2)
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // we add a 100 of the reward.
- // it will be paid in 2 times, first 90,
- // then the remain 10,
- // and it'll be all
- v.AddReward("party1", "eth", num.NewUint(100), 0)
-
- // now we expect 1 call to the collateral for the transfer of 90, for the transfer of the 90
- v.col.EXPECT().TransferVestedRewards(gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
- func(ctx context.Context, transfers []*types.Transfer) ([]*types.LedgerMovements, error) {
- assert.Len(t, transfers, 1)
- assert.Equal(t, int(transfers[0].Amount.Amount.Uint64()), 90)
- assert.Equal(t, transfers[0].Owner, "party1")
- assert.Equal(t, int(transfers[0].MinAmount.Uint64()), 90)
- assert.Equal(t, transfers[0].Amount.Asset, "eth")
- return nil, nil
- },
- )
- // one call to the broker
- v.broker.EXPECT().Send(gomock.Any()).Times(3)
-
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // now we expect 1 call to the collateral for the transfer of 10, for the transfer of the 90, which is the whole remaining thing
- v.col.EXPECT().TransferVestedRewards(gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
- func(ctx context.Context, transfers []*types.Transfer) ([]*types.LedgerMovements, error) {
- assert.Len(t, transfers, 1)
- assert.Equal(t, int(transfers[0].Amount.Amount.Uint64()), 10)
- assert.Equal(t, transfers[0].Owner, "party1")
- assert.Equal(t, int(transfers[0].MinAmount.Uint64()), 10)
- assert.Equal(t, transfers[0].Amount.Asset, "eth")
- return nil, nil
- },
- )
- // one call to the broker
- v.broker.EXPECT().Send(gomock.Any()).Times(3)
-
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // try it again and nothing happen
- v.broker.EXPECT().Send(gomock.Any()).Times(2)
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-}
-
-func TestDistributeWithStreakRate(t *testing.T) {
- v := getTestEngine(t)
-
- // distribute 90% as the base rate,
- // so first we distribute some, then we get under the minimum value, and all the rest
- // is distributed
- v.OnRewardVestingBaseRateUpdate(context.Background(), num.MustDecimalFromString("0.9"))
- // this is multiplied by the quantume, so it will make it 100% of the quantum
- v.OnRewardVestingMinimumTransferUpdate(context.Background(), num.MustDecimalFromString("1"))
-
- v.col.EXPECT().GetAllVestingQuantumBalance(gomock.Any()).AnyTimes().Return(num.UintZero())
-
- // set the asvm to return always 1
- v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1.1"))
-
- // set asset to return proper quantum
- v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
- v.broker.EXPECT().Send(gomock.Any()).Times(2)
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // Add a reward to be locked for 3 epochs then
- // we add a 100 of the reward.
- // it will be paid in 2 times, first 90,
- // then the remain 10,
- // and it'll be all
- v.AddReward("party1", "eth", num.NewUint(100), 0)
-
- // now we expect 1 call to the collateral for the transfer of 99, for the transfer of the 99
- // this is 100 * 0.9 + 1.1
- v.col.EXPECT().TransferVestedRewards(gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
- func(ctx context.Context, transfers []*types.Transfer) ([]*types.LedgerMovements, error) {
- assert.Len(t, transfers, 1)
- assert.Equal(t, int(transfers[0].Amount.Amount.Uint64()), 99)
- assert.Equal(t, transfers[0].Owner, "party1")
- assert.Equal(t, int(transfers[0].MinAmount.Uint64()), 99)
- assert.Equal(t, transfers[0].Amount.Asset, "eth")
- return nil, nil
- },
- )
- // one call to the broker
- v.broker.EXPECT().Send(gomock.Any()).Times(3)
-
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // now we expect 1 call to the collateral for the transfer of 10, for the transfer of the 90, which is the whole remaining thing
- v.col.EXPECT().TransferVestedRewards(gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
- func(ctx context.Context, transfers []*types.Transfer) ([]*types.LedgerMovements, error) {
- assert.Len(t, transfers, 1)
- assert.Equal(t, int(transfers[0].Amount.Amount.Uint64()), 1)
- assert.Equal(t, transfers[0].Owner, "party1")
- assert.Equal(t, int(transfers[0].MinAmount.Uint64()), 1)
- assert.Equal(t, transfers[0].Amount.Asset, "eth")
- return nil, nil
- },
- )
- // one call to the broker
- v.broker.EXPECT().Send(gomock.Any()).Times(3)
-
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // try it again and nothing happen
- v.broker.EXPECT().Send(gomock.Any()).Times(2)
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-}
-
-func TestDistributeMultipleAfterDelay(t *testing.T) {
- v := getTestEngine(t)
-
- // distribute 90% as the base rate,
- // so first we distribute some, then we get under the minimum value, and all the rest
- // is distributed
- v.OnRewardVestingBaseRateUpdate(context.Background(), num.MustDecimalFromString("0.9"))
- // this is multiplied by the quantume, so it will make it 100% of the quantum
- v.OnRewardVestingMinimumTransferUpdate(context.Background(), num.MustDecimalFromString("1"))
-
- v.col.EXPECT().GetAllVestingQuantumBalance(gomock.Any()).AnyTimes().Return(num.UintZero())
-
- // set the asvm to return always 1
- v.asvm.EXPECT().GetRewardsVestingMultiplier(gomock.Any()).AnyTimes().Return(num.MustDecimalFromString("1"))
-
- // set asset to return proper quantum
- v.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(dummyAsset{quantum: 10}), nil)
- v.broker.EXPECT().Send(gomock.Any()).Times(2)
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // Add a reward to be locked for 2 epochs then
- // we add a 100 of the reward.
- v.AddReward("party1", "eth", num.NewUint(100), 2)
- // then another for 1 epoch
- v.AddReward("party1", "eth", num.NewUint(100), 1)
- v.broker.EXPECT().Send(gomock.Any()).Times(2)
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // now we expect 1 call to the collateral for the transfer of 90, for the transfer of the 90
- v.col.EXPECT().TransferVestedRewards(gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
- func(ctx context.Context, transfers []*types.Transfer) ([]*types.LedgerMovements, error) {
- assert.Len(t, transfers, 1)
- assert.Equal(t, int(transfers[0].Amount.Amount.Uint64()), 90)
- assert.Equal(t, transfers[0].Owner, "party1")
- assert.Equal(t, int(transfers[0].MinAmount.Uint64()), 90)
- assert.Equal(t, transfers[0].Amount.Asset, "eth")
- return nil, nil
- },
- )
- // one call to the broker
- v.broker.EXPECT().Send(gomock.Any()).Times(3)
-
- // this will deliver 100 more as well ready to be paid
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // now we expect another transfer of 99 which is 110*0.9
- v.col.EXPECT().TransferVestedRewards(gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
- func(ctx context.Context, transfers []*types.Transfer) ([]*types.LedgerMovements, error) {
- assert.Len(t, transfers, 1)
- assert.Equal(t, int(transfers[0].Amount.Amount.Uint64()), 99)
- assert.Equal(t, transfers[0].Owner, "party1")
- assert.Equal(t, int(transfers[0].MinAmount.Uint64()), 99)
- assert.Equal(t, transfers[0].Amount.Asset, "eth")
- return nil, nil
- },
- )
- // one call to the broker
- v.broker.EXPECT().Send(gomock.Any()).Times(3)
-
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // now we expect another transfer of 9 which is 110*0.9 floored
- // but it's actually defaulting to 10 which is the minimum acceptable transfer
- v.col.EXPECT().TransferVestedRewards(gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
- func(ctx context.Context, transfers []*types.Transfer) ([]*types.LedgerMovements, error) {
- assert.Len(t, transfers, 1)
- assert.Equal(t, int(transfers[0].Amount.Amount.Uint64()), 10)
- assert.Equal(t, transfers[0].Owner, "party1")
- assert.Equal(t, int(transfers[0].MinAmount.Uint64()), 10)
- assert.Equal(t, transfers[0].Amount.Asset, "eth")
- return nil, nil
- },
- )
- // one call to the broker
- v.broker.EXPECT().Send(gomock.Any()).Times(3)
-
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // now we expect another transfer of 1 which is all that is left
- v.col.EXPECT().TransferVestedRewards(gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
- func(ctx context.Context, transfers []*types.Transfer) ([]*types.LedgerMovements, error) {
- assert.Len(t, transfers, 1)
- assert.Equal(t, int(transfers[0].Amount.Amount.Uint64()), 1)
- assert.Equal(t, transfers[0].Owner, "party1")
- assert.Equal(t, int(transfers[0].MinAmount.Uint64()), 1)
- assert.Equal(t, transfers[0].Amount.Asset, "eth")
- return nil, nil
- },
- )
- // one call to the broker
- v.broker.EXPECT().Send(gomock.Any()).Times(3)
-
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-
- // try it again and nothing happen
- v.broker.EXPECT().Send(gomock.Any()).Times(2)
- v.OnEpochEvent(context.Background(), types.Epoch{
- Action: vegapb.EpochAction_EPOCH_ACTION_END,
- })
-}
-
-type dummyAsset struct {
- quantum uint64
-}
-
-func (d dummyAsset) Type() *types.Asset {
- return &types.Asset{
- Details: &types.AssetDetails{
- Quantum: num.DecimalFromInt64(int64(d.quantum)),
- },
- }
-}
-
-func (dummyAsset) GetAssetClass() common.AssetClass { return common.ERC20 }
-func (dummyAsset) IsValid() bool { return true }
-func (dummyAsset) SetPendingListing() {}
-func (dummyAsset) SetRejected() {}
-func (dummyAsset) SetEnabled() {}
-func (dummyAsset) SetValid() {}
-func (dummyAsset) String() string { return "" }