diff --git a/x/stakers/keeper/exported_functions.go b/x/stakers/keeper/exported_functions.go index 0a8e02f0..0e74ad19 100644 --- a/x/stakers/keeper/exported_functions.go +++ b/x/stakers/keeper/exported_functions.go @@ -193,7 +193,7 @@ func (k Keeper) GetValidatorPoolStakes(ctx sdk.Context, poolId uint64, mustInclu totalStakeRemainder := totalStake // sort descending based on stake - sort.Slice(validators, func(i, j int) bool { + sort.SliceStable(validators, func(i, j int) bool { return validators[i].Stake > validators[j].Stake }) @@ -202,6 +202,8 @@ func (k Keeper) GetValidatorPoolStakes(ctx sdk.Context, poolId uint64, mustInclu 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)) { @@ -220,9 +222,20 @@ func (k Keeper) GetValidatorPoolStakes(ctx sdk.Context, poolId uint64, mustInclu 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 } } + // 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 @@ -241,8 +254,14 @@ func (k Keeper) GetValidatorPoolStakes(ctx sdk.Context, poolId uint64, mustInclu } // scale all effective stakes down to scale factor - for _, validator := range validators { - stakes[validator.Address] = uint64(scaleFactor.MulInt64(int64(stakes[validator.Address])).RoundInt64()) + 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])).RoundInt64()) + } } // the result is a map which contains the effective stake for every validator in a pool. The effective stake diff --git a/x/stakers/keeper/keeper_suite_effective_stake_test.go b/x/stakers/keeper/keeper_suite_effective_stake_test.go index 991b7d41..e68ea9a4 100644 --- a/x/stakers/keeper/keeper_suite_effective_stake_test.go +++ b/x/stakers/keeper/keeper_suite_effective_stake_test.go @@ -139,10 +139,10 @@ var _ = Describe("keeper_suite_effective_stake_test.go", Ordered, func() { 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)) + 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)) + 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() { @@ -268,11 +268,11 @@ var _ = Describe("keeper_suite_effective_stake_test.go", Ordered, func() { // 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_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)) + 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() { @@ -459,10 +459,10 @@ var _ = Describe("keeper_suite_effective_stake_test.go", Ordered, func() { 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)) + 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)) + 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() { @@ -504,11 +504,11 @@ var _ = Describe("keeper_suite_effective_stake_test.go", Ordered, func() { // 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(23333333334))) - Expect(s.App().StakersKeeper.GetValidatorPoolStake(s.Ctx(), i.STAKER_1, 0)).To(Equal(uint64(23333333334))) + 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(66666666668))) + 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() {