diff --git a/CHANGELOG.md b/CHANGELOG.md index 741872bc..fedf109d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ An '!' indicates a state machine breaking change. - ! (`x/stakers`) [#209](https://github.com/KYVENetwork/chain/pull/209) Shared Staking: Consensus-validator stake is now used for the protocol. - ! (`x/stakers`) [#210](https://github.com/KYVENetwork/chain/pull/210) Shared Staking: Pool specific commission and stake fraction. +- ! (`x/stakers`) [#211](https://github.com/KYVENetwork/chain/pull/211) Shared Staking: Maximum voting power per pool. ### Bug Fixes diff --git a/proto/kyve/stakers/v1beta1/events.proto b/proto/kyve/stakers/v1beta1/events.proto index 78a7313a..9abf0df3 100644 --- a/proto/kyve/stakers/v1beta1/events.proto +++ b/proto/kyve/stakers/v1beta1/events.proto @@ -96,8 +96,14 @@ message EventSlash { uint64 pool_id = 1; // staker is the account address of the protocol node. string staker = 2; - // amount ... + // amount is the total amount that got slashed uint64 amount = 3; - // slash_type + // slash_type is the type of the protocol slash SlashType slash_type = 4; + // stake_fraction is the percentage of how much of the validators total + // bonded amount was under risk for slashing + string stake_fraction = 5 [ + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; } diff --git a/testutil/integration/checks.go b/testutil/integration/checks.go index 94c1de37..b76c5fdf 100644 --- a/testutil/integration/checks.go +++ b/testutil/integration/checks.go @@ -101,7 +101,7 @@ func (suite *KeeperTestSuite) VerifyPoolQueries() { for i := range poolsState { bundleProposalState, _ := suite.App().BundlesKeeper.GetBundleProposal(suite.Ctx(), poolsState[i].Id) stakersState := suite.App().StakersKeeper.GetAllStakerAddressesOfPool(suite.Ctx(), poolsState[i].Id) - totalDelegationState := suite.App().StakersKeeper.GetDelegationOfPool(suite.Ctx(), poolsState[i].Id) + totalDelegationState := suite.App().StakersKeeper.GetTotalStakeOfPool(suite.Ctx(), poolsState[i].Id) Expect(poolsQuery[i].Id).To(Equal(poolsState[i].Id)) Expect(*poolsQuery[i].Data).To(Equal(poolsState[i])) @@ -175,7 +175,7 @@ func (suite *KeeperTestSuite) VerifyStakersModuleAssetsIntegrity() { func (suite *KeeperTestSuite) VerifyPoolTotalStake() { for _, pool := range suite.App().PoolKeeper.GetAllPools(suite.Ctx()) { expectedBalance := uint64(0) - actualBalance := suite.App().StakersKeeper.GetDelegationOfPool(suite.Ctx(), pool.Id) + actualBalance := suite.App().StakersKeeper.GetTotalStakeOfPool(suite.Ctx(), pool.Id) for _, stakerAddress := range suite.App().StakersKeeper.GetAllStakerAddressesOfPool(suite.Ctx(), pool.Id) { expectedBalance += suite.App().StakersKeeper.GetValidatorPoolStake(suite.Ctx(), stakerAddress, pool.Id) diff --git a/util/arrays.go b/util/arrays.go index 8cf4815b..fd948cdc 100644 --- a/util/arrays.go +++ b/util/arrays.go @@ -38,3 +38,15 @@ func ContainsString(array []string, match string) bool { } return false } + +func RemoveDuplicateStrings(strSlice []string) []string { + allKeys := make(map[string]bool) + list := []string{} + for _, item := range strSlice { + if _, value := allKeys[item]; !value { + allKeys[item] = true + list = append(list, item) + } + } + return list +} diff --git a/x/bundles/keeper/keeper_suite_invalid_bundles_test.go b/x/bundles/keeper/keeper_suite_invalid_bundles_test.go index 32d1f9b0..d6b270d0 100644 --- a/x/bundles/keeper/keeper_suite_invalid_bundles_test.go +++ b/x/bundles/keeper/keeper_suite_invalid_bundles_test.go @@ -23,6 +23,8 @@ TEST CASES - invalid bundles * Produce an invalid bundle with multiple validators and no foreign delegations * Produce an invalid bundle with multiple validators and foreign delegations * Produce an invalid bundle with multiple validators although some voted valid +* Produce an invalid bundle with multiple validators and stake fractions +* Produce an invalid bundle with multiple validators and stake fractions and foreign delegations */ @@ -62,6 +64,10 @@ var _ = Describe("invalid bundles", Ordered, func() { } s.RunTxPoolSuccess(msg) + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("1") + s.App().PoolKeeper.SetParams(s.Ctx(), params) + s.RunTxFundersSuccess(&funderstypes.MsgCreateFunder{ Creator: i.ALICE, Moniker: "Alice", @@ -74,6 +80,22 @@ var _ = Describe("invalid bundles", Ordered, func() { AmountsPerBundle: i.KYVECoins(1 * i.T_KYVE), }) + initialBalanceStaker0 = s.GetBalanceFromAddress(i.STAKER_0) + initialBalanceValaddress0 = s.GetBalanceFromAddress(i.VALADDRESS_0_A) + + initialBalanceStaker1 = s.GetBalanceFromAddress(i.STAKER_1) + initialBalanceValaddress1 = s.GetBalanceFromAddress(i.VALADDRESS_1_A) + + initialBalanceStaker2 = s.GetBalanceFromAddress(i.STAKER_2) + initialBalanceValaddress2 = s.GetBalanceFromAddress(i.VALADDRESS_2_A) + }) + + AfterEach(func() { + s.PerformValidityChecks() + }) + + It("Produce an invalid bundle with multiple validators and no foreign delegations", func() { + // ARRANGE s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ @@ -94,30 +116,8 @@ var _ = Describe("invalid bundles", Ordered, func() { StakeFraction: math.LegacyMustNewDecFromStr("1"), }) - s.RunTxBundlesSuccess(&bundletypes.MsgClaimUploaderRole{ - Creator: i.VALADDRESS_0_A, - Staker: i.STAKER_0, - PoolId: 0, - }) - - initialBalanceStaker0 = s.GetBalanceFromAddress(i.STAKER_0) - initialBalanceValaddress0 = s.GetBalanceFromAddress(i.VALADDRESS_0_A) - - initialBalanceStaker1 = s.GetBalanceFromAddress(i.STAKER_1) - initialBalanceValaddress1 = s.GetBalanceFromAddress(i.VALADDRESS_1_A) - - initialBalanceStaker2 = s.GetBalanceFromAddress(i.STAKER_2) - initialBalanceValaddress2 = s.GetBalanceFromAddress(i.VALADDRESS_2_A) - s.CommitAfterSeconds(60) - }) - AfterEach(func() { - s.PerformValidityChecks() - }) - - It("Produce an invalid bundle with multiple validators and no foreign delegations", func() { - // ARRANGE s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{ Creator: i.VALADDRESS_0_A, Staker: i.STAKER_0, @@ -142,7 +142,14 @@ var _ = Describe("invalid bundles", Ordered, func() { StakeFraction: math.LegacyMustNewDecFromStr("1"), }) - initialBalanceStaker1 = s.GetBalanceFromAddress(i.STAKER_2) + initialBalanceStaker0 = s.GetBalanceFromAddress(i.STAKER_0) + initialBalanceValaddress0 = s.GetBalanceFromAddress(i.VALADDRESS_0_A) + + initialBalanceStaker1 = s.GetBalanceFromAddress(i.STAKER_1) + initialBalanceValaddress1 = s.GetBalanceFromAddress(i.VALADDRESS_1_A) + + initialBalanceStaker2 = s.GetBalanceFromAddress(i.STAKER_2) + initialBalanceValaddress2 = s.GetBalanceFromAddress(i.VALADDRESS_2_A) // ACT s.RunTxBundlesSuccess(&bundletypes.MsgVoteBundleProposal{ @@ -164,8 +171,8 @@ var _ = Describe("invalid bundles", Ordered, func() { s.CommitAfterSeconds(60) s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{ - Creator: i.VALADDRESS_1_A, - Staker: i.STAKER_1, + Creator: i.VALADDRESS_0_A, + Staker: i.STAKER_0, PoolId: 0, StorageId: "P9edn0bjEfMU_lecFDIPLvGO2v2ltpFNUMWp5kgPddg", DataSize: 100, @@ -230,7 +237,7 @@ var _ = Describe("invalid bundles", Ordered, func() { slashAmount := uint64(math.LegacyNewDec(int64(100 * i.KYVE)).Mul(fraction).TruncateInt64()) Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_0, i.STAKER_0)).To(Equal(100*i.KYVE - slashAmount)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) // check voter status valaccountVoter, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_1) @@ -266,6 +273,26 @@ var _ = Describe("invalid bundles", Ordered, func() { It("Produce an invalid bundle with multiple validators and foreign delegations", func() { // ARRANGE + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) + + s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(100*i.KYVE)) + + s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + s.RunTxSuccess(stakingTypes.NewMsgDelegate( i.ALICE, util.MustValaddressFromOperatorAddress(i.STAKER_0), @@ -294,6 +321,8 @@ var _ = Describe("invalid bundles", Ordered, func() { sdk.NewInt64Coin(globalTypes.Denom, int64(100*i.KYVE)), )) + s.CommitAfterSeconds(60) + s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{ Creator: i.VALADDRESS_0_A, Staker: i.STAKER_0, @@ -308,7 +337,14 @@ var _ = Describe("invalid bundles", Ordered, func() { BundleSummary: "test_value", }) + initialBalanceStaker0 = s.GetBalanceFromAddress(i.STAKER_0) + initialBalanceValaddress0 = s.GetBalanceFromAddress(i.VALADDRESS_0_A) + initialBalanceStaker1 = s.GetBalanceFromAddress(i.STAKER_1) + initialBalanceValaddress1 = s.GetBalanceFromAddress(i.VALADDRESS_1_A) + + initialBalanceStaker2 = s.GetBalanceFromAddress(i.STAKER_2) + initialBalanceValaddress2 = s.GetBalanceFromAddress(i.VALADDRESS_2_A) // ACT s.RunTxBundlesSuccess(&bundletypes.MsgVoteBundleProposal{ @@ -330,8 +366,8 @@ var _ = Describe("invalid bundles", Ordered, func() { s.CommitAfterSeconds(60) s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{ - Creator: i.VALADDRESS_1_A, - Staker: i.STAKER_1, + Creator: i.VALADDRESS_0_A, + Staker: i.STAKER_0, PoolId: 0, StorageId: "P9edn0bjEfMU_lecFDIPLvGO2v2ltpFNUMWp5kgPddg", DataSize: 100, @@ -400,7 +436,7 @@ var _ = Describe("invalid bundles", Ordered, func() { Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_0, i.STAKER_0)).To(Equal(100*i.KYVE - slashAmountUploader)) Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_0, i.ALICE)).To(Equal(100*i.KYVE - slashAmountDelegator)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(400 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(400 * i.KYVE)) // check voter status valaccountVoter, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_1) @@ -436,6 +472,26 @@ var _ = Describe("invalid bundles", Ordered, func() { It("Produce an invalid bundle with multiple validators although some voted valid", func() { // ARRANGE + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) + + s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(100*i.KYVE)) + + s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + s.RunTxSuccess(stakingTypes.NewMsgDelegate( i.ALICE, util.MustValaddressFromOperatorAddress(i.STAKER_0), @@ -481,6 +537,8 @@ var _ = Describe("invalid bundles", Ordered, func() { sdk.NewInt64Coin(globalTypes.Denom, int64(150*i.KYVE)), )) + s.CommitAfterSeconds(60) + s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{ Creator: i.VALADDRESS_0_A, Staker: i.STAKER_0, @@ -495,8 +553,14 @@ var _ = Describe("invalid bundles", Ordered, func() { BundleSummary: "test_value", }) + initialBalanceStaker0 = s.GetBalanceFromAddress(i.STAKER_0) + initialBalanceValaddress0 = s.GetBalanceFromAddress(i.VALADDRESS_0_A) + initialBalanceStaker1 = s.GetBalanceFromAddress(i.STAKER_1) + initialBalanceValaddress1 = s.GetBalanceFromAddress(i.VALADDRESS_1_A) + initialBalanceStaker2 = s.GetBalanceFromAddress(i.STAKER_2) + initialBalanceValaddress2 = s.GetBalanceFromAddress(i.VALADDRESS_2_A) // ACT s.RunTxBundlesSuccess(&bundletypes.MsgVoteBundleProposal{ @@ -526,8 +590,8 @@ var _ = Describe("invalid bundles", Ordered, func() { s.CommitAfterSeconds(60) s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{ - Creator: i.VALADDRESS_3_A, - Staker: i.STAKER_3, + Creator: i.VALADDRESS_0_A, + Staker: i.STAKER_0, PoolId: 0, StorageId: "P9edn0bjEfMU_lecFDIPLvGO2v2ltpFNUMWp5kgPddg", DataSize: 100, @@ -604,7 +668,7 @@ var _ = Describe("invalid bundles", Ordered, func() { Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_1, i.STAKER_1)).To(Equal(100*i.KYVE - slashAmountVoter)) Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_1, i.BOB)).To(Equal(100*i.KYVE - slashAmountDelegator2)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(450 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(450 * i.KYVE)) // check voter status _, voterActive := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_1) @@ -649,4 +713,366 @@ var _ = Describe("invalid bundles", Ordered, func() { Expect(s.App().FundersKeeper.GetTotalActiveFunding(s.Ctx(), fundingState.PoolId)[0].Amount.Uint64()).To(Equal(100 * i.KYVE)) Expect(fundingState.ActiveFunderAddresses).To(HaveLen(1)) }) + + It("Produce an invalid bundle with multiple validators and stake fractions", func() { + // ARRANGE + s.CreateValidator(i.STAKER_0, "Staker-0", int64(200*i.KYVE)) + + s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0.5"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(100*i.KYVE)) + + s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CommitAfterSeconds(60) + + s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{ + Creator: i.VALADDRESS_0_A, + Staker: i.STAKER_0, + PoolId: 0, + StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI", + DataSize: 100, + DataHash: "test_hash", + FromIndex: 0, + BundleSize: 100, + FromKey: "0", + ToKey: "99", + BundleSummary: "test_value", + }) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(100*i.KYVE)) + + s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + initialBalanceStaker0 = s.GetBalanceFromAddress(i.STAKER_0) + initialBalanceValaddress0 = s.GetBalanceFromAddress(i.VALADDRESS_0_A) + + initialBalanceStaker1 = s.GetBalanceFromAddress(i.STAKER_1) + initialBalanceValaddress1 = s.GetBalanceFromAddress(i.VALADDRESS_1_A) + + initialBalanceStaker2 = s.GetBalanceFromAddress(i.STAKER_2) + initialBalanceValaddress2 = s.GetBalanceFromAddress(i.VALADDRESS_2_A) + + // ACT + s.RunTxBundlesSuccess(&bundletypes.MsgVoteBundleProposal{ + Creator: i.VALADDRESS_1_A, + Staker: i.STAKER_1, + PoolId: 0, + StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI", + Vote: bundletypes.VOTE_TYPE_INVALID, + }) + + s.RunTxBundlesSuccess(&bundletypes.MsgVoteBundleProposal{ + Creator: i.VALADDRESS_2_A, + Staker: i.STAKER_2, + PoolId: 0, + StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI", + Vote: bundletypes.VOTE_TYPE_INVALID, + }) + + s.CommitAfterSeconds(60) + + s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{ + Creator: i.VALADDRESS_0_A, + Staker: i.STAKER_0, + PoolId: 0, + StorageId: "P9edn0bjEfMU_lecFDIPLvGO2v2ltpFNUMWp5kgPddg", + DataSize: 100, + DataHash: "test_hash2", + FromIndex: 100, + BundleSize: 100, + FromKey: "100", + ToKey: "199", + BundleSummary: "test_value2", + }) + + // ASSERT + // check if bundle got not finalized on pool + pool, poolFound := s.App().PoolKeeper.GetPool(s.Ctx(), 0) + Expect(poolFound).To(BeTrue()) + + Expect(pool.CurrentKey).To(Equal("")) + Expect(pool.CurrentSummary).To(BeEmpty()) + Expect(pool.CurrentIndex).To(BeZero()) + Expect(pool.TotalBundles).To(BeZero()) + + // check if finalized bundle exists + _, finalizedBundleFound := s.App().BundlesKeeper.GetFinalizedBundle(s.Ctx(), 0, 0) + Expect(finalizedBundleFound).To(BeFalse()) + + // check if bundle proposal got dropped + bundleProposal, bundleProposalFound := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0) + Expect(bundleProposalFound).To(BeTrue()) + + Expect(bundleProposal.PoolId).To(Equal(uint64(0))) + Expect(bundleProposal.StorageId).To(BeEmpty()) + Expect(bundleProposal.Uploader).To(BeEmpty()) + Expect(bundleProposal.NextUploader).NotTo(BeEmpty()) + Expect(bundleProposal.DataSize).To(BeZero()) + Expect(bundleProposal.DataHash).To(BeEmpty()) + Expect(bundleProposal.BundleSize).To(BeZero()) + Expect(bundleProposal.FromKey).To(BeEmpty()) + Expect(bundleProposal.ToKey).To(BeEmpty()) + Expect(bundleProposal.BundleSummary).To(BeEmpty()) + Expect(bundleProposal.UpdatedAt).NotTo(BeZero()) + Expect(bundleProposal.VotersValid).To(BeEmpty()) + Expect(bundleProposal.VotersInvalid).To(BeEmpty()) + Expect(bundleProposal.VotersAbstain).To(BeEmpty()) + + // check uploader status + _, uploaderActive := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) + Expect(uploaderActive).To(BeFalse()) + + balanceValaddress := s.GetBalanceFromAddress(i.VALADDRESS_0_A) + Expect(balanceValaddress).To(Equal(initialBalanceValaddress0)) + + balanceUploader := s.GetBalanceFromAddress(i.STAKER_0) + + _, uploaderFound := s.App().StakersKeeper.GetValidator(s.Ctx(), i.STAKER_0) + Expect(uploaderFound).To(BeTrue()) + + Expect(balanceUploader).To(Equal(initialBalanceStaker0)) + Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_0, i.STAKER_0)).To(BeEmpty()) + + // calculate uploader slashes + fraction := s.App().StakersKeeper.GetUploadSlash(s.Ctx()) + slashAmount := uint64(math.LegacyNewDec(int64(100 * i.KYVE)).Mul(fraction).TruncateInt64()) + + Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_0, i.STAKER_0)).To(Equal(200*i.KYVE - slashAmount)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + + // check voter status + valaccountVoter, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_1) + Expect(valaccountVoter.Points).To(BeZero()) + + balanceVoterValaddress := s.GetBalanceFromAddress(valaccountVoter.Valaddress) + Expect(balanceVoterValaddress).To(Equal(initialBalanceValaddress1)) + + balanceVoter := s.GetBalanceFromAddress(valaccountVoter.Staker) + + Expect(balanceVoter).To(Equal(initialBalanceStaker1)) + Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_1, i.STAKER_1)).To(BeEmpty()) + + // check voter 2 status + valaccountVoter, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_2) + Expect(valaccountVoter.Points).To(BeZero()) + + balanceVoterValaddress = s.GetBalanceFromAddress(valaccountVoter.Valaddress) + Expect(balanceVoterValaddress).To(Equal(initialBalanceValaddress1)) + + balanceVoter = s.GetBalanceFromAddress(valaccountVoter.Staker) + + Expect(balanceVoter).To(Equal(initialBalanceStaker1)) + Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_2, i.STAKER_2)).To(BeEmpty()) + + // check pool funds + fundingState, _ := s.App().FundersKeeper.GetFundingState(s.Ctx(), 0) + + // assert total pool funds + Expect(s.App().FundersKeeper.GetTotalActiveFunding(s.Ctx(), fundingState.PoolId)[0].Amount.Uint64()).To(Equal(100 * i.KYVE)) + Expect(fundingState.ActiveFunderAddresses).To(HaveLen(1)) + }) + + It("Produce an invalid bundle with multiple validators and stake fractions and foreign delegations", func() { + // ARRANGE + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) + + s.RunTxSuccess(stakingTypes.NewMsgDelegate( + i.CHARLIE, + util.MustValaddressFromOperatorAddress(i.STAKER_0), + sdk.NewInt64Coin(globalTypes.Denom, int64(100*i.KYVE)), + )) + + s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0.5"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(100*i.KYVE)) + + s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CommitAfterSeconds(60) + + s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{ + Creator: i.VALADDRESS_0_A, + Staker: i.STAKER_0, + PoolId: 0, + StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI", + DataSize: 100, + DataHash: "test_hash", + FromIndex: 0, + BundleSize: 100, + FromKey: "0", + ToKey: "99", + BundleSummary: "test_value", + }) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(100*i.KYVE)) + + s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + initialBalanceStaker0 = s.GetBalanceFromAddress(i.STAKER_0) + initialBalanceValaddress0 = s.GetBalanceFromAddress(i.VALADDRESS_0_A) + + initialBalanceStaker1 = s.GetBalanceFromAddress(i.STAKER_1) + initialBalanceValaddress1 = s.GetBalanceFromAddress(i.VALADDRESS_1_A) + + initialBalanceStaker2 = s.GetBalanceFromAddress(i.STAKER_2) + initialBalanceValaddress2 = s.GetBalanceFromAddress(i.VALADDRESS_2_A) + + // ACT + s.RunTxBundlesSuccess(&bundletypes.MsgVoteBundleProposal{ + Creator: i.VALADDRESS_1_A, + Staker: i.STAKER_1, + PoolId: 0, + StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI", + Vote: bundletypes.VOTE_TYPE_INVALID, + }) + + s.RunTxBundlesSuccess(&bundletypes.MsgVoteBundleProposal{ + Creator: i.VALADDRESS_2_A, + Staker: i.STAKER_2, + PoolId: 0, + StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI", + Vote: bundletypes.VOTE_TYPE_INVALID, + }) + + s.CommitAfterSeconds(60) + + s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{ + Creator: i.VALADDRESS_0_A, + Staker: i.STAKER_0, + PoolId: 0, + StorageId: "P9edn0bjEfMU_lecFDIPLvGO2v2ltpFNUMWp5kgPddg", + DataSize: 100, + DataHash: "test_hash2", + FromIndex: 100, + BundleSize: 100, + FromKey: "100", + ToKey: "199", + BundleSummary: "test_value2", + }) + + // ASSERT + // check if bundle got not finalized on pool + pool, poolFound := s.App().PoolKeeper.GetPool(s.Ctx(), 0) + Expect(poolFound).To(BeTrue()) + + Expect(pool.CurrentKey).To(Equal("")) + Expect(pool.CurrentSummary).To(BeEmpty()) + Expect(pool.CurrentIndex).To(BeZero()) + Expect(pool.TotalBundles).To(BeZero()) + + // check if finalized bundle exists + _, finalizedBundleFound := s.App().BundlesKeeper.GetFinalizedBundle(s.Ctx(), 0, 0) + Expect(finalizedBundleFound).To(BeFalse()) + + // check if bundle proposal got dropped + bundleProposal, bundleProposalFound := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0) + Expect(bundleProposalFound).To(BeTrue()) + + Expect(bundleProposal.PoolId).To(Equal(uint64(0))) + Expect(bundleProposal.StorageId).To(BeEmpty()) + Expect(bundleProposal.Uploader).To(BeEmpty()) + Expect(bundleProposal.NextUploader).NotTo(BeEmpty()) + Expect(bundleProposal.DataSize).To(BeZero()) + Expect(bundleProposal.DataHash).To(BeEmpty()) + Expect(bundleProposal.BundleSize).To(BeZero()) + Expect(bundleProposal.FromKey).To(BeEmpty()) + Expect(bundleProposal.ToKey).To(BeEmpty()) + Expect(bundleProposal.BundleSummary).To(BeEmpty()) + Expect(bundleProposal.UpdatedAt).NotTo(BeZero()) + Expect(bundleProposal.VotersValid).To(BeEmpty()) + Expect(bundleProposal.VotersInvalid).To(BeEmpty()) + Expect(bundleProposal.VotersAbstain).To(BeEmpty()) + + // check uploader status + _, uploaderActive := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) + Expect(uploaderActive).To(BeFalse()) + + balanceValaddress := s.GetBalanceFromAddress(i.VALADDRESS_0_A) + Expect(balanceValaddress).To(Equal(initialBalanceValaddress0)) + + balanceUploader := s.GetBalanceFromAddress(i.STAKER_0) + + _, uploaderFound := s.App().StakersKeeper.GetValidator(s.Ctx(), i.STAKER_0) + Expect(uploaderFound).To(BeTrue()) + + Expect(balanceUploader).To(Equal(initialBalanceStaker0)) + Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_0, i.STAKER_0)).To(BeEmpty()) + + // calculate uploader slashes (stake fraction is 0.5 because only 100 kyve out of 200 are at risk) + fraction := s.App().StakersKeeper.GetUploadSlash(s.Ctx()).Mul(math.LegacyMustNewDecFromStr("0.5")) + slashAmount := uint64(math.LegacyNewDec(int64(100 * i.KYVE)).Mul(fraction).TruncateInt64()) + slashAmountDelegator := uint64(math.LegacyNewDec(int64(100 * i.KYVE)).Mul(fraction).TruncateInt64()) + + Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_0, i.STAKER_0)).To(Equal(100*i.KYVE - slashAmount)) + Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_0, i.CHARLIE)).To(Equal(100*i.KYVE - slashAmountDelegator)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + + // check voter status + valaccountVoter, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_1) + Expect(valaccountVoter.Points).To(BeZero()) + + balanceVoterValaddress := s.GetBalanceFromAddress(valaccountVoter.Valaddress) + Expect(balanceVoterValaddress).To(Equal(initialBalanceValaddress1)) + + balanceVoter := s.GetBalanceFromAddress(valaccountVoter.Staker) + + Expect(balanceVoter).To(Equal(initialBalanceStaker1)) + Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_1, i.STAKER_1)).To(BeEmpty()) + + // check voter 2 status + valaccountVoter, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_2) + Expect(valaccountVoter.Points).To(BeZero()) + + balanceVoterValaddress = s.GetBalanceFromAddress(valaccountVoter.Valaddress) + Expect(balanceVoterValaddress).To(Equal(initialBalanceValaddress1)) + + balanceVoter = s.GetBalanceFromAddress(valaccountVoter.Staker) + + Expect(balanceVoter).To(Equal(initialBalanceStaker1)) + Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_2, i.STAKER_2)).To(BeEmpty()) + + // check pool funds + fundingState, _ := s.App().FundersKeeper.GetFundingState(s.Ctx(), 0) + + // assert total pool funds + Expect(s.App().FundersKeeper.GetTotalActiveFunding(s.Ctx(), fundingState.PoolId)[0].Amount.Uint64()).To(Equal(100 * i.KYVE)) + Expect(fundingState.ActiveFunderAddresses).To(HaveLen(1)) + }) }) diff --git a/x/bundles/keeper/keeper_suite_stakers_leave_test.go b/x/bundles/keeper/keeper_suite_stakers_leave_test.go index 29ee4340..f42c26d1 100644 --- a/x/bundles/keeper/keeper_suite_stakers_leave_test.go +++ b/x/bundles/keeper/keeper_suite_stakers_leave_test.go @@ -140,7 +140,7 @@ var _ = Describe("stakers leave", Ordered, func() { _, valaccountActive := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccountActive).To(BeFalse()) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) // check if next uploader got not slashed Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_0, i.STAKER_0)).To(Equal(100 * i.KYVE)) @@ -228,7 +228,7 @@ var _ = Describe("stakers leave", Ordered, func() { _, valaccountActive := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccountActive).To(BeFalse()) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) // check if next uploader got not slashed Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_0, i.STAKER_0)).To(Equal(100 * i.KYVE)) @@ -344,7 +344,7 @@ var _ = Describe("stakers leave", Ordered, func() { slashAmount := uint64(math.LegacyNewDec(int64(100 * i.KYVE)).Mul(fraction).TruncateInt64()) Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_0, i.STAKER_0)).To(Equal(100*i.KYVE - slashAmount)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) // check if next uploader did not receive the uploader reward balanceUploader := s.GetBalanceFromAddress(i.STAKER_0) @@ -444,7 +444,7 @@ var _ = Describe("stakers leave", Ordered, func() { slashAmount := uint64(math.LegacyNewDec(int64(100 * i.KYVE)).Mul(fraction).TruncateInt64()) Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_1, i.STAKER_1)).To(Equal(100*i.KYVE - slashAmount)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) // check if next uploader did not receive any rewards balanceVoter := s.GetBalanceFromAddress(i.STAKER_1) @@ -536,7 +536,7 @@ var _ = Describe("stakers leave", Ordered, func() { // check if voter status Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_1, i.STAKER_1)).To(Equal(100 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) // check if next uploader did not receive any rewards balanceVoter := s.GetBalanceFromAddress(i.STAKER_1) @@ -631,7 +631,7 @@ var _ = Describe("stakers leave", Ordered, func() { // check if voter not got slashed Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_1, i.STAKER_1)).To(Equal(100 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) // check if next uploader did not receive any rewards balanceVoter := s.GetBalanceFromAddress(i.STAKER_1) diff --git a/x/bundles/keeper/keeper_suite_valid_bundles_test.go b/x/bundles/keeper/keeper_suite_valid_bundles_test.go index b3de63d4..addc5630 100644 --- a/x/bundles/keeper/keeper_suite_valid_bundles_test.go +++ b/x/bundles/keeper/keeper_suite_valid_bundles_test.go @@ -25,6 +25,7 @@ TEST CASES - valid bundles * Produce a valid bundle with multiple validators and foreign delegation although some did not vote at all * Produce a valid bundle with multiple validators and foreign delegation although some voted abstain * Produce a valid bundle with multiple validators and foreign delegation although some voted invalid +* Produce a valid bundle with multiple validators and foreign delegation although some voted invalid with maximum voting power * Produce a valid bundle with multiple validators and no foreign delegations and another storage provider * Produce a valid bundle with multiple validators, multiple coins and no foreign delegations * Produce a valid bundle with multiple validators, multiple coins which are not enough for the storage reward and no foreign delegations @@ -922,7 +923,7 @@ var _ = Describe("valid bundles", Ordered, func() { Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_2, i.STAKER_2)).To(Equal(100*i.KYVE - slashAmountVoter)) Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_2, i.CHARLIE)).To(Equal(300*i.KYVE - slashAmountDelegator)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(800 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(800 * i.KYVE)) // check voter status _, voterActive := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_2) @@ -963,6 +964,207 @@ var _ = Describe("valid bundles", Ordered, func() { Expect(fundingState.ActiveFunderAddresses).To(HaveLen(1)) }) + It("Produce a valid bundle with multiple validators and foreign delegation although some voted invalid with maximum voting power", func() { + // ARRANGE + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("0.4") + s.App().PoolKeeper.SetParams(s.Ctx(), params) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(100*i.KYVE)) + + s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + // delegate to staker 2 so he runs into the max voting power limit + s.RunTxSuccess(stakingTypes.NewMsgDelegate( + i.CHARLIE, + util.MustValaddressFromOperatorAddress(i.STAKER_2), + sdk.NewInt64Coin(globalTypes.Denom, int64(300*i.KYVE)), + )) + + s.CreateValidator(i.STAKER_3, "Staker-3", int64(100*i.KYVE)) + + s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ + Creator: i.STAKER_3, + PoolId: 0, + Valaddress: i.VALADDRESS_3_A, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.RunTxBundlesSuccess(&bundletypes.MsgClaimUploaderRole{ + Creator: i.VALADDRESS_0_A, + Staker: i.STAKER_0, + PoolId: 0, + }) + + s.CommitAfterSeconds(60) + + s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{ + Creator: i.VALADDRESS_0_A, + Staker: i.STAKER_0, + PoolId: 0, + StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI", + DataSize: 100, + DataHash: "test_hash", + FromIndex: 0, + BundleSize: 100, + FromKey: "0", + ToKey: "99", + BundleSummary: "test_value", + }) + + s.RunTxBundlesSuccess(&bundletypes.MsgVoteBundleProposal{ + Creator: i.VALADDRESS_1_A, + Staker: i.STAKER_1, + PoolId: 0, + StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI", + Vote: bundletypes.VOTE_TYPE_VALID, + }) + + s.RunTxBundlesSuccess(&bundletypes.MsgVoteBundleProposal{ + Creator: i.VALADDRESS_2_A, + Staker: i.STAKER_2, + PoolId: 0, + StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI", + Vote: bundletypes.VOTE_TYPE_INVALID, + }) + + s.RunTxBundlesSuccess(&bundletypes.MsgVoteBundleProposal{ + Creator: i.VALADDRESS_3_A, + Staker: i.STAKER_3, + PoolId: 0, + StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI", + Vote: bundletypes.VOTE_TYPE_VALID, + }) + + initialBalanceStaker2 = s.GetCoinsFromAddress(i.STAKER_2) + initialBalanceValaddress2 = s.GetCoinsFromAddress(i.VALADDRESS_2_A) + + c1 := s.GetCoinsFromCommunityPool() + + s.CommitAfterSeconds(60) + + // ACT + // TODO: why is staker 2 selected as next uploader? + s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{ + Creator: i.VALADDRESS_2_A, + Staker: i.STAKER_2, + PoolId: 0, + StorageId: "P9edn0bjEfMU_lecFDIPLvGO2v2ltpFNUMWp5kgPddg", + DataSize: 100, + DataHash: "test_hash2", + FromIndex: 100, + BundleSize: 100, + FromKey: "100", + ToKey: "199", + BundleSummary: "test_value2", + }) + + // ASSERT + // check if bundle got finalized on pool + pool, poolFound := s.App().PoolKeeper.GetPool(s.Ctx(), 0) + Expect(poolFound).To(BeTrue()) + + Expect(pool.CurrentKey).To(Equal("99")) + Expect(pool.CurrentSummary).To(Equal("test_value")) + Expect(pool.CurrentIndex).To(Equal(uint64(100))) + Expect(pool.TotalBundles).To(Equal(uint64(1))) + + // check if finalized bundle got saved + finalizedBundle, finalizedBundleFound := s.App().BundlesKeeper.GetFinalizedBundle(s.Ctx(), 0, 0) + Expect(finalizedBundleFound).To(BeTrue()) + + Expect(finalizedBundle.PoolId).To(Equal(uint64(0))) + Expect(finalizedBundle.StorageId).To(Equal("y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI")) + Expect(finalizedBundle.Uploader).To(Equal(i.STAKER_0)) + Expect(finalizedBundle.FromIndex).To(Equal(uint64(0))) + Expect(finalizedBundle.ToIndex).To(Equal(uint64(100))) + Expect(finalizedBundle.FromKey).To(Equal("0")) + Expect(finalizedBundle.ToKey).To(Equal("99")) + Expect(finalizedBundle.BundleSummary).To(Equal("test_value")) + Expect(finalizedBundle.DataHash).To(Equal("test_hash")) + Expect(finalizedBundle.FinalizedAt).NotTo(BeZero()) + Expect(finalizedBundle.StakeSecurity.ValidVotePower).To(Equal(300 * i.KYVE)) + Expect(finalizedBundle.StakeSecurity.TotalVotePower).To(Equal(500*i.KYVE + 1)) + + // check if next bundle proposal got registered + bundleProposal, bundleProposalFound := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0) + Expect(bundleProposalFound).To(BeTrue()) + + Expect(bundleProposal.PoolId).To(Equal(uint64(0))) + Expect(bundleProposal.StorageId).To(Equal("P9edn0bjEfMU_lecFDIPLvGO2v2ltpFNUMWp5kgPddg")) + Expect(bundleProposal.Uploader).To(Equal(i.STAKER_2)) + Expect(bundleProposal.NextUploader).To(Equal(i.STAKER_3)) + Expect(bundleProposal.DataSize).To(Equal(uint64(100))) + Expect(bundleProposal.DataHash).To(Equal("test_hash2")) + Expect(bundleProposal.BundleSize).To(Equal(uint64(100))) + Expect(bundleProposal.FromKey).To(Equal("100")) + Expect(bundleProposal.ToKey).To(Equal("199")) + Expect(bundleProposal.BundleSummary).To(Equal("test_value2")) + Expect(bundleProposal.UpdatedAt).NotTo(BeZero()) + Expect(bundleProposal.VotersValid).To(ContainElement(i.STAKER_2)) + Expect(bundleProposal.VotersInvalid).To(BeEmpty()) + Expect(bundleProposal.VotersAbstain).To(BeEmpty()) + + // check uploader status + valaccountUploader, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) + Expect(valaccountUploader.Points).To(BeZero()) + + balanceUploaderValaddress := s.GetCoinsFromAddress(valaccountUploader.Valaddress) + Expect(balanceUploaderValaddress).To(Equal(initialBalanceValaddress0)) + + // calculate voter slashes (due to maximum vote power only 200 kyve out of 400 where at risk for slashing) + fraction := s.App().StakersKeeper.GetVoteSlash(s.Ctx()).Mul(math.LegacyMustNewDecFromStr("0.5")) // 200 / 400 + slashAmountVoter := uint64(math.LegacyNewDec(int64(100 * i.KYVE)).Mul(fraction).TruncateInt64()) + slashAmountDelegator := uint64(math.LegacyNewDec(int64(300 * i.KYVE)).Mul(fraction).TruncateInt64()) + + Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_2, i.STAKER_2)).To(Equal(100*i.KYVE - slashAmountVoter)) + Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_2, i.CHARLIE)).To(Equal(300*i.KYVE - slashAmountDelegator)) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(300 * i.KYVE)) + + // check voter status + _, voterActive := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_2) + Expect(voterActive).To(BeFalse()) + + balanceVoterValaddress := s.GetCoinsFromAddress(i.VALADDRESS_2_A) + Expect(balanceVoterValaddress).To(Equal(initialBalanceValaddress2)) + + balanceVoter := s.GetCoinsFromAddress(i.STAKER_2) + Expect(balanceVoter).To(Equal(initialBalanceStaker2)) + + // check uploader rewards + balanceUploader := s.GetCoinsFromAddress(valaccountUploader.Staker) + + // assert payout transfer + Expect(balanceUploader.String()).To(Equal(initialBalanceStaker0.String())) + // assert commission rewards + // (10_000 - (10_000 * 0.01) - (100 * 0.5)) * 0.1 + (100 * 0.5) + Expect(s.App().StakersKeeper.GetOutstandingCommissionRewards(s.Ctx(), valaccountUploader.Staker).String()).To(Equal(i.ACoins(1035).String())) + // assert uploader self delegation rewards + // (10_000 - (10_000 * 0.01) - (100 * 0.5)) * (1 - 0.1) + Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_0, i.STAKER_0).String()).To(Equal(i.ACoins(8865).String())) + + // check voter rewards + Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_2, i.CHARLIE)).To(BeEmpty()) + + fundingState, _ := s.App().FundersKeeper.GetFundingState(s.Ctx(), 0) + + // assert treasury payout + c2 := s.GetCoinsFromCommunityPool() + Expect(c2.Sub(c1...).AmountOf(i.A_DENOM).Uint64()).To(Equal(uint64(100))) + + // assert total pool funds + Expect(s.App().FundersKeeper.GetTotalActiveFunding(s.Ctx(), fundingState.PoolId).String()).To(Equal(i.ACoins(100*i.T_KYVE - amountPerBundle).String())) + Expect(fundingState.ActiveFunderAddresses).To(HaveLen(1)) + }) + It("Produce a valid bundle with multiple validators and no foreign delegations and another storage provider", func() { // ARRANGE storageProviderId := uint32(2) diff --git a/x/bundles/keeper/keeper_suite_zero_delegation_test.go b/x/bundles/keeper/keeper_suite_zero_delegation_test.go index 757de27d..7a8585f5 100644 --- a/x/bundles/keeper/keeper_suite_zero_delegation_test.go +++ b/x/bundles/keeper/keeper_suite_zero_delegation_test.go @@ -291,7 +291,7 @@ var _ = Describe("zero delegation", Ordered, func() { slashAmountVoter := uint64(math.LegacyNewDec(int64(0 * i.KYVE)).Mul(fraction).TruncateInt64()) Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_2, i.STAKER_2)).To(Equal(0*i.KYVE - slashAmountVoter)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200*i.KYVE - slashAmountVoter)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200*i.KYVE - slashAmountVoter)) }) It("Staker submit bundle proposal with zero delegation", func() { @@ -329,11 +329,10 @@ var _ = Describe("zero delegation", Ordered, func() { StakeFraction: math.LegacyMustNewDecFromStr("1"), }) - s.RunTxBundlesSuccess(&bundletypes.MsgClaimUploaderRole{ - Creator: i.VALADDRESS_0_A, - Staker: i.STAKER_0, - PoolId: 0, - }) + // manually set next uploader + bundleProposal, _ := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0) + bundleProposal.NextUploader = i.STAKER_0 + s.App().BundlesKeeper.SetBundleProposal(s.Ctx(), bundleProposal) s.CommitAfterSeconds(60) @@ -422,7 +421,7 @@ var _ = Describe("zero delegation", Ordered, func() { Expect(bundleProposal.PoolId).To(Equal(uint64(0))) Expect(bundleProposal.StorageId).To(Equal("P9edn0bjEfMU_lecFDIPLvGO2v2ltpFNUMWp5kgPddg")) Expect(bundleProposal.Uploader).To(Equal(i.STAKER_1)) - Expect(bundleProposal.NextUploader).To(Equal(i.STAKER_2)) + Expect(bundleProposal.NextUploader).To(Equal(i.STAKER_1)) Expect(bundleProposal.DataSize).To(Equal(uint64(100))) Expect(bundleProposal.DataHash).To(Equal("test_hash2")) Expect(bundleProposal.BundleSize).To(Equal(uint64(100))) @@ -508,11 +507,10 @@ var _ = Describe("zero delegation", Ordered, func() { StakeFraction: math.LegacyMustNewDecFromStr("1"), }) - s.RunTxBundlesSuccess(&bundletypes.MsgClaimUploaderRole{ - Creator: i.VALADDRESS_0_A, - Staker: i.STAKER_0, - PoolId: 0, - }) + // manually set next uploader + bundleProposal, _ := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0) + bundleProposal.NextUploader = i.STAKER_0 + s.App().BundlesKeeper.SetBundleProposal(s.Ctx(), bundleProposal) s.CommitAfterSeconds(60) @@ -621,7 +619,7 @@ var _ = Describe("zero delegation", Ordered, func() { slashAmount := uint64(math.LegacyNewDec(int64(0 * i.KYVE)).Mul(fraction).TruncateInt64()) Expect(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_0, i.STAKER_0)).To(Equal(0*i.KYVE - slashAmount)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200*i.KYVE - slashAmount)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200*i.KYVE - slashAmount)) // check voter status valaccountVoter, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_1) @@ -677,11 +675,10 @@ var _ = Describe("zero delegation", Ordered, func() { StakeFraction: math.LegacyMustNewDecFromStr("1"), }) - s.RunTxBundlesSuccess(&bundletypes.MsgClaimUploaderRole{ - Creator: i.VALADDRESS_0_A, - Staker: i.STAKER_0, - PoolId: 0, - }) + // manually set next uploader + bundleProposal, _ := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0) + bundleProposal.NextUploader = i.STAKER_0 + s.App().BundlesKeeper.SetBundleProposal(s.Ctx(), bundleProposal) s.CommitAfterSeconds(60) @@ -784,15 +781,14 @@ var _ = Describe("zero delegation", Ordered, func() { StakeFraction: math.LegacyMustNewDecFromStr("1"), }) - s.RunTxBundlesSuccess(&bundletypes.MsgClaimUploaderRole{ - Creator: i.VALADDRESS_0_A, - Staker: i.STAKER_0, - PoolId: 0, - }) + // manually set next uploader + bundleProposal, _ := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0) + bundleProposal.NextUploader = i.STAKER_0 + s.App().BundlesKeeper.SetBundleProposal(s.Ctx(), bundleProposal) s.CommitAfterSeconds(60) - s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{ + s.RunTxBundlesError(&bundletypes.MsgSubmitBundleProposal{ Creator: i.VALADDRESS_0_A, Staker: i.STAKER_0, PoolId: 0, @@ -805,93 +801,5 @@ var _ = Describe("zero delegation", Ordered, func() { ToKey: "99", BundleSummary: "test_value", }) - - s.RunTxBundlesSuccess(&bundletypes.MsgVoteBundleProposal{ - Creator: i.VALADDRESS_1_A, - Staker: i.STAKER_1, - PoolId: 0, - StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI", - Vote: bundletypes.VOTE_TYPE_VALID, - }) - - initialBalanceStaker0 = s.GetBalanceFromAddress(i.STAKER_0) - initialBalanceValaddress0 = s.GetBalanceFromAddress(i.VALADDRESS_0_A) - - initialBalanceStaker1 = s.GetBalanceFromAddress(i.STAKER_1) - initialBalanceValaddress1 = s.GetBalanceFromAddress(i.VALADDRESS_1_A) - - // manually set next staker - bundleProposal, _ := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0) - Expect(bundleProposal.NextUploader).To(BeEmpty()) - bundleProposal.NextUploader = i.STAKER_1 - s.App().BundlesKeeper.SetBundleProposal(s.Ctx(), bundleProposal) - - // ACT - s.CommitAfterSeconds(60) - s.CommitAfterSeconds(1) - - // ASSERT - // check if bundle got not finalized on pool - pool, poolFound := s.App().PoolKeeper.GetPool(s.Ctx(), 0) - Expect(poolFound).To(BeTrue()) - - Expect(pool.CurrentKey).To(Equal("")) - Expect(pool.CurrentSummary).To(BeEmpty()) - Expect(pool.CurrentIndex).To(BeZero()) - Expect(pool.TotalBundles).To(BeZero()) - - // check if finalized bundle exists - _, finalizedBundleFound := s.App().BundlesKeeper.GetFinalizedBundle(s.Ctx(), 0, 0) - Expect(finalizedBundleFound).To(BeFalse()) - - // check if bundle proposal got dropped - bundleProposal, bundleProposalFound := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0) - Expect(bundleProposalFound).To(BeTrue()) - - Expect(bundleProposal.PoolId).To(Equal(uint64(0))) - Expect(bundleProposal.StorageId).To(BeEmpty()) - Expect(bundleProposal.Uploader).To(BeEmpty()) - Expect(bundleProposal.NextUploader).To(BeEmpty()) - Expect(bundleProposal.DataSize).To(BeZero()) - Expect(bundleProposal.DataHash).To(BeEmpty()) - Expect(bundleProposal.BundleSize).To(BeZero()) - Expect(bundleProposal.FromKey).To(BeEmpty()) - Expect(bundleProposal.ToKey).To(BeEmpty()) - Expect(bundleProposal.BundleSummary).To(BeEmpty()) - Expect(bundleProposal.UpdatedAt).NotTo(BeZero()) - Expect(bundleProposal.VotersValid).To(BeEmpty()) - Expect(bundleProposal.VotersInvalid).To(BeEmpty()) - Expect(bundleProposal.VotersAbstain).To(BeEmpty()) - - // check uploader status - valaccountUploader, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) - Expect(valaccountUploader.Points).To(BeZero()) - - balanceValaddress := s.GetBalanceFromAddress(valaccountUploader.Valaddress) - Expect(balanceValaddress).To(Equal(initialBalanceValaddress0)) - - balanceUploader := s.GetBalanceFromAddress(valaccountUploader.Staker) - - Expect(balanceUploader).To(Equal(initialBalanceStaker0)) - Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_0, i.STAKER_0)).To(BeEmpty()) - - // check voter status - valaccountVoter, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_1) - Expect(valaccountVoter.Points).To(BeZero()) - - balanceVoterValaddress := s.GetBalanceFromAddress(valaccountVoter.Valaddress) - Expect(balanceVoterValaddress).To(Equal(initialBalanceValaddress1)) - - balanceVoter := s.GetBalanceFromAddress(valaccountVoter.Staker) - Expect(balanceVoter).To(Equal(initialBalanceStaker1)) - - Expect(balanceVoter).To(Equal(initialBalanceStaker1)) - Expect(s.App().StakersKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_1, i.STAKER_1)).To(BeEmpty()) - - fundingState, _ := s.App().FundersKeeper.GetFundingState(s.Ctx(), 0) - - // assert total pool funds - Expect(s.App().FundersKeeper.GetTotalActiveFunding(s.Ctx(), fundingState.PoolId)[0].Amount.Uint64()).To(Equal(100 * i.KYVE)) - Expect(fundingState.ActiveFunderAddresses).To(HaveLen(1)) }) }) diff --git a/x/bundles/keeper/logic_bundles.go b/x/bundles/keeper/logic_bundles.go index 1a6c68ea..e2a18130 100644 --- a/x/bundles/keeper/logic_bundles.go +++ b/x/bundles/keeper/logic_bundles.go @@ -3,12 +3,11 @@ package keeper import ( "cosmossdk.io/errors" "cosmossdk.io/math" + "github.com/KYVENetwork/chain/util" + "github.com/KYVENetwork/chain/x/bundles/types" globalTypes "github.com/KYVENetwork/chain/x/global/types" poolTypes "github.com/KYVENetwork/chain/x/pool/types" stakersTypes "github.com/KYVENetwork/chain/x/stakers/types" - - "github.com/KYVENetwork/chain/util" - "github.com/KYVENetwork/chain/x/bundles/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -37,22 +36,17 @@ func (k Keeper) AssertPoolCanRun(ctx sdk.Context, poolId uint64) error { return types.ErrEndKeyReached } - // Get the total and the highest delegation of a single validator in the pool - totalDelegation, highestDelegation := k.stakerKeeper.GetTotalAndHighestDelegationOfPool(ctx, poolId) + // Error if max voting power is not achievable because there are not enough stakers + // in the pool + if k.stakerKeeper.IsVotingPowerTooHigh(ctx, poolId) { + return types.ErrVotingPowerTooHigh + } // Error if min delegation is not reached - if totalDelegation < pool.MinDelegation { + if k.stakerKeeper.GetTotalStakeOfPool(ctx, poolId) < pool.MinDelegation { return types.ErrMinDelegationNotReached } - maxVotingPower := k.poolKeeper.GetMaxVotingPowerPerPool(ctx) - maxDelegation := uint64(maxVotingPower.MulInt64(int64(totalDelegation)).TruncateInt64()) - - // Error if highest delegation exceeds max voting power - if highestDelegation > maxDelegation { - return types.ErrVotingPowerTooHigh - } - return nil } @@ -464,14 +458,6 @@ func (k Keeper) dropCurrentBundleProposal(ctx sdk.Context, poolId uint64, voteDi k.SetBundleProposal(ctx, bundleProposal) } -// calculateVotingPower calculates the voting power one staker has in a -// storage pool based only on the total delegation this staker has -func (k Keeper) calculateVotingPower(delegation uint64) (votingPower uint64) { - // voting power is linear - votingPower = delegation - return -} - // chooseNextUploader selects the next uploader based on a fixed set of stakers in a pool. // It is guaranteed that someone is chosen deterministically if the round-robin set itself is not empty. func (k Keeper) chooseNextUploader(ctx sdk.Context, poolId uint64, excluded ...string) (nextUploader string) { @@ -511,28 +497,26 @@ func (k Keeper) GetVoteDistribution(ctx sdk.Context, poolId uint64) (voteDistrib return } + stakes := k.stakerKeeper.GetValidatorPoolStakes(ctx, poolId) + // get voting power for valid for _, voter := range bundleProposal.VotersValid { - delegation := k.stakerKeeper.GetValidatorPoolStake(ctx, voter, poolId) - voteDistribution.Valid += k.calculateVotingPower(delegation) + voteDistribution.Valid += stakes[voter] } // get voting power for invalid for _, voter := range bundleProposal.VotersInvalid { - delegation := k.stakerKeeper.GetValidatorPoolStake(ctx, voter, poolId) - voteDistribution.Invalid += k.calculateVotingPower(delegation) + voteDistribution.Invalid += stakes[voter] } // get voting power for abstain for _, voter := range bundleProposal.VotersAbstain { - delegation := k.stakerKeeper.GetValidatorPoolStake(ctx, voter, poolId) - voteDistribution.Abstain += k.calculateVotingPower(delegation) + voteDistribution.Abstain += stakes[voter] } // get total voting power for _, staker := range k.stakerKeeper.GetAllStakerAddressesOfPool(ctx, poolId) { - delegation := k.stakerKeeper.GetValidatorPoolStake(ctx, staker, poolId) - voteDistribution.Total += k.calculateVotingPower(delegation) + voteDistribution.Total += stakes[staker] } if voteDistribution.Total == 0 { diff --git a/x/bundles/keeper/logic_bundles_test.go b/x/bundles/keeper/logic_bundles_test.go index 08d19811..9d3a0885 100644 --- a/x/bundles/keeper/logic_bundles_test.go +++ b/x/bundles/keeper/logic_bundles_test.go @@ -242,7 +242,7 @@ var _ = Describe("logic_bundles.go", Ordered, func() { err := s.App().BundlesKeeper.AssertPoolCanRun(s.Ctx(), 0) // ASSERT - Expect(err).To(Equal(bundlesTypes.ErrVotingPowerTooHigh)) + Expect(err).To(BeNil()) }) It("Assert pool can run while voting power of one node is 40%", func() { @@ -366,7 +366,7 @@ var _ = Describe("logic_bundles.go", Ordered, func() { err := s.App().BundlesKeeper.AssertPoolCanRun(s.Ctx(), 0) // ASSERT - Expect(err).To(Equal(bundlesTypes.ErrVotingPowerTooHigh)) + Expect(err).To(BeNil()) }) It("Assert pool can run with a single staker while voting power is 100%", func() { diff --git a/x/bundles/keeper/logic_end_block_handle_upload_timeout_test.go b/x/bundles/keeper/logic_end_block_handle_upload_timeout_test.go index ce19c849..bdb04f90 100644 --- a/x/bundles/keeper/logic_end_block_handle_upload_timeout_test.go +++ b/x/bundles/keeper/logic_end_block_handle_upload_timeout_test.go @@ -24,7 +24,7 @@ TEST CASES - logic_end_block_handle_upload_timeout.go * Next uploader gets removed due to pool upgrading * Next uploader gets removed due to pool being disabled * Next uploader gets removed due to pool not reaching min delegation -* Next uploader gets removed due to pool having one node with more than 50% voting power +* Next uploader gets not removed although pool having one node with more than 50% voting power * Staker is next uploader of genesis bundle and upload interval and timeout does not pass * Staker is next uploader of genesis bundle and upload timeout does not pass but upload interval passes * Staker is next uploader of genesis bundle and upload timeout does pass together with upload interval @@ -281,7 +281,7 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() { Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_1, 0)).To(Equal(20 * i.KYVE)) }) - It("Next uploader gets removed due to pool having one node with more than 50% voting power", func() { + It("Next uploader gets removed due to pool having validators with too much voting power", func() { // ARRANGE s.RunTxBundlesSuccess(&bundletypes.MsgClaimUploaderRole{ Creator: i.VALADDRESS_0_A, @@ -289,11 +289,9 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() { PoolId: 0, }) - s.RunTxSuccess(stakingTypes.NewMsgDelegate( - i.STAKER_0, - util.MustValaddressFromOperatorAddress(i.STAKER_0), - sdk.NewInt64Coin(globalTypes.Denom, int64(1*i.KYVE)), - )) + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("0.2") + s.App().PoolKeeper.SetParams(s.Ctx(), params) // ACT s.CommitAfterSeconds(1) @@ -308,11 +306,11 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() { _, found := s.App().StakersKeeper.GetValidator(s.Ctx(), i.STAKER_0) Expect(found).To(BeTrue()) - Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(101 * i.KYVE)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(BeZero()) _, found = s.App().StakersKeeper.GetValidator(s.Ctx(), i.STAKER_1) Expect(found).To(BeTrue()) - Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_1, 0)).To(Equal(100 * i.KYVE)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_1, 0)).To(BeZero()) }) It("Staker is next uploader of genesis bundle and upload interval and timeout does not pass", func() { @@ -401,7 +399,7 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() { _, found := s.App().StakersKeeper.GetValidator(s.Ctx(), i.STAKER_0) Expect(found).To(BeTrue()) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) // check if next uploader not got slashed expectedBalance := 100 * i.KYVE @@ -578,7 +576,7 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() { Expect(expectedBalance).To(Equal(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_1, i.STAKER_1))) // pool delegations equals delegations of staker 0 & 1 - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) }) It("Staker is next uploader of bundle proposal and upload timeout passes with the previous bundle not reaching quorum", func() { @@ -641,7 +639,7 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() { Expect(found).To(BeTrue()) // pool delegations equals delegations of staker 0 & 1 - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) }) It("Staker is next uploader of bundle proposal and upload timeout passes with the previous bundle being invalid", func() { @@ -697,7 +695,7 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() { Vote: bundletypes.VOTE_TYPE_INVALID, }) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(300 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(300 * i.KYVE)) // ACT s.CommitAfterSeconds(s.App().BundlesKeeper.GetUploadTimeout(s.Ctx())) @@ -739,7 +737,7 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() { Expect(expectedBalance).To(Equal(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_2, i.STAKER_2))) // pool delegations equals delegations of staker 1 & 2 - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) }) It("Staker with already max points is next uploader of bundle proposal and upload timeout passes", func() { @@ -869,7 +867,7 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() { Expect(expectedBalance).To(Equal(s.App().StakersKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_2, i.STAKER_2))) // pool delegations equals delegations of staker 0 & 1 - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) }) It("A bundle proposal with no quorum does not reach the upload interval", func() { @@ -1030,7 +1028,7 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() { _, found = s.App().StakersKeeper.GetValidator(s.Ctx(), i.STAKER_2) Expect(found).To(BeTrue()) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) // check if next uploader not got slashed expectedBalance := 100 * i.KYVE @@ -1118,7 +1116,7 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() { _, found = s.App().StakersKeeper.GetValidator(s.Ctx(), i.STAKER_2) Expect(found).To(BeTrue()) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) // check if next uploader not got slashed expectedBalance := 100 * i.KYVE @@ -1201,7 +1199,7 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() { _, found = s.App().StakersKeeper.GetValidator(s.Ctx(), i.STAKER_0) Expect(found).To(BeTrue()) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) // check that uploader received upload slash expectedBalance := 100*i.KYVE - uint64(s.App().StakersKeeper.GetUploadSlash(s.Ctx()).Mul(math.LegacyNewDec(int64(100*i.KYVE))).TruncateInt64()) @@ -1367,7 +1365,7 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() { _, found = s.App().StakersKeeper.GetValidator(s.Ctx(), i.STAKER_2) Expect(found).To(BeTrue()) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 1)).To(Equal(200 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 1)).To(Equal(200 * i.KYVE)) // check if next uploader not got slashed slashAmountRatio := s.App().StakersKeeper.GetTimeoutSlash(s.Ctx()) diff --git a/x/bundles/keeper/logic_round_robin.go b/x/bundles/keeper/logic_round_robin.go index 3edf586d..ea9e99c9 100644 --- a/x/bundles/keeper/logic_round_robin.go +++ b/x/bundles/keeper/logic_round_robin.go @@ -75,15 +75,15 @@ func (k Keeper) LoadRoundRobinValidatorSet(ctx sdk.Context, poolId uint64) Round newValidators := make(map[string]bool, 0) // Add all current pool validators to the round-robin set for _, address := range k.stakerKeeper.GetAllStakerAddressesOfPool(ctx, poolId) { - delegation := k.stakerKeeper.GetValidatorPoolStake(ctx, address, poolId) - if delegation > 0 { + stake := k.stakerKeeper.GetValidatorPoolStake(ctx, address, poolId) + if stake > 0 { // If a validator has no delegation do not add to the round-robin set. Validator is basically non-existent. vs.Validators = append(vs.Validators, RoundRobinValidatorPower{ Address: address, - Power: int64(delegation), + Power: int64(stake), }) vs.Progress[address] = 0 - totalDelegation += int64(delegation) + totalDelegation += int64(stake) newValidators[address] = true } } diff --git a/x/bundles/keeper/logic_round_robin_test.go b/x/bundles/keeper/logic_round_robin_test.go index 09582726..bd5229c8 100644 --- a/x/bundles/keeper/logic_round_robin_test.go +++ b/x/bundles/keeper/logic_round_robin_test.go @@ -22,6 +22,8 @@ TEST CASES - logic_bundles.go * Empty round-robin set * Partially filled round-robin set (one staker with 0 delegation) * Frequency analysis +* Frequency analysis with stake fractions +* Frequency analysis with maximum voting power cap * Frequency analysis (rounding) * Frequency analysis (excluded) * Exclude everybody @@ -32,6 +34,10 @@ TEST CASES - logic_bundles.go */ func joinDummy(s *i.KeeperTestSuite, index, kyveAmount uint64) { + joinDummyWithStakeFraction(s, index, kyveAmount, math.LegacyOneDec()) +} + +func joinDummyWithStakeFraction(s *i.KeeperTestSuite, index, kyveAmount uint64, stakeFraction math.LegacyDec) { s.CreateValidator(i.DUMMY[index], fmt.Sprintf("dummy-%d", index), int64(kyveAmount*i.KYVE)) s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{ @@ -40,7 +46,7 @@ func joinDummy(s *i.KeeperTestSuite, index, kyveAmount uint64) { Valaddress: i.VALDUMMY[index], Amount: 0, Commission: math.LegacyMustNewDecFromStr("0.1"), - StakeFraction: math.LegacyMustNewDecFromStr("1"), + StakeFraction: stakeFraction, }) } @@ -94,6 +100,10 @@ var _ = Describe("logic_round_robin.go", Ordered, func() { Duration: 60, } s.App().PoolKeeper.SetPool(s.Ctx(), pool) + + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("1") + s.App().PoolKeeper.SetParams(s.Ctx(), params) }) AfterEach(func() { @@ -219,6 +229,88 @@ var _ = Describe("logic_round_robin.go", Ordered, func() { Expect(frequency3[i.DUMMY[2]]).To(Equal(67000)) }) + It("Frequency analysis with stake fractions", func() { + // ARRANGE + joinDummyWithStakeFraction(s, 0, 100, math.LegacyMustNewDecFromStr("0")) + joinDummyWithStakeFraction(s, 1, 100, math.LegacyMustNewDecFromStr("0.5")) + joinDummyWithStakeFraction(s, 2, 100, math.LegacyMustNewDecFromStr("1")) + + // ACT + rrvs := s.App().BundlesKeeper.LoadRoundRobinValidatorSet(s.Ctx(), 0) + + frequency1 := make(map[string]int, 0) + for i := 0; i < 10; i++ { + frequency1[rrvs.NextProposer()] += 1 + } + + frequency2 := make(map[string]int, 0) + for i := 0; i < 100; i++ { + frequency2[rrvs.NextProposer()] += 1 + } + + frequency3 := make(map[string]int, 0) + for i := 0; i < 100000; i++ { + frequency3[rrvs.NextProposer()] += 1 + } + + // ASSERT + Expect(frequency1[i.DUMMY[0]]).To(Equal(0)) + Expect(frequency1[i.DUMMY[1]]).To(Equal(3)) + Expect(frequency1[i.DUMMY[2]]).To(Equal(7)) + + Expect(frequency2[i.DUMMY[0]]).To(Equal(0)) + Expect(frequency2[i.DUMMY[1]]).To(Equal(34)) + Expect(frequency2[i.DUMMY[2]]).To(Equal(66)) + + Expect(frequency3[i.DUMMY[0]]).To(Equal(0)) + Expect(frequency3[i.DUMMY[1]]).To(Equal(33333)) + Expect(frequency3[i.DUMMY[2]]).To(Equal(66667)) + }) + + It("Frequency analysis with maximum voting power cap", func() { + // ARRANGE + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("0.5") + s.App().PoolKeeper.SetParams(s.Ctx(), params) + + // NOTE that dummy with index 2 has more than 50% voting power, so his effective stake + // will be lower + joinDummy(s, 0, 2) + joinDummy(s, 1, 31) + joinDummy(s, 2, 67) + + // ACT + rrvs := s.App().BundlesKeeper.LoadRoundRobinValidatorSet(s.Ctx(), 0) + + frequency1 := make(map[string]int, 0) + for i := 0; i < 10; i++ { + frequency1[rrvs.NextProposer()] += 1 + } + + frequency2 := make(map[string]int, 0) + for i := 0; i < 100; i++ { + frequency2[rrvs.NextProposer()] += 1 + } + + frequency3 := make(map[string]int, 0) + for i := 0; i < 100000; i++ { + frequency3[rrvs.NextProposer()] += 1 + } + + // ASSERT + Expect(frequency1[i.DUMMY[0]]).To(Equal(0)) + Expect(frequency1[i.DUMMY[1]]).To(Equal(5)) + Expect(frequency1[i.DUMMY[2]]).To(Equal(5)) + + Expect(frequency2[i.DUMMY[0]]).To(Equal(3)) + Expect(frequency2[i.DUMMY[1]]).To(Equal(47)) + Expect(frequency2[i.DUMMY[2]]).To(Equal(50)) + + Expect(frequency3[i.DUMMY[0]]).To(Equal(3031)) // 2/66 + Expect(frequency3[i.DUMMY[1]]).To(Equal(46969)) // 31/66 + Expect(frequency3[i.DUMMY[2]]).To(Equal(50000)) // 33/66 + }) + It("Frequency analysis (rounding)", func() { // ARRANGE joinDummy(s, 0, 1) diff --git a/x/bundles/types/errors.go b/x/bundles/types/errors.go index 557386d6..64e00ba9 100644 --- a/x/bundles/types/errors.go +++ b/x/bundles/types/errors.go @@ -24,4 +24,5 @@ var ( ErrAlreadyVotedAbstain = errors.Register(ModuleName, 1206, "already voted abstain on bundle proposal") ErrVotingPowerTooHigh = errors.Register(ModuleName, 1207, "staker in pool has too much voting power") ErrEndKeyReached = errors.Register(ModuleName, 1208, "end key reached") + ErrPoolHasZeroDelegation = errors.Register(ModuleName, 1209, "pool has zero delegation") ) diff --git a/x/bundles/types/expected_keepers.go b/x/bundles/types/expected_keepers.go index 3d9c0ddd..e4d64802 100644 --- a/x/bundles/types/expected_keepers.go +++ b/x/bundles/types/expected_keepers.go @@ -38,11 +38,12 @@ type StakerKeeper interface { IncrementPoints(ctx sdk.Context, poolId uint64, stakerAddress string) (newPoints uint64) ResetPoints(ctx sdk.Context, poolId uint64, stakerAddress string) (previousPoints uint64) - GetTotalAndHighestDelegationOfPool(ctx sdk.Context, poolId uint64) (totalDelegation, highestDelegation uint64) GetValidator(ctx sdk.Context, staker string) (stakingtypes.Validator, bool) GetValidatorPoolCommission(ctx sdk.Context, staker string, poolId uint64) math.LegacyDec - GetValidatorPoolStakeFraction(ctx sdk.Context, staker string, poolId uint64) math.LegacyDec GetValidatorPoolStake(ctx sdk.Context, staker string, poolId uint64) uint64 + GetTotalStakeOfPool(ctx sdk.Context, poolId uint64) (totalStake uint64) + GetValidatorPoolStakes(ctx sdk.Context, poolId uint64, mustIncludeStakers ...string) map[string]uint64 + IsVotingPowerTooHigh(ctx sdk.Context, poolId uint64) bool Slash(ctx sdk.Context, poolId uint64, staker string, slashType stakersTypes.SlashType) PayoutRewards(ctx sdk.Context, staker string, amount sdk.Coins, payerModuleName string) error PayoutAdditionalCommissionRewards(ctx sdk.Context, validator string, payerModuleName string, amount sdk.Coins) error diff --git a/x/query/keeper/grpc_query_pool.go b/x/query/keeper/grpc_query_pool.go index e940b6c0..6062b54b 100644 --- a/x/query/keeper/grpc_query_pool.go +++ b/x/query/keeper/grpc_query_pool.go @@ -60,7 +60,7 @@ func (k Keeper) parsePoolResponse(ctx sdk.Context, pool *poolTypes.Pool) types.P totalSelfDelegation += k.stakerKeeper.GetDelegationAmountOfDelegator(ctx, address, address) } - totalDelegation := k.stakerKeeper.GetDelegationOfPool(ctx, pool.Id) + totalDelegation := k.stakerKeeper.GetTotalStakeOfPool(ctx, pool.Id) poolAccount := pool.GetPoolAccount() poolBalance := k.bankKeeper.GetBalance(ctx, poolAccount, globalTypes.Denom).Amount.Uint64() diff --git a/x/query/keeper/helper.go b/x/query/keeper/helper.go index 637486c1..364183c5 100644 --- a/x/query/keeper/helper.go +++ b/x/query/keeper/helper.go @@ -40,7 +40,7 @@ func (k Keeper) GetFullStaker(ctx sdk.Context, stakerAddress string) *types.Full InflationShareWeight: pool.InflationShareWeight, UploadInterval: pool.UploadInterval, TotalFunds: k.fundersKeeper.GetTotalActiveFunding(ctx, pool.Id), - TotalDelegation: k.stakerKeeper.GetDelegationOfPool(ctx, pool.Id), + TotalDelegation: k.stakerKeeper.GetTotalStakeOfPool(ctx, pool.Id), Status: k.GetPoolStatus(ctx, &pool), }, Points: valaccount.Points, @@ -67,11 +67,6 @@ func (k Keeper) GetFullStaker(ctx sdk.Context, stakerAddress string) *types.Full } func (k Keeper) GetPoolStatus(ctx sdk.Context, pool *pooltypes.Pool) pooltypes.PoolStatus { - // Get the total and the highest delegation of a single validator in the pool - totalDelegation, highestDelegation := k.stakerKeeper.GetTotalAndHighestDelegationOfPool(ctx, pool.Id) - maxVotingPower := k.poolKeeper.GetMaxVotingPowerPerPool(ctx) - maxDelegation := uint64(maxVotingPower.MulInt64(int64(totalDelegation)).TruncateInt64()) - var poolStatus pooltypes.PoolStatus poolStatus = pooltypes.POOL_STATUS_ACTIVE @@ -81,10 +76,10 @@ func (k Keeper) GetPoolStatus(ctx sdk.Context, pool *pooltypes.Pool) pooltypes.P poolStatus = pooltypes.POOL_STATUS_DISABLED } else if pool.EndKey != "" && pool.EndKey == pool.CurrentKey { poolStatus = pooltypes.POOL_STATUS_END_KEY_REACHED - } else if totalDelegation < pool.MinDelegation { - poolStatus = pooltypes.POOL_STATUS_NOT_ENOUGH_DELEGATION - } else if highestDelegation > maxDelegation { + } else if k.stakerKeeper.IsVotingPowerTooHigh(ctx, pool.Id) { poolStatus = pooltypes.POOL_STATUS_VOTING_POWER_TOO_HIGH + } else if k.stakerKeeper.GetTotalStakeOfPool(ctx, pool.Id) < pool.MinDelegation { + poolStatus = pooltypes.POOL_STATUS_NOT_ENOUGH_DELEGATION } else if k.fundersKeeper.GetTotalActiveFunding(ctx, pool.Id).IsZero() { poolStatus = pooltypes.POOL_STATUS_NO_FUNDS } diff --git a/x/stakers/keeper/exported_functions.go b/x/stakers/keeper/exported_functions.go index 153c50ed..8b2286f1 100644 --- a/x/stakers/keeper/exported_functions.go +++ b/x/stakers/keeper/exported_functions.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "sort" "cosmossdk.io/errors" "cosmossdk.io/math" @@ -11,8 +12,9 @@ import ( "github.com/cosmos/cosmos-sdk/types/query" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" + // Stakers - "github.com/KYVENetwork/chain/x/stakers/types" + stakertypes "github.com/KYVENetwork/chain/x/stakers/types" ) // These functions are meant to be called from external modules. @@ -25,7 +27,7 @@ import ( func (k Keeper) LeavePool(ctx sdk.Context, staker string, poolId uint64) { k.RemoveValaccountFromPool(ctx, poolId, staker) - _ = ctx.EventManager().EmitTypedEvent(&types.EventLeavePool{ + _ = ctx.EventManager().EmitTypedEvent(&stakertypes.EventLeavePool{ PoolId: poolId, Staker: staker, }) @@ -63,11 +65,11 @@ func (k Keeper) GetPaginatedStakersByDelegation(ctx sdk.Context, pagination *que func (k Keeper) AssertValaccountAuthorized(ctx sdk.Context, poolId uint64, stakerAddress string, valaddress string) error { valaccount, active := k.GetValaccount(ctx, poolId, stakerAddress) if !active { - return types.ErrValaccountUnauthorized + return stakertypes.ErrValaccountUnauthorized } if valaccount.Valaddress != valaddress { - return types.ErrValaccountUnauthorized + return stakertypes.ErrValaccountUnauthorized } return nil @@ -79,29 +81,27 @@ func (k Keeper) GetActiveStakers(ctx sdk.Context) []string { return k.getAllActiveStakers(ctx) } -// GetDelegationOfPool returns the amount of how many ukyve users have delegated -// to stakers that are participating in the given pool -func (k Keeper) GetDelegationOfPool(ctx sdk.Context, poolId uint64) uint64 { - totalDelegation := uint64(0) - for _, address := range k.GetAllStakerAddressesOfPool(ctx, poolId) { - totalDelegation += k.GetValidatorPoolStake(ctx, address, poolId) +// GetTotalStakeOfPool returns the amount in uykve which actively secures +// the given pool +func (k Keeper) GetTotalStakeOfPool(ctx sdk.Context, poolId uint64) (totalStake uint64) { + effectiveStakes := k.GetValidatorPoolStakes(ctx, poolId) + for _, stake := range effectiveStakes { + totalStake += stake } - return totalDelegation + return } -// GetTotalAndHighestDelegationOfPool iterates all validators of a given pool and returns the stake of the validator -// with the highest stake and the sum of all stakes. -func (k Keeper) GetTotalAndHighestDelegationOfPool(ctx sdk.Context, poolId uint64) (totalDelegation, highestDelegation uint64) { - for _, address := range k.GetAllStakerAddressesOfPool(ctx, poolId) { - delegation := k.GetValidatorPoolStake(ctx, address, poolId) - totalDelegation += delegation +// IsVotingPowerTooHigh returns whether there are enough validators in a pool +// to successfully stay below the max voting power +func (k Keeper) IsVotingPowerTooHigh(ctx sdk.Context, poolId uint64) bool { + addresses := int64(len(k.GetAllStakerAddressesOfPool(ctx, poolId))) + maxVotingPower := k.poolKeeper.GetMaxVotingPowerPerPool(ctx) - if delegation > highestDelegation { - highestDelegation = delegation - } + if maxVotingPower.IsZero() { + return true } - return totalDelegation, highestDelegation + return math.LegacyOneDec().Quo(maxVotingPower).GT(math.LegacyNewDec(addresses)) } // GetValidator returns the Cosmos-validator for a given kyve-address. @@ -124,21 +124,150 @@ func (k Keeper) GetValidatorPoolCommission(ctx sdk.Context, staker string, poolI return valaccount.Commission } -// GetValidatorPoolStakeFraction returns the stake fraction a validator has inside the pool -func (k Keeper) GetValidatorPoolStakeFraction(ctx sdk.Context, staker string, poolId uint64) math.LegacyDec { - valaccount, _ := k.GetValaccount(ctx, poolId, staker) - return valaccount.StakeFraction +// GetValidatorPoolStake returns stake a validator has actively and at risk inside the pool +func (k Keeper) GetValidatorPoolStake(ctx sdk.Context, staker string, poolId uint64) uint64 { + return k.GetValidatorPoolStakes(ctx, poolId, staker)[staker] } -// GetValidatorPoolStake returns stake a validator has inside the pool -func (k Keeper) GetValidatorPoolStake(ctx sdk.Context, staker string, poolId uint64) uint64 { - validator, found := k.GetValidator(ctx, staker) - if !found { - return 0 +// GetValidatorPoolStakes returns a map for all pool validators with their effective stake. Effective stake +// is the actual amount which determines the validator's voting power and is the actual amount at risk for +// slashing. The effective stake can be lower (never higher) than the specified stake by the validators by +// his stake fraction because of the maximum voting power which limits the voting power a validator can have +// in a pool. +// +// We limit the voting power by first sorting all validators based on their stake and start at the highest. From +// there we check if this validator exceeds the voting power with his stake, if he does we cut off an amount +// from his stake and redistribute it to all the validators below him based on their stakes. Because we are simply +// redistributing exact amounts we do not need to worry about changing the total stake which would mess up voting +// powers of other validators again. After this has been repeated for every validator that exceeds the max voting +// power we finally scale down all stakes to the lowest validator's stake. This is because the top validators lost +// stake and the lowest validators gained stake, but because it is not allowed to risk more than specified so we scale +// everything down accordingly. This results in a stake distribution where every validator who was below the max +// voting power has the same effective stake as his dedicated stake and those validators who where above it have +// less effective stake. +func (k Keeper) GetValidatorPoolStakes(ctx sdk.Context, poolId uint64, mustIncludeStakers ...string) map[string]uint64 { + type ValidatorStake struct { + Address string + Stake uint64 + } + + validators := make([]ValidatorStake, 0) + stakes := make(map[string]uint64) + + // we include given stakers since in some instances the staker we are looking for has been already kicked + // out of a pool, but we still need to payout rewards or slash him afterward depending on his last action + // right before leaving the pool + addresses := util.RemoveDuplicateStrings(append(k.GetAllStakerAddressesOfPool(ctx, poolId), mustIncludeStakers...)) + maxVotingPower := k.poolKeeper.GetMaxVotingPowerPerPool(ctx) + + // it is impossible regardless how many validators are in a pool to have a max voting power of 0%, + // therefore we return here + if maxVotingPower.IsZero() { + return stakes + } + + // if there are not enough validators in a pool so that the max voting power is always + // exceeded by at least one validator we return + if math.LegacyOneDec().Quo(maxVotingPower).GT(math.LegacyNewDec(int64(len(addresses)))) { + return stakes + } + + totalStake := int64(0) + + for _, address := range addresses { + validator, _ := k.GetValidator(ctx, address) + valaccount, _ := k.GetValaccount(ctx, poolId, address) + + // calculate the stake the validator has specifically chosen for this pool + // with his stake fraction + stake := uint64(valaccount.StakeFraction.MulInt(validator.GetBondedTokens()).TruncateInt64()) + + stakes[address] = stake + validators = append(validators, ValidatorStake{ + Address: address, + Stake: stake, + }) + totalStake += int64(stake) + } + + totalStakeRemainder := totalStake + + // sort descending based on stake + sort.SliceStable(validators, func(i, j int) bool { + return validators[i].Stake > validators[j].Stake + }) + + // if the total stake of the pool is zero we return + if totalStake == 0 { + return stakes + } + + var lastCutoffIndex int + + for i, validator := range validators { + // check if the validator has a higher stake than allowed by the max voting power + if math.LegacyNewDec(int64(stakes[validator.Address])).GT(maxVotingPower.MulInt64(totalStake)) { + // if the validator got a stake which would give him a higher voting power than the maximum allowed + // one we cut off the exact amount from his stake so that he is just below the max voting power + cutoffAmount := math.LegacyNewDec(int64(stakes[validator.Address])).Sub(maxVotingPower.MulInt64(totalStake)).TruncateInt64() + + totalStakeRemainder -= int64(validator.Stake) + stakes[validator.Address] -= uint64(cutoffAmount) + + // we take the cutoff amount and distribute it on the remaining validators down the list + // who all have less voting power than the current one. We distribute the cutoff amount + // based on the validator's stake + if totalStakeRemainder > 0 { + for _, v := range validators[i+1:] { + stakes[v.Address] += uint64(math.LegacyNewDec(int64(v.Stake)).QuoInt64(totalStakeRemainder).MulInt64(cutoffAmount).TruncateInt64()) + } + } + + lastCutoffIndex = i + } else { + // if we reach the first validator who is below the max voting power we know that the remaining + // ones will be also below it + break + } } - stakeFraction := k.GetValidatorPoolStakeFraction(ctx, staker, poolId) - return uint64(math.LegacyNewDecFromInt(validator.BondedTokens()).Mul(stakeFraction).TruncateInt64()) + // if no amounts got cut off we can return already + if totalStakeRemainder == totalStake { + return stakes + } + + // after we have redistributed all cutoff amounts so that no validator exceeds the maximum voting power + // based on their remaining effective stake we now scale the stakes to get the true effective staking amount. + // This is because while the top validators who got their voting power reduced the lower validators have actually + // gained voting power relatively. But because their effective stake is now bigger than their allocated stake + // we have to scale it down again, because we are not allowed to slash more than the validator has allocated in + // this particular pool. Therefore, we take the stake of the lowest validator (with a bigger stake than 0) and scale + // down all stakes of all other validators to that accordingly + scaleFactor := math.LegacyZeroDec() + + // get the lowest validator with effective stake still bigger than zero and determine the scale factor + for i := len(validators) - 1; i >= 0; i-- { + if stakes[validators[i].Address] > 0 { + scaleFactor = math.LegacyNewDec(int64(validators[i].Stake)).QuoInt64(int64(stakes[validators[i].Address])) + break + } + } + + // scale all effective stakes down to scale factor + for i, validator := range validators { + // for all validators who got cut off we always round down to ensure that their voting power actually + // stays below the max voting power + if i <= lastCutoffIndex { + stakes[validator.Address] = uint64(scaleFactor.MulInt64(int64(stakes[validator.Address])).TruncateInt64()) + } else { + stakes[validator.Address] = uint64(scaleFactor.MulInt64(int64(stakes[validator.Address])).Ceil().TruncateInt64()) + } + } + + // the result is a map which contains the effective stake for every validator in a pool. The effective stake + // can not be higher than the dedicated stake specified by the validator by his stake fraction, therefore it + // represents the true amount of $KYVE which is at risk for slashing + return stakes } // GetOutstandingCommissionRewards returns the outstanding commission rewards for a given validator @@ -194,7 +323,7 @@ func (k Keeper) GetOutstandingRewards(orgCtx sdk.Context, staker string, delegat } // Slash reduces the delegation of all delegators of `staker` by fraction. The slash itself is handled by the cosmos-sdk -func (k Keeper) Slash(ctx sdk.Context, poolId uint64, staker string, slashType types.SlashType) { +func (k Keeper) Slash(ctx sdk.Context, poolId uint64, staker string, slashType stakertypes.SlashType) { validator, found := k.GetValidator(ctx, staker) if !found { return @@ -202,9 +331,19 @@ func (k Keeper) Slash(ctx sdk.Context, poolId uint64, staker string, slashType t consAddrBytes, _ := validator.GetConsAddr() - // the validator can only be slashed for his stake fraction in a pool, therefore we update the slash fraction - // accordingly - slashFraction := k.getSlashFraction(ctx, slashType).Mul(k.GetValidatorPoolStakeFraction(ctx, staker, poolId)) + // here the stake fraction can be actually different from the stake fraction the validator has specified + // for this pool because with the max voting power in place his effective stake in a pool could have been + // reduced because his original stake was too high. Therefore, we determine the true stake fraction + // by dividing his effective stake with his bonded amount. + stakeFraction := math.LegacyZeroDec() + + if !validator.GetBondedTokens().IsZero() { + stakeFraction = math.LegacyNewDec(int64(k.GetValidatorPoolStake(ctx, staker, poolId))).QuoInt(validator.GetBondedTokens()) + } + + // the validator can only be slashed for his effective stake fraction in a pool, therefore we + // update the slash fraction accordingly + slashFraction := k.getSlashFraction(ctx, slashType).Mul(stakeFraction) amount, err := k.stakingKeeper.Slash( ctx, @@ -217,11 +356,12 @@ func (k Keeper) Slash(ctx sdk.Context, poolId uint64, staker string, slashType t return } - _ = ctx.EventManager().EmitTypedEvent(&types.EventSlash{ - PoolId: poolId, - Staker: staker, - Amount: amount.Uint64(), - SlashType: slashType, + _ = ctx.EventManager().EmitTypedEvent(&stakertypes.EventSlash{ + PoolId: poolId, + Staker: staker, + Amount: amount.Uint64(), + SlashType: slashType, + StakeFraction: stakeFraction, }) } diff --git a/x/stakers/keeper/keeper_suite_effective_stake_test.go b/x/stakers/keeper/keeper_suite_effective_stake_test.go new file mode 100644 index 00000000..e68ea9a4 --- /dev/null +++ b/x/stakers/keeper/keeper_suite_effective_stake_test.go @@ -0,0 +1,593 @@ +package keeper_test + +import ( + "cosmossdk.io/math" + stakerstypes "github.com/KYVENetwork/chain/x/stakers/types" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + i "github.com/KYVENetwork/chain/testutil/integration" + pooltypes "github.com/KYVENetwork/chain/x/pool/types" +) + +/* + +TEST CASES - keeper_suite_effective_stake_test.go + +* Test effective stake with all validators below the max pool voting power +* Test effective stake with one validator above the max pool voting power +* Test effective stake with multiple validators above the max pool voting power +* Test effective stake with fewer validators than required to undercut the max pool voting power +* Test effective stake with some validators having zero delegation +* Test effective stake with all validators having zero delegation +* Test effective stake with 0% as max pool stake +* Test effective stake with 100% as max pool stake +* Test effective stake with all validators below the max pool voting power due to stake fractions +* Test effective stake with one validator above the max pool voting power due to stake fractions +* Test effective stake with multiple validators above the max pool voting power due to stake fractions +* Test effective stake with some validators having zero delegation due to stake fractions +* Test effective stake with all validators having zero delegation due to stake fractions + +*/ + +var _ = Describe("keeper_suite_effective_stake_test.go", Ordered, func() { + s := i.NewCleanChain() + + gov := s.App().GovKeeper.GetGovernanceAccount(s.Ctx()).GetAddress().String() + + BeforeEach(func() { + // init new clean chain + s = i.NewCleanChain() + + // create pool + msg := &pooltypes.MsgCreatePool{ + Authority: gov, + UploadInterval: 60, + MaxBundleSize: 100, + InflationShareWeight: math.LegacyZeroDec(), + Binaries: "{}", + } + s.RunTxPoolSuccess(msg) + + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("0.5") + s.App().PoolKeeper.SetParams(s.Ctx(), params) + }) + + AfterEach(func() { + s.PerformValidityChecks() + }) + + It("Test effective stake with all validators below the max pool voting power", func() { + // ARRANGE + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + // ACT + + // ASSERT + Expect(s.App().StakersKeeper.IsVotingPowerTooHigh(s.Ctx(), 0)).To(BeFalse()) + + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(100 * i.KYVE)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_1, 0)).To(Equal(100 * i.KYVE)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_2, 0)).To(Equal(100 * i.KYVE)) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(300 * i.KYVE)) + }) + + It("Test effective stake with one validator above the max pool voting power", func() { + // ARRANGE + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(250*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + // ASSERT + Expect(s.App().StakersKeeper.IsVotingPowerTooHigh(s.Ctx(), 0)).To(BeFalse()) + + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(100 * i.KYVE)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_1, 0)).To(Equal(200*i.KYVE - 1)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_2, 0)).To(Equal(100 * i.KYVE)) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(400*i.KYVE - 1)) + }) + + It("Test effective stake with multiple validators above the max pool voting power", func() { + // ARRANGE + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("0.35") + s.App().PoolKeeper.SetParams(s.Ctx(), params) + + s.CreateValidator(i.STAKER_0, "Staker-0", int64(600*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(500*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(120*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + // ASSERT + Expect(s.App().StakersKeeper.IsVotingPowerTooHigh(s.Ctx(), 0)).To(BeFalse()) + + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(140 * i.KYVE)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_1, 0)).To(Equal(140 * i.KYVE)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_2, 0)).To(Equal(120 * i.KYVE)) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(400 * i.KYVE)) + }) + + It("Test effective stake with fewer validators than required to undercut the max pool voting power", func() { + // ARRANGE + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("0.2") + s.App().PoolKeeper.SetParams(s.Ctx(), params) + + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + // ASSERT + Expect(s.App().StakersKeeper.IsVotingPowerTooHigh(s.Ctx(), 0)).To(BeTrue()) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(BeZero()) + }) + + It("Test effective stake with some validators having zero delegation", func() { + // ARRANGE + s.CreateValidator(i.STAKER_0, "Staker-0", int64(200*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateZeroDelegationValidator(i.STAKER_1, "Staker-1") + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + // ASSERT + Expect(s.App().StakersKeeper.IsVotingPowerTooHigh(s.Ctx(), 0)).To(BeFalse()) + + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(100*i.KYVE - 1)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_1, 0)).To(Equal(0 * i.KYVE)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_2, 0)).To(Equal(100 * i.KYVE)) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200*i.KYVE - 1)) + }) + + It("Test effective stake with all validators having zero delegation", func() { + // ARRANGE + s.CreateZeroDelegationValidator(i.STAKER_0, "Staker-0") + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateZeroDelegationValidator(i.STAKER_1, "Staker-1") + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateZeroDelegationValidator(i.STAKER_2, "Staker-2") + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + // ASSERT + Expect(s.App().StakersKeeper.IsVotingPowerTooHigh(s.Ctx(), 0)).To(BeFalse()) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(BeZero()) + }) + + It("Test effective stake with 0% as max pool stake", func() { + // ARRANGE + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("0") + s.App().PoolKeeper.SetParams(s.Ctx(), params) + + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + // ASSERT + Expect(s.App().StakersKeeper.IsVotingPowerTooHigh(s.Ctx(), 0)).To(BeTrue()) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(BeZero()) + }) + + It("Test effective stake with 100% as max pool stake", func() { + // ARRANGE + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("1") + s.App().PoolKeeper.SetParams(s.Ctx(), params) + + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + // ASSERT + Expect(s.App().StakersKeeper.IsVotingPowerTooHigh(s.Ctx(), 0)).To(BeFalse()) + + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(100 * i.KYVE)) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(100 * i.KYVE)) + }) + + It("Test effective stake with all validators below the max pool voting power due to stake fractions", func() { + // ARRANGE + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(200*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0.5"), + }) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(500*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0.2"), + }) + + // ACT + + // ASSERT + Expect(s.App().StakersKeeper.IsVotingPowerTooHigh(s.Ctx(), 0)).To(BeFalse()) + + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(100 * i.KYVE)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_1, 0)).To(Equal(100 * i.KYVE)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_2, 0)).To(Equal(100 * i.KYVE)) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(300 * i.KYVE)) + }) + + It("Test effective stake with one validator above the max pool voting power due to stake fractions", func() { + // ARRANGE + s.CreateValidator(i.STAKER_0, "Staker-0", int64(200*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0.5"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(300*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0.9"), + }) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(500*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0.2"), + }) + + // ASSERT + Expect(s.App().StakersKeeper.IsVotingPowerTooHigh(s.Ctx(), 0)).To(BeFalse()) + + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(100 * i.KYVE)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_1, 0)).To(Equal(200*i.KYVE - 1)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_2, 0)).To(Equal(100 * i.KYVE)) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(400*i.KYVE - 1)) + }) + + It("Test effective stake with multiple validators above the max pool voting power due to stake fractions", func() { + // ARRANGE + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("0.35") + s.App().PoolKeeper.SetParams(s.Ctx(), params) + + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0.8"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0.7"), + }) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0.2"), + }) + + // ASSERT + Expect(s.App().StakersKeeper.IsVotingPowerTooHigh(s.Ctx(), 0)).To(BeFalse()) + + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(uint64(23333333333))) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_1, 0)).To(Equal(uint64(23333333333))) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_2, 0)).To(Equal(20 * i.KYVE)) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(uint64(66666666666))) + }) + + It("Test effective stake with some validators having zero delegation due to stake fractions", func() { + // ARRANGE + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("1"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0"), + }) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(200*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0.5"), + }) + + // ASSERT + Expect(s.App().StakersKeeper.IsVotingPowerTooHigh(s.Ctx(), 0)).To(BeFalse()) + + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(100 * i.KYVE)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_1, 0)).To(Equal(0 * i.KYVE)) + Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_2, 0)).To(Equal(100 * i.KYVE)) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE)) + }) + + It("Test effective stake with all validators having zero delegation due to stake fractions", func() { + // ARRANGE + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_0, + PoolId: 0, + Valaddress: i.VALADDRESS_0_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0"), + }) + + s.CreateValidator(i.STAKER_1, "Staker-1", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_1, + PoolId: 0, + Valaddress: i.VALADDRESS_1_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0"), + }) + + s.CreateValidator(i.STAKER_2, "Staker-2", int64(100*i.KYVE)) + s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ + Creator: i.STAKER_2, + PoolId: 0, + Valaddress: i.VALADDRESS_2_A, + Amount: 100 * i.KYVE, + Commission: math.LegacyMustNewDecFromStr("0.1"), + StakeFraction: math.LegacyMustNewDecFromStr("0"), + }) + + // ASSERT + Expect(s.App().StakersKeeper.IsVotingPowerTooHigh(s.Ctx(), 0)).To(BeFalse()) + + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(BeZero()) + }) +}) diff --git a/x/stakers/keeper/logic_stakers.go b/x/stakers/keeper/logic_stakers.go index 22009d1f..1d888b25 100644 --- a/x/stakers/keeper/logic_stakers.go +++ b/x/stakers/keeper/logic_stakers.go @@ -23,9 +23,9 @@ func (k Keeper) getLowestStaker(ctx sdk.Context, poolId uint64) (val stakingType var minAmount uint64 = m.MaxUint64 for _, staker := range k.getAllStakersOfPool(ctx, poolId) { - delegationAmount := k.GetValidatorPoolStake(ctx, util.MustAccountAddressFromValAddress(staker.OperatorAddress), poolId) - if delegationAmount < minAmount { - minAmount = delegationAmount + stake := k.GetValidatorPoolStake(ctx, util.MustAccountAddressFromValAddress(staker.OperatorAddress), poolId) + if stake < minAmount { + minAmount = stake val = staker } } diff --git a/x/stakers/keeper/msg_server_join_pool.go b/x/stakers/keeper/msg_server_join_pool.go index c745550d..0764430b 100644 --- a/x/stakers/keeper/msg_server_join_pool.go +++ b/x/stakers/keeper/msg_server_join_pool.go @@ -76,8 +76,6 @@ func (k msgServer) JoinPool(goCtx context.Context, msg *types.MsgJoinPool) (*typ } } - // TODO: check here if validator with his stake fraction is over the maximum join limit - k.AddValaccountToPool(ctx, msg.PoolId, msg.Creator, msg.Valaddress, msg.Commission, msg.StakeFraction) if err := util.TransferFromAddressToAddress(k.bankKeeper, ctx, msg.Creator, msg.Valaddress, msg.Amount); err != nil { diff --git a/x/stakers/keeper/msg_server_join_pool_test.go b/x/stakers/keeper/msg_server_join_pool_test.go index 85d7f519..92f7ffbd 100644 --- a/x/stakers/keeper/msg_server_join_pool_test.go +++ b/x/stakers/keeper/msg_server_join_pool_test.go @@ -71,6 +71,10 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { } s.RunTxPoolSuccess(msg) + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("1") + s.App().PoolKeeper.SetParams(s.Ctx(), params) + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) initialBalanceStaker0 = s.GetBalanceFromAddress(i.STAKER_0) @@ -124,7 +128,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(HaveLen(1)) - totalStakeOfPool := s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0) + totalStakeOfPool := s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0) Expect(totalStakeOfPool).To(Equal(100 * i.KYVE)) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(totalStakeOfPool)) @@ -178,7 +182,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(HaveLen(0)) - totalStakeOfPool := s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 1) + totalStakeOfPool := s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 1) Expect(totalStakeOfPool).To(Equal(0 * i.KYVE)) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 1)).To(Equal(0 * i.KYVE)) @@ -234,7 +238,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(HaveLen(2)) - totalStakeOfPool := s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0) + totalStakeOfPool := s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0) Expect(totalStakeOfPool).To(Equal(200 * i.KYVE)) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(100 * i.KYVE)) @@ -252,7 +256,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { StakeFraction: math.LegacyMustNewDecFromStr("1"), }) - totalStakeOfPool := s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0) + totalStakeOfPool := s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0) Expect(totalStakeOfPool).To(Equal(100 * i.KYVE)) // ACT @@ -281,7 +285,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(HaveLen(1)) - totalStakeOfPool = s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0) + totalStakeOfPool = s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0) Expect(totalStakeOfPool).To(Equal(150 * i.KYVE)) @@ -576,7 +580,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(HaveLen(1)) - totalStakeOfPool := s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0) + totalStakeOfPool := s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0) Expect(totalStakeOfPool).To(Equal(100 * i.KYVE)) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(totalStakeOfPool)) @@ -619,7 +623,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(HaveLen(1)) - totalStakeOfPool := s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0) + totalStakeOfPool := s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0) Expect(totalStakeOfPool).To(Equal(100 * i.KYVE)) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(totalStakeOfPool)) @@ -686,7 +690,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { } // STAKER_0 is lowest staker and all stakers are full now. - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) s.CreateValidator(i.STAKER_1, "Staker-1", int64(150*i.KYVE)) @@ -701,7 +705,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { }) // Assert - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal((150*49 + 150) * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal((150*49 + 150) * i.KYVE)) Expect(s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)).ToNot(ContainElement(i.STAKER_0)) }) @@ -731,7 +735,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { } // STAKER_0 is lowest staker and all stakers are full now. - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) s.CreateValidator(i.STAKER_1, "Staker-1", int64(50*i.KYVE)) @@ -746,7 +750,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { }) // Assert - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) Expect(s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)).To(ContainElement(i.STAKER_0)) Expect(s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)).ToNot(ContainElement(i.STAKER_1)) }) @@ -777,7 +781,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { } // Alice is lowest staker and all stakers are full now. - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) s.CreateValidator(i.STAKER_1, "Staker-1", int64(150*i.KYVE)) @@ -798,7 +802,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { }) // ASSERT - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal((150*49 + 250) * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal((150*49 + 250) * i.KYVE)) Expect(s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)).To(ContainElement(i.STAKER_0)) Expect(s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)).NotTo(ContainElement(i.STAKER_1)) }) @@ -829,7 +833,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { } // STAKER_0 is lowest staker and all stakers are full now. - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) s.CreateValidator(i.STAKER_1, "Staker-1", int64(50*i.KYVE)) @@ -844,7 +848,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { }) // Assert - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) Expect(s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)).To(ContainElement(i.STAKER_0)) Expect(s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)).ToNot(ContainElement(i.STAKER_1)) }) @@ -875,7 +879,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { } // Alice is lowest staker and all stakers are full now. - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) s.CreateValidator(i.STAKER_1, "Staker-1", int64(50*i.KYVE)) @@ -896,7 +900,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { }) // ASSERT - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal((150*49 + 100) * i.KYVE)) Expect(s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)).To(ContainElement(i.STAKER_0)) Expect(s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)).NotTo(ContainElement(i.STAKER_1)) }) @@ -961,7 +965,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(HaveLen(1)) - totalStakeOfPool := s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0) + totalStakeOfPool := s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0) Expect(totalStakeOfPool).To(Equal(100 * i.KYVE)) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(totalStakeOfPool)) @@ -1031,7 +1035,7 @@ var _ = Describe("msg_server_join_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(HaveLen(1)) - totalStakeOfPool := s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0) + totalStakeOfPool := s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0) Expect(totalStakeOfPool).To(Equal(100 * i.KYVE)) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(totalStakeOfPool)) diff --git a/x/stakers/keeper/msg_server_leave_pool_test.go b/x/stakers/keeper/msg_server_leave_pool_test.go index 80e5bfad..ca33d3e5 100644 --- a/x/stakers/keeper/msg_server_leave_pool_test.go +++ b/x/stakers/keeper/msg_server_leave_pool_test.go @@ -43,6 +43,10 @@ var _ = Describe("msg_server_leave_pool.go", Ordered, func() { // create staker s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("1") + s.App().PoolKeeper.SetParams(s.Ctx(), params) + // join pool s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ Creator: i.STAKER_0, @@ -86,7 +90,7 @@ var _ = Describe("msg_server_leave_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(HaveLen(1)) - totalStakeOfPool := s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0) + totalStakeOfPool := s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0) Expect(totalStakeOfPool).To(Equal(100 * i.KYVE)) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(totalStakeOfPool)) @@ -110,7 +114,7 @@ var _ = Describe("msg_server_leave_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(BeEmpty()) - totalStakeOfPool = s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0) + totalStakeOfPool = s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0) Expect(totalStakeOfPool).To(BeZero()) // check if commission and stake fraction is still available @@ -159,7 +163,7 @@ var _ = Describe("msg_server_leave_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(HaveLen(2)) - totalStakeOfPool := s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0) + totalStakeOfPool := s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0) Expect(totalStakeOfPool).To(Equal(200 * i.KYVE)) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(100 * i.KYVE)) @@ -183,7 +187,7 @@ var _ = Describe("msg_server_leave_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(HaveLen(1)) - totalStakeOfPool = s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0) + totalStakeOfPool = s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0) Expect(totalStakeOfPool).To(Equal(100 * i.KYVE)) // check if commission and stake fraction is still available @@ -264,7 +268,7 @@ var _ = Describe("msg_server_leave_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(HaveLen(1)) - totalStakeOfPool := s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 1) + totalStakeOfPool := s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 1) Expect(totalStakeOfPool).To(Equal(100 * i.KYVE)) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(totalStakeOfPool)) @@ -286,7 +290,7 @@ var _ = Describe("msg_server_leave_pool.go", Ordered, func() { Expect(valaccountsOfPool).To(BeEmpty()) - totalStakeOfPool = s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 1) + totalStakeOfPool = s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 1) Expect(totalStakeOfPool).To(BeZero()) // check if commission and stake fraction is still available diff --git a/x/stakers/keeper/msg_server_update_stake_fraction_test.go b/x/stakers/keeper/msg_server_update_stake_fraction_test.go index e406f7a9..afb76ea5 100644 --- a/x/stakers/keeper/msg_server_update_stake_fraction_test.go +++ b/x/stakers/keeper/msg_server_update_stake_fraction_test.go @@ -50,6 +50,10 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { } s.RunTxPoolSuccess(msg) + params := s.App().PoolKeeper.GetParams(s.Ctx()) + params.MaxVotingPowerPerPool = math.LegacyMustNewDecFromStr("1") + s.App().PoolKeeper.SetParams(s.Ctx(), params) + s.CreateValidator(i.STAKER_0, "Staker-0", int64(100*i.KYVE)) s.RunTxStakersSuccess(&stakerstypes.MsgJoinPool{ @@ -71,7 +75,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.1"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(10 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) }) It("Increase stake fraction to 50% from previous stake fraction", func() { @@ -87,7 +91,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.5"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(50 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(50 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(50 * i.KYVE)) }) It("Decrease stake fraction to 0% from previous stake fraction", func() { @@ -111,7 +115,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(BeZero()) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(BeZero()) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(BeZero()) }) It("Decrease stake fraction to 1% from previous stake fraction", func() { @@ -127,7 +131,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.1"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(10 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) // wait for update s.CommitAfterSeconds(s.App().StakersKeeper.GetStakeFractionChangeTime(s.Ctx())) @@ -136,7 +140,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.01"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(1 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(1 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(1 * i.KYVE)) }) It("Increase stake fraction to 100% from previous stake fraction", func() { @@ -152,7 +156,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("1"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(100 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(100 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(100 * i.KYVE)) }) It("Update stake fraction to same value from previous stake fraction", func() { @@ -168,7 +172,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.1"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(10 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) }) It("Update stake fraction with a negative number from previous stake fraction", func() { @@ -184,7 +188,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.1"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(10 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) }) It("Update stake fraction with a too high number from previous stake fraction during change time", func() { @@ -200,7 +204,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.1"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(10 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) }) It("Increase stake fraction after stake fraction has been decreased before during change time", func() { @@ -223,7 +227,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.2"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(20 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(20 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(20 * i.KYVE)) // wait for update s.CommitAfterSeconds(s.App().StakersKeeper.GetCommissionChangeTime(s.Ctx())) @@ -232,7 +236,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.2"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(20 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(20 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(20 * i.KYVE)) }) It("Decrease stake fraction after stake fraction has been decreased before during change time", func() { @@ -255,7 +259,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.1"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(10 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) // wait for update s.CommitAfterSeconds(s.App().StakersKeeper.GetCommissionChangeTime(s.Ctx())) @@ -264,7 +268,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.01"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(1 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(1 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(1 * i.KYVE)) }) It("Decrease stake fraction after stake fraction has been increased before", func() { @@ -278,7 +282,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.5"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(50 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(50 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(50 * i.KYVE)) s.PerformValidityChecks() @@ -293,7 +297,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.5"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(50 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(50 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(50 * i.KYVE)) // wait for update s.CommitAfterSeconds(s.App().StakersKeeper.GetCommissionChangeTime(s.Ctx())) @@ -302,7 +306,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.02"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(2 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(2 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(2 * i.KYVE)) }) It("Update stake fraction with multiple pools", func() { @@ -344,12 +348,12 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount0, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount0.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.5"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(50 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(50 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(50 * i.KYVE)) valaccount1, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 1, i.STAKER_0) Expect(valaccount1.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.1"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 1)).To(Equal(10 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 1)).To(Equal(10 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 1)).To(Equal(10 * i.KYVE)) // wait for update s.CommitAfterSeconds(s.App().StakersKeeper.GetCommissionChangeTime(s.Ctx())) @@ -358,12 +362,12 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount0, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount0.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.5"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(50 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(50 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(50 * i.KYVE)) valaccount1, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 1, i.STAKER_0) Expect(valaccount1.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.03"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 1)).To(Equal(3 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 1)).To(Equal(3 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 1)).To(Equal(3 * i.KYVE)) }) It("Validator stake increases while stake fraction stays the same", func() { @@ -371,7 +375,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.1"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(10 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) // ACT s.SelfDelegateValidator(i.STAKER_0, 50*i.KYVE) @@ -380,7 +384,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.1"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(15 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(15 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(15 * i.KYVE)) }) It("Validator stake decreases while stake fraction stays the same", func() { @@ -388,7 +392,7 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.1"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(10 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(10 * i.KYVE)) // ACT s.SelfUndelegateValidator(i.STAKER_0, 50*i.KYVE) @@ -402,6 +406,6 @@ var _ = Describe("msg_server_update_stake_fraction.go", Ordered, func() { valaccount, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0) Expect(valaccount.StakeFraction).To(Equal(math.LegacyMustNewDecFromStr("0.1"))) Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_0, 0)).To(Equal(5 * i.KYVE)) - Expect(s.App().StakersKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(5 * i.KYVE)) + Expect(s.App().StakersKeeper.GetTotalStakeOfPool(s.Ctx(), 0)).To(Equal(5 * i.KYVE)) }) }) diff --git a/x/stakers/types/events.pb.go b/x/stakers/types/events.pb.go index 445aa39b..393ad404 100644 --- a/x/stakers/types/events.pb.go +++ b/x/stakers/types/events.pb.go @@ -403,10 +403,13 @@ type EventSlash struct { PoolId uint64 `protobuf:"varint,1,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` // staker is the account address of the protocol node. Staker string `protobuf:"bytes,2,opt,name=staker,proto3" json:"staker,omitempty"` - // amount ... + // amount is the total amount that got slashed Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` - // slash_type + // slash_type is the type of the protocol slash SlashType SlashType `protobuf:"varint,4,opt,name=slash_type,json=slashType,proto3,enum=kyve.stakers.v1beta1.SlashType" json:"slash_type,omitempty"` + // stake_fraction is the percentage of how much of the validators total + // bonded amount was under risk for slashing + StakeFraction cosmossdk_io_math.LegacyDec `protobuf:"bytes,5,opt,name=stake_fraction,json=stakeFraction,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"stake_fraction"` } func (m *EventSlash) Reset() { *m = EventSlash{} } @@ -483,41 +486,41 @@ func init() { func init() { proto.RegisterFile("kyve/stakers/v1beta1/events.proto", fileDescriptor_7a1b3dc9634155a0) } var fileDescriptor_7a1b3dc9634155a0 = []byte{ - // 532 bytes of a gzipped FileDescriptorProto + // 537 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0xc1, 0x6e, 0xd3, 0x40, - 0x10, 0x86, 0xb3, 0x69, 0x48, 0x94, 0x41, 0x8d, 0x44, 0x54, 0xc0, 0x4a, 0x91, 0x53, 0xcc, 0xa5, - 0x07, 0x64, 0xab, 0xe5, 0x8e, 0xd4, 0x84, 0x56, 0xa2, 0x54, 0x50, 0xb9, 0x80, 0x04, 0x97, 0x68, - 0xe3, 0x5d, 0x12, 0x2b, 0xb6, 0xc7, 0xf2, 0x6e, 0x13, 0xfc, 0x08, 0x1c, 0x90, 0xb8, 0x20, 0x1e, - 0x84, 0x97, 0xe8, 0xb1, 0x47, 0xc4, 0xa1, 0x42, 0xc9, 0x8b, 0x20, 0xaf, 0xed, 0xc6, 0x41, 0x46, - 0x6a, 0x73, 0xdb, 0xb1, 0xfe, 0xf9, 0xe6, 0xdf, 0x7f, 0xe4, 0x85, 0xc7, 0x93, 0x78, 0xca, 0x2d, - 0x21, 0xe9, 0x84, 0x47, 0xc2, 0x9a, 0xee, 0x0d, 0xb9, 0xa4, 0x7b, 0x16, 0x9f, 0xf2, 0x40, 0x0a, - 0x33, 0x8c, 0x50, 0x62, 0x7b, 0x2b, 0x91, 0x98, 0x99, 0xc4, 0xcc, 0x24, 0x9d, 0xad, 0x11, 0x8e, - 0x50, 0x09, 0xac, 0xe4, 0x94, 0x6a, 0x3b, 0xe5, 0xb8, 0x90, 0x46, 0xd4, 0xcf, 0x70, 0x1d, 0xa3, - 0x54, 0x92, 0xe3, 0x95, 0xc6, 0xf8, 0x49, 0xe0, 0xde, 0x61, 0xe2, 0xe1, 0x5d, 0xc8, 0xa8, 0xe4, - 0xa7, 0xaa, 0xbf, 0x7d, 0x00, 0x80, 0x1e, 0x1b, 0xa4, 0x34, 0x8d, 0xec, 0x90, 0xdd, 0xbb, 0xfb, - 0x8f, 0xcc, 0x32, 0x77, 0x66, 0xda, 0xd1, 0xab, 0x5d, 0x5c, 0x75, 0x2b, 0x76, 0x13, 0x3d, 0xb6, - 0x44, 0x04, 0x7c, 0x96, 0x23, 0xaa, 0x37, 0x47, 0x04, 0x7c, 0x96, 0x21, 0x34, 0x68, 0x84, 0x34, - 0xf6, 0x90, 0x32, 0x6d, 0x63, 0x87, 0xec, 0x36, 0xed, 0xbc, 0x34, 0xbe, 0x12, 0xb8, 0x5f, 0x70, - 0xdd, 0x47, 0xdf, 0x77, 0x85, 0x70, 0x31, 0x68, 0x3f, 0x80, 0x7a, 0x8a, 0x57, 0xae, 0x9b, 0x76, - 0x56, 0xb5, 0x1f, 0x42, 0x23, 0x44, 0xf4, 0x06, 0x2e, 0x53, 0x5e, 0x6a, 0x76, 0x3d, 0x29, 0x5f, - 0xb2, 0x76, 0x1f, 0xc0, 0xb9, 0x6e, 0x4f, 0xe7, 0xf4, 0x9e, 0x24, 0x4e, 0x7e, 0x5f, 0x75, 0xb7, - 0x1d, 0x14, 0x3e, 0x0a, 0xc1, 0x26, 0xa6, 0x8b, 0x96, 0x4f, 0xe5, 0xd8, 0x3c, 0xe1, 0x23, 0xea, - 0xc4, 0x2f, 0xb8, 0x63, 0x17, 0xda, 0x8c, 0x1f, 0x04, 0xb4, 0x82, 0x9f, 0xb3, 0x64, 0xe6, 0x51, - 0x44, 0x1d, 0xb9, 0x96, 0xa5, 0x63, 0x68, 0x29, 0xc9, 0xe0, 0x53, 0x86, 0xb8, 0x8d, 0xad, 0x4d, - 0x51, 0x1c, 0x6e, 0xbc, 0x81, 0x6d, 0x65, 0xac, 0xef, 0x51, 0xd7, 0x5f, 0xe6, 0x64, 0xf3, 0x19, - 0x8d, 0x98, 0xf8, 0xaf, 0x37, 0x0d, 0x1a, 0xd4, 0xc7, 0xf3, 0x40, 0xa6, 0xab, 0x6b, 0xda, 0x79, - 0x69, 0x7c, 0xa9, 0xc2, 0xa6, 0x22, 0x1e, 0xa3, 0x1b, 0x9c, 0x22, 0x7a, 0xc5, 0x7b, 0x90, 0x95, - 0x7b, 0x2c, 0xe1, 0xd5, 0x15, 0xb8, 0x0e, 0x30, 0xa5, 0x1e, 0x65, 0x2c, 0xe2, 0x42, 0x64, 0xab, - 0x2d, 0x7c, 0x49, 0xfa, 0xd2, 0x69, 0x5a, 0x2d, 0xe5, 0xa5, 0xd5, 0x3f, 0xab, 0xba, 0xb3, 0xd6, - 0xaa, 0x4a, 0xc2, 0xad, 0xaf, 0x1d, 0xee, 0x01, 0xb4, 0x54, 0x14, 0x27, 0x9c, 0x4e, 0xf9, 0x5a, - 0x59, 0x18, 0xdf, 0x09, 0x80, 0x62, 0x9c, 0x79, 0x54, 0x8c, 0x6f, 0x9f, 0xe5, 0x32, 0xab, 0x8d, - 0x95, 0xac, 0x9e, 0x03, 0x88, 0x84, 0x38, 0x90, 0x71, 0xc8, 0x55, 0x8e, 0xad, 0xfd, 0x6e, 0xf9, - 0xef, 0xa7, 0x26, 0xbf, 0x8d, 0x43, 0x6e, 0x37, 0x45, 0x7e, 0xec, 0x1d, 0x5d, 0xcc, 0x75, 0x72, - 0x39, 0xd7, 0xc9, 0x9f, 0xb9, 0x4e, 0xbe, 0x2d, 0xf4, 0xca, 0xe5, 0x42, 0xaf, 0xfc, 0x5a, 0xe8, - 0x95, 0x8f, 0x4f, 0x47, 0xae, 0x1c, 0x9f, 0x0f, 0x4d, 0x07, 0x7d, 0xeb, 0xd5, 0x87, 0xf7, 0x87, - 0xaf, 0xb9, 0x9c, 0x61, 0x34, 0xb1, 0x9c, 0x31, 0x75, 0x03, 0xeb, 0xf3, 0xf5, 0x7b, 0x93, 0x0c, - 0x16, 0xc3, 0xba, 0x7a, 0x66, 0x9e, 0xfd, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x42, 0x2b, 0x27, 0xf7, - 0xfe, 0x04, 0x00, 0x00, + 0x10, 0x86, 0xb3, 0x69, 0x9a, 0x28, 0x83, 0x1a, 0x89, 0xa8, 0x80, 0x95, 0x22, 0xa7, 0x98, 0x4b, + 0x0f, 0xc8, 0x56, 0xcb, 0x1d, 0xa9, 0x09, 0xad, 0x44, 0xa9, 0xa0, 0x72, 0x01, 0x09, 0x2e, 0xd1, + 0xc6, 0xbb, 0x24, 0x56, 0x6c, 0x8f, 0xe5, 0xdd, 0x26, 0xe4, 0x11, 0x38, 0x20, 0x71, 0xe3, 0x41, + 0x78, 0x89, 0x1e, 0x7b, 0x44, 0x1c, 0x2a, 0x48, 0x5e, 0x04, 0x79, 0x6d, 0x37, 0x0e, 0x32, 0x12, + 0xf5, 0x6d, 0xc7, 0xfa, 0xe7, 0x9b, 0x7f, 0xff, 0x91, 0x17, 0x1e, 0x4d, 0xe6, 0x53, 0x6e, 0x09, + 0x49, 0x27, 0x3c, 0x12, 0xd6, 0x74, 0x7f, 0xc8, 0x25, 0xdd, 0xb7, 0xf8, 0x94, 0x07, 0x52, 0x98, + 0x61, 0x84, 0x12, 0xdb, 0xdb, 0xb1, 0xc4, 0x4c, 0x25, 0x66, 0x2a, 0xe9, 0x6c, 0x8f, 0x70, 0x84, + 0x4a, 0x60, 0xc5, 0xa7, 0x44, 0xdb, 0x29, 0xc6, 0x85, 0x34, 0xa2, 0x7e, 0x8a, 0xeb, 0x18, 0x85, + 0x92, 0x0c, 0xaf, 0x34, 0xc6, 0x77, 0x02, 0x77, 0x8f, 0x62, 0x0f, 0x6f, 0x43, 0x46, 0x25, 0x3f, + 0x53, 0xfd, 0xed, 0x43, 0x00, 0xf4, 0xd8, 0x20, 0xa1, 0x69, 0x64, 0x97, 0xec, 0xdd, 0x39, 0x78, + 0x68, 0x16, 0xb9, 0x33, 0x93, 0x8e, 0x5e, 0xed, 0xf2, 0xba, 0x5b, 0xb1, 0x9b, 0xe8, 0xb1, 0x15, + 0x22, 0xe0, 0xb3, 0x0c, 0x51, 0xfd, 0x7f, 0x44, 0xc0, 0x67, 0x29, 0x42, 0x83, 0x46, 0x48, 0xe7, + 0x1e, 0x52, 0xa6, 0x6d, 0xec, 0x92, 0xbd, 0xa6, 0x9d, 0x95, 0xc6, 0x17, 0x02, 0xf7, 0x72, 0xae, + 0xfb, 0xe8, 0xfb, 0xae, 0x10, 0x2e, 0x06, 0xed, 0xfb, 0x50, 0x4f, 0xf0, 0xca, 0x75, 0xd3, 0x4e, + 0xab, 0xf6, 0x03, 0x68, 0x84, 0x88, 0xde, 0xc0, 0x65, 0xca, 0x4b, 0xcd, 0xae, 0xc7, 0xe5, 0x0b, + 0xd6, 0xee, 0x03, 0x38, 0x37, 0xed, 0xc9, 0x9c, 0xde, 0xe3, 0xd8, 0xc9, 0xcf, 0xeb, 0xee, 0x8e, + 0x83, 0xc2, 0x47, 0x21, 0xd8, 0xc4, 0x74, 0xd1, 0xf2, 0xa9, 0x1c, 0x9b, 0xa7, 0x7c, 0x44, 0x9d, + 0xf9, 0x73, 0xee, 0xd8, 0xb9, 0x36, 0xe3, 0x1b, 0x01, 0x2d, 0xe7, 0xe7, 0x3c, 0x9e, 0x79, 0x1c, + 0x51, 0x47, 0x96, 0xb2, 0x74, 0x02, 0x2d, 0x25, 0x19, 0x7c, 0x4c, 0x11, 0xb7, 0xb1, 0xb5, 0x25, + 0xf2, 0xc3, 0x8d, 0xd7, 0xb0, 0xa3, 0x8c, 0xf5, 0x3d, 0xea, 0xfa, 0xab, 0x9c, 0x6c, 0x3e, 0xa3, + 0x11, 0x13, 0xff, 0xf4, 0xa6, 0x41, 0x83, 0xfa, 0x78, 0x11, 0xc8, 0x64, 0x75, 0x4d, 0x3b, 0x2b, + 0x8d, 0xcf, 0x55, 0xd8, 0x52, 0xc4, 0x13, 0x74, 0x83, 0x33, 0x44, 0x2f, 0x7f, 0x0f, 0xb2, 0x76, + 0x8f, 0x15, 0xbc, 0xba, 0x06, 0xd7, 0x01, 0xa6, 0xd4, 0xa3, 0x8c, 0x45, 0x5c, 0x88, 0x74, 0xb5, + 0xb9, 0x2f, 0x71, 0x5f, 0x32, 0x4d, 0xab, 0x25, 0xbc, 0xa4, 0xfa, 0x6b, 0x55, 0x9b, 0xa5, 0x56, + 0x55, 0x10, 0x6e, 0xbd, 0x74, 0xb8, 0x87, 0xd0, 0x52, 0x51, 0x9c, 0x72, 0x3a, 0xe5, 0xa5, 0xb2, + 0x30, 0x7e, 0x13, 0x00, 0xc5, 0x38, 0xf7, 0xa8, 0x18, 0xdf, 0x3e, 0xcb, 0x55, 0x56, 0x1b, 0x6b, + 0x59, 0x3d, 0x03, 0x10, 0x31, 0x71, 0x20, 0xe7, 0x21, 0x57, 0x39, 0xb6, 0x0e, 0xba, 0xc5, 0xbf, + 0x9f, 0x9a, 0xfc, 0x66, 0x1e, 0x72, 0xbb, 0x29, 0xb2, 0x63, 0x41, 0x4c, 0x9b, 0x65, 0x63, 0xea, + 0x1d, 0x5f, 0x2e, 0x74, 0x72, 0xb5, 0xd0, 0xc9, 0xaf, 0x85, 0x4e, 0xbe, 0x2e, 0xf5, 0xca, 0xd5, + 0x52, 0xaf, 0xfc, 0x58, 0xea, 0x95, 0x0f, 0x4f, 0x46, 0xae, 0x1c, 0x5f, 0x0c, 0x4d, 0x07, 0x7d, + 0xeb, 0xe5, 0xfb, 0x77, 0x47, 0xaf, 0xb8, 0x9c, 0x61, 0x34, 0xb1, 0x9c, 0x31, 0x75, 0x03, 0xeb, + 0xd3, 0xcd, 0xdb, 0x15, 0x5f, 0x42, 0x0c, 0xeb, 0xea, 0xc9, 0x7a, 0xfa, 0x27, 0x00, 0x00, 0xff, + 0xff, 0x4e, 0xa8, 0x49, 0x66, 0x4a, 0x05, 0x00, 0x00, } func (m *EventUpdateParams) Marshal() (dAtA []byte, err error) { @@ -819,6 +822,16 @@ func (m *EventSlash) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size := m.StakeFraction.Size() + i -= size + if _, err := m.StakeFraction.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a if m.SlashType != 0 { i = encodeVarintEvents(dAtA, i, uint64(m.SlashType)) i-- @@ -987,6 +1000,8 @@ func (m *EventSlash) Size() (n int) { if m.SlashType != 0 { n += 1 + sovEvents(uint64(m.SlashType)) } + l = m.StakeFraction.Size() + n += 1 + l + sovEvents(uint64(l)) return n } @@ -1967,6 +1982,40 @@ func (m *EventSlash) Unmarshal(dAtA []byte) error { break } } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakeFraction", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.StakeFraction.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) diff --git a/x/stakers/types/expected_keepers.go b/x/stakers/types/expected_keepers.go index b489cdd2..ab86eabb 100644 --- a/x/stakers/types/expected_keepers.go +++ b/x/stakers/types/expected_keepers.go @@ -1,11 +1,13 @@ package types import ( + "cosmossdk.io/math" poolTypes "github.com/KYVENetwork/chain/x/pool/types" sdk "github.com/cosmos/cosmos-sdk/types" ) type PoolKeeper interface { + GetMaxVotingPowerPerPool(ctx sdk.Context) (res math.LegacyDec) AssertPoolExists(ctx sdk.Context, poolId uint64) error GetPoolWithError(ctx sdk.Context, poolId uint64) (poolTypes.Pool, error) }