Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: set validator minimum commissions to at least 5% in upgrade handler #1761

Merged
merged 8 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (community) [#1706] Add disable inflation upgrade to begin blocker that updates x/mint and x/kavadist params
- (community) [#1729] Consolidate community funds from `x/distribution` and `x/kavadist` to `x/community`
- (community) [#1752] Set `x/distribution` CommunityTax to zero on inflation disable upgrade
- (staking) [#1761] Set validator minimum commission to 5% for all validators under 5%

## [v0.24.0]

Expand Down Expand Up @@ -295,6 +296,7 @@ the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.38.4/CHANGELOG.md).
- [#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run
large-scale simulations remotely using aws-batch

[#1761]: https://github.com/Kava-Labs/kava/pull/1761
[#1752]: https://github.com/Kava-Labs/kava/pull/1752
[#1729]: https://github.com/Kava-Labs/kava/pull/1729
[#1745]: https://github.com/Kava-Labs/kava/pull/1745
Expand Down
64 changes: 64 additions & 0 deletions app/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"

communitytypes "github.com/kava-labs/kava/x/community/types"
Expand Down Expand Up @@ -52,6 +53,9 @@ var (
sdkmath.LegacyNewDec(0), // stakingRewardsPerSecond
sdkmath.LegacyNewDec(1000), // upgradeTimeSetstakingRewardsPerSecond
)

// ValidatorMinimumCommission is the new 5% minimum commission rate for validators
ValidatorMinimumCommission = sdk.NewDecWithPrec(5, 2)
)

// RegisterUpgradeHandlers registers the upgrade handlers for the app.
Expand Down Expand Up @@ -109,6 +113,8 @@ func upgradeHandler(
return toVM, err
}

UpdateValidatorMinimumCommission(ctx, app)

app.communityKeeper.SetParams(ctx, communityParams)
app.Logger().Info(
"initialized x/community params",
Expand All @@ -120,3 +126,61 @@ func upgradeHandler(
return toVM, nil
}
}

// UpdateValidatorMinimumCommission updates the commission rate for all
// validators to be at least the new min commission rate, and sets the minimum
// commission rate in the staking params.
func UpdateValidatorMinimumCommission(
ctx sdk.Context,
app App,
) {
resultCount := make(map[stakingtypes.BondStatus]int)

// Iterate over *all* validators including inactive
app.stakingKeeper.IterateValidators(
ctx,
func(index int64, validator stakingtypes.ValidatorI) (stop bool) {
// Skip if validator commission is already >= 5%
if validator.GetCommission().GTE(ValidatorMinimumCommission) {
return false
}

val, ok := validator.(stakingtypes.Validator)
if !ok {
panic("expected stakingtypes.Validator")
drklee3 marked this conversation as resolved.
Show resolved Hide resolved
}

// Set minimum commission rate to 5%, when commission is < 5%
val.Commission.Rate = ValidatorMinimumCommission
drklee3 marked this conversation as resolved.
Show resolved Hide resolved
app.stakingKeeper.SetValidator(ctx, val)
drklee3 marked this conversation as resolved.
Show resolved Hide resolved

// Keep track of counts just for logging purposes
switch val.GetStatus() {
case stakingtypes.Bonded:
resultCount[stakingtypes.Bonded]++
case stakingtypes.Unbonded:
resultCount[stakingtypes.Unbonded]++
case stakingtypes.Unbonding:
resultCount[stakingtypes.Unbonding]++
}

return false
},
)

app.Logger().Info(
"updated validator minimum commission rate for all existing validators",
stakingtypes.BondStatusBonded, resultCount[stakingtypes.Bonded],
stakingtypes.BondStatusUnbonded, resultCount[stakingtypes.Unbonded],
stakingtypes.BondStatusUnbonding, resultCount[stakingtypes.Unbonding],
)

stakingParams := app.stakingKeeper.GetParams(ctx)
stakingParams.MinCommissionRate = ValidatorMinimumCommission
app.stakingKeeper.SetParams(ctx, stakingParams)

app.Logger().Info(
"updated x/staking params minimum commission rate",
"MinCommissionRate", stakingParams.MinCommissionRate,
)
}
104 changes: 104 additions & 0 deletions app/upgrades_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import (
"testing"

sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/evmos/ethermint/crypto/ethsecp256k1"
"github.com/kava-labs/kava/app"
"github.com/stretchr/testify/require"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
tmtime "github.com/tendermint/tendermint/types/time"
)

func TestUpgradeCommunityParams_Mainnet(t *testing.T) {
Expand Down Expand Up @@ -39,3 +44,102 @@ func TestUpgradeCommunityParams_Testnet(t *testing.T) {
"testnet kava per second should be correct",
)
}

func TestUpdateValidatorMinimumCommission(t *testing.T) {
tApp := app.NewTestApp()
tApp.InitializeFromGenesisStates()
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})

sk := tApp.GetStakingKeeper()
stakingParams := sk.GetParams(ctx)
stakingParams.MinCommissionRate = sdk.ZeroDec()
sk.SetParams(ctx, stakingParams)

// Set some validators with varying commission rates

vals := []struct {
operatorAddr sdk.ValAddress
consPriv *ethsecp256k1.PrivKey
commissionRate sdk.Dec
}{
{
operatorAddr: sdk.ValAddress("val0"),
consPriv: generateConsKey(t),
commissionRate: sdk.ZeroDec(),
},
{
operatorAddr: sdk.ValAddress("val1"),
consPriv: generateConsKey(t),
commissionRate: sdk.MustNewDecFromStr("0.01"),
},
{
operatorAddr: sdk.ValAddress("val2"),
consPriv: generateConsKey(t),
commissionRate: sdk.MustNewDecFromStr("0.05"),
},
{
operatorAddr: sdk.ValAddress("val3"),
consPriv: generateConsKey(t),
commissionRate: sdk.MustNewDecFromStr("0.06"),
},
{
operatorAddr: sdk.ValAddress("val4"),
consPriv: generateConsKey(t),
commissionRate: sdk.MustNewDecFromStr("0.5"),
},
}

for _, v := range vals {
val, err := stakingtypes.NewValidator(
v.operatorAddr,
v.consPriv.PubKey(),
stakingtypes.Description{},
)
require.NoError(t, err)
val.Commission.Rate = v.commissionRate

err = sk.SetValidatorByConsAddr(ctx, val)
require.NoError(t, err)
sk.SetValidator(ctx, val)
}

require.NotPanics(
t, func() {
app.UpdateValidatorMinimumCommission(ctx, tApp.App)
},
)

stakingParamsAfter := sk.GetParams(ctx)
require.Equal(t, stakingParamsAfter.MinCommissionRate, app.ValidatorMinimumCommission)

// Check that all validators have a commission rate >= 5%
count := 0
sk.IterateValidators(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) {
require.True(
t,
validator.GetCommission().GTE(app.ValidatorMinimumCommission),
"commission rate should be >= 5%",
)

count++
return false
})

require.Equal(
t,
len(vals)+1, // InitializeFromGenesisStates adds a validator
count,
"validator count should match test validators",
)
}

func generateConsKey(
t *testing.T,
) *ethsecp256k1.PrivKey {
t.Helper()

key, err := ethsecp256k1.GenerateKey()
require.NoError(t, err)

return key
}
68 changes: 68 additions & 0 deletions tests/e2e/e2e_upgrade_min_commission_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package e2e_test

import (
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking/types"

"github.com/kava-labs/kava/tests/util"
)

func (suite *IntegrationTestSuite) TestValMinCommission() {
suite.SkipIfUpgradeDisabled()

beforeUpgradeCtx := util.CtxAtHeight(suite.UpgradeHeight - 1)
afterUpgradeCtx := util.CtxAtHeight(suite.UpgradeHeight)

suite.Run("before upgrade", func() {
// Before params
beforeParams, err := suite.Kava.Staking.Params(beforeUpgradeCtx, &types.QueryParamsRequest{})
suite.Require().NoError(err)

suite.Require().Equal(
sdkmath.LegacyZeroDec().String(),
beforeParams.Params.MinCommissionRate.String(),
"min commission rate should be 0%% before upgrade",
)

// Before validators
beforeValidators, err := suite.Kava.Staking.Validators(beforeUpgradeCtx, &types.QueryValidatorsRequest{})
suite.Require().NoError(err)

for _, val := range beforeValidators.Validators {
expectedRate := sdkmath.LegacyMustNewDecFromStr("0.1")

suite.Require().Equalf(
expectedRate.String(),
val.Commission.CommissionRates.Rate.String(),
"validator %s should have commission rate of %s before upgrade",
val.OperatorAddress,
expectedRate,
)
}
})

suite.Run("after upgrade", func() {
// After params
afterParams, err := suite.Kava.Staking.Params(afterUpgradeCtx, &types.QueryParamsRequest{})
suite.Require().NoError(err)

suite.Require().Equal(
sdkmath.LegacyMustNewDecFromStr("0.05").String(),
afterParams.Params.MinCommissionRate.String(),
"min commission rate should be 5%% after upgrade",
)

// After validators
afterValidators, err := suite.Kava.Staking.Validators(afterUpgradeCtx, &types.QueryValidatorsRequest{})
suite.Require().NoError(err)

for _, val := range afterValidators.Validators {
suite.Require().Truef(
val.Commission.CommissionRates.Rate.GTE(sdk.MustNewDecFromStr("0.05")),
"validator %s should have commission rate of at least 5%%",
val.OperatorAddress,
)
}
})
}