Skip to content

Commit

Permalink
test: added effective stake tests
Browse files Browse the repository at this point in the history
  • Loading branch information
troykessler committed Dec 16, 2024
1 parent 2487d71 commit 29ff7b0
Show file tree
Hide file tree
Showing 2 changed files with 338 additions and 4 deletions.
24 changes: 20 additions & 4 deletions x/stakers/keeper/exported_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ func (k Keeper) GetDelegationOfPool(ctx sdk.Context, poolId uint64) uint64 {
return totalDelegation
}

// GetPoolTotalStake returns the amount in uykve which actively secures
// the given pool
func (k Keeper) GetPoolTotalStake(ctx sdk.Context, poolId uint64) (totalStake uint64) {
effectiveStakes := k.GetEffectiveValidatorStakes(ctx, poolId)
for _, stake := range effectiveStakes {
totalStake += stake
}
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) {
Expand Down Expand Up @@ -230,19 +240,23 @@ func (k Keeper) GetEffectiveValidatorStakes(ctx sdk.Context, poolId uint64, must
})

if totalStake == 0 || maxVotingPower.IsZero() {
return
return make(map[string]uint64)
}

if math.LegacyOneDec().Quo(maxVotingPower).GT(math.LegacyNewDec(int64(len(addresses)))) {
return make(map[string]uint64)
}

for i, validator := range validators {
if math.LegacyNewDec(int64(effectiveStakes[validator.Address])).QuoInt64(totalStake).GT(maxVotingPower) {
if math.LegacyNewDec(int64(effectiveStakes[validator.Address])).GT(maxVotingPower.MulInt64(totalStake)) {
amount := math.LegacyNewDec(int64(effectiveStakes[validator.Address])).Sub(maxVotingPower.MulInt64(totalStake)).TruncateInt64()

totalStakeRemainder -= int64(validator.Stake)
effectiveStakes[validator.Address] -= uint64(amount)

if totalStakeRemainder > 0 {
for _, v := range validators[i+1:] {
effectiveStakes[v.Address] += uint64(math.LegacyNewDec(int64(effectiveStakes[v.Address])).QuoInt64(totalStakeRemainder).MulInt64(amount).TruncateInt64())
effectiveStakes[v.Address] += uint64(math.LegacyNewDec(int64(v.Stake)).QuoInt64(totalStakeRemainder).MulInt64(amount).TruncateInt64())
}
}
}
Expand All @@ -254,12 +268,14 @@ func (k Keeper) GetEffectiveValidatorStakes(ctx sdk.Context, poolId uint64, must
for i := len(validators) - 1; i >= 0; i-- {
if effectiveStakes[validators[i].Address] > 0 {
scaleFactor = math.LegacyNewDec(int64(validators[i].Stake)).QuoInt64(int64(effectiveStakes[validators[i].Address]))
break
}
}

// scale all effective stakes down to scale factor
for _, validator := range validators {
effectiveStakes[validator.Address] = uint64(scaleFactor.MulInt64(int64(effectiveStakes[validator.Address])).TruncateInt64())
// TODO: is rounding here fine?
effectiveStakes[validator.Address] = uint64(scaleFactor.MulInt64(int64(effectiveStakes[validator.Address])).RoundInt64())
}

return
Expand Down
318 changes: 318 additions & 0 deletions x/stakers/keeper/keeper_suite_effective_stake_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
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
*/

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
effectiveStakes := s.App().StakersKeeper.GetEffectiveValidatorStakes(s.Ctx(), 0)

// ASSERT
Expect(len(effectiveStakes)).To(Equal(3))
Expect(effectiveStakes[i.STAKER_0]).To(Equal(100 * i.KYVE))
Expect(effectiveStakes[i.STAKER_1]).To(Equal(100 * i.KYVE))
Expect(effectiveStakes[i.STAKER_2]).To(Equal(100 * i.KYVE))

Expect(s.App().StakersKeeper.GetPoolTotalStake(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"),
})

// ACT
effectiveStakes := s.App().StakersKeeper.GetEffectiveValidatorStakes(s.Ctx(), 0)

// ASSERT
Expect(len(effectiveStakes)).To(Equal(3))
Expect(effectiveStakes[i.STAKER_0]).To(Equal(100 * i.KYVE))
Expect(effectiveStakes[i.STAKER_1]).To(Equal(200 * i.KYVE))
Expect(effectiveStakes[i.STAKER_2]).To(Equal(100 * i.KYVE))

Expect(s.App().StakersKeeper.GetPoolTotalStake(s.Ctx(), 0)).To(Equal(400 * i.KYVE))
})

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"),
})

// ACT
effectiveStakes := s.App().StakersKeeper.GetEffectiveValidatorStakes(s.Ctx(), 0)

// ASSERT
Expect(len(effectiveStakes)).To(Equal(3))
Expect(effectiveStakes[i.STAKER_0]).To(Equal(140 * i.KYVE))
Expect(effectiveStakes[i.STAKER_1]).To(Equal(140 * i.KYVE))
Expect(effectiveStakes[i.STAKER_2]).To(Equal(120 * i.KYVE))

Expect(s.App().StakersKeeper.GetPoolTotalStake(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"),
})

// ACT
effectiveStakes := s.App().StakersKeeper.GetEffectiveValidatorStakes(s.Ctx(), 0)

// ASSERT
Expect(len(effectiveStakes)).To(BeZero())
Expect(s.App().StakersKeeper.GetPoolTotalStake(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"),
})

// ACT
effectiveStakes := s.App().StakersKeeper.GetEffectiveValidatorStakes(s.Ctx(), 0)

// ASSERT
Expect(len(effectiveStakes)).To(Equal(3))
Expect(effectiveStakes[i.STAKER_0]).To(Equal(100 * i.KYVE))
Expect(effectiveStakes[i.STAKER_1]).To(Equal(0 * i.KYVE))
Expect(effectiveStakes[i.STAKER_2]).To(Equal(100 * i.KYVE))

Expect(s.App().StakersKeeper.GetPoolTotalStake(s.Ctx(), 0)).To(Equal(200 * i.KYVE))
})

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"),
})

// ACT
effectiveStakes := s.App().StakersKeeper.GetEffectiveValidatorStakes(s.Ctx(), 0)

// ASSERT
Expect(len(effectiveStakes)).To(BeZero())
Expect(s.App().StakersKeeper.GetPoolTotalStake(s.Ctx(), 0)).To(BeZero())
})
})

0 comments on commit 29ff7b0

Please sign in to comment.