Skip to content

Commit

Permalink
feat: [v0.45.12] add minimum commission rate to x/staking (#182)
Browse files Browse the repository at this point in the history
Co-authored-by: Hansol Lee <38912532+hansol-medi@users.noreply.github.com>
  • Loading branch information
Youngjoon Lee and 0xHansLee authored Feb 13, 2023
1 parent 82ce891 commit 33617b5
Show file tree
Hide file tree
Showing 15 changed files with 905 additions and 742 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ BINDIR ?= $(GOPATH)/bin
BUILDDIR ?= $(CURDIR)/build
SIMAPP = ./simapp
MOCKS_DIR = $(CURDIR)/tests/mocks
HTTPS_GIT := https://github.com/cosmos/cosmos-sdk.git
HTTPS_GIT := https://github.com/medibloc/cosmos-sdk.git
DOCKER := $(shell which docker)
DOCKER_BUF := $(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace bufbuild/buf
DOCKER_BUF := $(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace bufbuild/buf:0.56.0

export GO111MODULE = on

Expand Down Expand Up @@ -396,7 +396,7 @@ proto-lint:
@$(DOCKER_BUF) lint --error-format=json

proto-check-breaking:
@$(DOCKER_BUF) breaking --against $(HTTPS_GIT)#branch=master
@$(DOCKER_BUF) breaking --against $(HTTPS_GIT)#branch=v0.45.12-panacea


TM_URL = https://raw.githubusercontent.com/tendermint/tendermint/v0.34.22/proto/tendermint
Expand Down
1 change: 1 addition & 0 deletions docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -6728,6 +6728,7 @@ Params defines the parameters for the staking module.
| `max_entries` | [uint32](#uint32) | | max_entries is the max entries for either unbonding delegation or redelegation (per pair/trio). |
| `historical_entries` | [uint32](#uint32) | | historical_entries is the number of historical entries to persist. |
| `bond_denom` | [string](#string) | | bond_denom defines the bondable coin denomination. |
| `min_commission_rate` | [string](#string) | | min_commission_rate is the chain-wide minimum commission rate that a validator can charge their delegators |



Expand Down
6 changes: 6 additions & 0 deletions proto/cosmos/staking/v1beta1/staking.proto
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,12 @@ message Params {
uint32 historical_entries = 4 [(gogoproto.moretags) = "yaml:\"historical_entries\""];
// bond_denom defines the bondable coin denomination.
string bond_denom = 5 [(gogoproto.moretags) = "yaml:\"bond_denom\""];
// min_commission_rate is the chain-wide minimum commission rate that a validator can charge their delegators
string min_commission_rate = 6 [
(gogoproto.moretags) = "yaml:\"min_commission_rate\"",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}

// DelegationResponse is equivalent to Delegation except that it contains a
Expand Down
3 changes: 2 additions & 1 deletion x/staking/client/testutil/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -876,12 +876,13 @@ func (s *IntegrationTestSuite) TestGetCmdQueryParams() {
historical_entries: 10000
max_entries: 7
max_validators: 100
min_commission_rate: "0.000000000000000000"
unbonding_time: 1814400s`,
},
{
"with json output",
[]string{fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
`{"unbonding_time":"1814400s","max_validators":100,"max_entries":7,"historical_entries":10000,"bond_denom":"stake"}`,
`{"unbonding_time":"1814400s","max_validators":100,"max_entries":7,"historical_entries":10000,"bond_denom":"stake","min_commission_rate":"0.000000000000000000"}`,
},
}
for _, tc := range testCases {
Expand Down
5 changes: 5 additions & 0 deletions x/staking/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ func (k msgServer) CreateValidator(goCtx context.Context, msg *types.MsgCreateVa
if err != nil {
return nil, err
}

if msg.Commission.Rate.LT(k.MinCommissionRate(ctx)) {
return nil, sdkerrors.Wrapf(types.ErrCommissionLTMinRate, "cannot set validator commission to less than minimum rate of %s", k.MinCommissionRate(ctx))
}

commission := types.NewCommissionWithTime(
msg.Commission.Rate, msg.Commission.MaxRate,
msg.Commission.MaxChangeRate, ctx.BlockHeader().Time,
Expand Down
40 changes: 40 additions & 0 deletions x/staking/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package keeper_test

import (
"testing"

"github.com/stretchr/testify/require"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

func TestCreateValidatorWithLessThanMinCommission(t *testing.T) {
PKS := simapp.CreateTestPubKeys(1)
valConsPk1 := PKS[0]

app := simapp.Setup(false)
ctx := app.BaseApp.NewContext(false, tmproto.Header{})
addrs := simapp.AddTestAddrs(app, ctx, 3, sdk.NewInt(1234))
// set min commission rate to non-zero
params := app.StakingKeeper.GetParams(ctx)
params.MinCommissionRate = sdk.NewDecWithPrec(1, 2)
app.StakingKeeper.SetParams(ctx, params)

// create validator with 0% commission
msg, err := stakingtypes.NewMsgCreateValidator(
sdk.ValAddress(addrs[0]),
valConsPk1,
sdk.NewInt64Coin(sdk.DefaultBondDenom, 100),
stakingtypes.Description{},
stakingtypes.NewCommissionRates(sdk.NewDec(0), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)),
sdk.OneInt())
require.NoError(t, err)

sh := staking.NewHandler(app.StakingKeeper)
_, err = sh(ctx, msg)
require.Error(t, err)
}
7 changes: 7 additions & 0 deletions x/staking/keeper/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ func (k Keeper) BondDenom(ctx sdk.Context) (res string) {
return
}

// MinCommissionRate - Minimum validator commission rate
func (k Keeper) MinCommissionRate(ctx sdk.Context) (res sdk.Dec) {
k.paramstore.Get(ctx, types.KeyMinCommissionRate, &res)
return
}

// PowerReduction - is the amount of staking tokens required for 1 unit of consensus-engine power.
// Currently, this returns a global variable that the app developer can tweak.
// TODO: we might turn this into an on-chain param:
Expand All @@ -55,6 +61,7 @@ func (k Keeper) GetParams(ctx sdk.Context) types.Params {
k.MaxEntries(ctx),
k.HistoricalEntries(ctx),
k.BondDenom(ctx),
k.MinCommissionRate(ctx),
)
}

Expand Down
4 changes: 4 additions & 0 deletions x/staking/keeper/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ func (k Keeper) UpdateValidatorCommission(ctx sdk.Context,
return commission, err
}

if newRate.LT(k.MinCommissionRate(ctx)) {
return commission, fmt.Errorf("cannot set validator commission to less than minimum rate of %s", k.MinCommissionRate(ctx))
}

commission.Rate = newRate
commission.UpdateTime = blockTime

Expand Down
5 changes: 5 additions & 0 deletions x/staking/keeper/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,10 @@ func TestUpdateValidatorCommission(t *testing.T) {
app, ctx, _, addrVals := bootstrapValidatorTest(t, 1000, 20)
ctx = ctx.WithBlockHeader(tmproto.Header{Time: time.Now().UTC()})

params := app.StakingKeeper.GetParams(ctx)
params.MinCommissionRate = sdk.MustNewDecFromStr("0.05")
app.StakingKeeper.SetParams(ctx, params)

commission1 := types.NewCommissionWithTime(
sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(3, 1),
sdk.NewDecWithPrec(1, 1), time.Now().UTC().Add(time.Duration(-1)*time.Hour),
Expand All @@ -1067,6 +1071,7 @@ func TestUpdateValidatorCommission(t *testing.T) {
{val2, sdk.NewDecWithPrec(-1, 1), true},
{val2, sdk.NewDecWithPrec(4, 1), true},
{val2, sdk.NewDecWithPrec(3, 1), true},
{val2, sdk.NewDecWithPrec(1, 2), true}, // Commission rate below minimum
{val2, sdk.NewDecWithPrec(2, 1), false},
}

Expand Down
50 changes: 26 additions & 24 deletions x/staking/legacy/v040/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ import (
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
v034staking "github.com/cosmos/cosmos-sdk/x/staking/legacy/v034"
v038staking "github.com/cosmos/cosmos-sdk/x/staking/legacy/v038"
v040staking "github.com/cosmos/cosmos-sdk/x/staking/types"
)

func migrateBondStatus(oldStatus v034staking.BondStatus) BondStatus {
func migrateBondStatus(oldStatus v034staking.BondStatus) v040staking.BondStatus {
switch oldStatus {
case v034staking.Unbonded:
return Unbonded
return v040staking.Unbonded

case v034staking.Unbonding:
return Unbonding
return v040staking.Unbonding

case v034staking.Bonded:
return Bonded
return v040staking.Bonded

default:
panic(fmt.Errorf("invalid bond status %d", oldStatus))
Expand All @@ -30,29 +31,29 @@ func migrateBondStatus(oldStatus v034staking.BondStatus) BondStatus {
// - Convert addresses from bytes to bech32 strings.
// - Update BondStatus staking constants.
// - Re-encode in v0.40 GenesisState.
func Migrate(stakingState v038staking.GenesisState) *GenesisState {
newLastValidatorPowers := make([]LastValidatorPower, len(stakingState.LastValidatorPowers))
func Migrate(stakingState v038staking.GenesisState) *v040staking.GenesisState {
newLastValidatorPowers := make([]v040staking.LastValidatorPower, len(stakingState.LastValidatorPowers))
for i, oldLastValidatorPower := range stakingState.LastValidatorPowers {
newLastValidatorPowers[i] = LastValidatorPower{
newLastValidatorPowers[i] = v040staking.LastValidatorPower{
Address: oldLastValidatorPower.Address.String(),
Power: oldLastValidatorPower.Power,
}
}

newValidators := make([]Validator, len(stakingState.Validators))
newValidators := make([]v040staking.Validator, len(stakingState.Validators))
for i, oldValidator := range stakingState.Validators {
pkAny, err := codectypes.NewAnyWithValue(oldValidator.ConsPubKey)
if err != nil {
panic(fmt.Sprintf("Can't pack validator consensus PK as Any: %s", err))
}
newValidators[i] = Validator{
newValidators[i] = v040staking.Validator{
OperatorAddress: oldValidator.OperatorAddress.String(),
ConsensusPubkey: pkAny,
Jailed: oldValidator.Jailed,
Status: migrateBondStatus(oldValidator.Status),
Tokens: oldValidator.Tokens,
DelegatorShares: oldValidator.DelegatorShares,
Description: Description{
Description: v040staking.Description{
Moniker: oldValidator.Description.Moniker,
Identity: oldValidator.Description.Identity,
Website: oldValidator.Description.Website,
Expand All @@ -61,8 +62,8 @@ func Migrate(stakingState v038staking.GenesisState) *GenesisState {
},
UnbondingHeight: oldValidator.UnbondingHeight,
UnbondingTime: oldValidator.UnbondingCompletionTime,
Commission: Commission{
CommissionRates: CommissionRates{
Commission: v040staking.Commission{
CommissionRates: v040staking.CommissionRates{
Rate: oldValidator.Commission.Rate,
MaxRate: oldValidator.Commission.MaxRate,
MaxChangeRate: oldValidator.Commission.MaxChangeRate,
Expand All @@ -73,61 +74,62 @@ func Migrate(stakingState v038staking.GenesisState) *GenesisState {
}
}

newDelegations := make([]Delegation, len(stakingState.Delegations))
newDelegations := make([]v040staking.Delegation, len(stakingState.Delegations))
for i, oldDelegation := range stakingState.Delegations {
newDelegations[i] = Delegation{
newDelegations[i] = v040staking.Delegation{
DelegatorAddress: oldDelegation.DelegatorAddress.String(),
ValidatorAddress: oldDelegation.ValidatorAddress.String(),
Shares: oldDelegation.Shares,
}
}

newUnbondingDelegations := make([]UnbondingDelegation, len(stakingState.UnbondingDelegations))
newUnbondingDelegations := make([]v040staking.UnbondingDelegation, len(stakingState.UnbondingDelegations))
for i, oldUnbondingDelegation := range stakingState.UnbondingDelegations {
newEntries := make([]UnbondingDelegationEntry, len(oldUnbondingDelegation.Entries))
newEntries := make([]v040staking.UnbondingDelegationEntry, len(oldUnbondingDelegation.Entries))
for j, oldEntry := range oldUnbondingDelegation.Entries {
newEntries[j] = UnbondingDelegationEntry{
newEntries[j] = v040staking.UnbondingDelegationEntry{
CreationHeight: oldEntry.CreationHeight,
CompletionTime: oldEntry.CompletionTime,
InitialBalance: oldEntry.InitialBalance,
Balance: oldEntry.Balance,
}
}

newUnbondingDelegations[i] = UnbondingDelegation{
newUnbondingDelegations[i] = v040staking.UnbondingDelegation{
DelegatorAddress: oldUnbondingDelegation.DelegatorAddress.String(),
ValidatorAddress: oldUnbondingDelegation.ValidatorAddress.String(),
Entries: newEntries,
}
}

newRedelegations := make([]Redelegation, len(stakingState.Redelegations))
newRedelegations := make([]v040staking.Redelegation, len(stakingState.Redelegations))
for i, oldRedelegation := range stakingState.Redelegations {
newEntries := make([]RedelegationEntry, len(oldRedelegation.Entries))
newEntries := make([]v040staking.RedelegationEntry, len(oldRedelegation.Entries))
for j, oldEntry := range oldRedelegation.Entries {
newEntries[j] = RedelegationEntry{
newEntries[j] = v040staking.RedelegationEntry{
CreationHeight: oldEntry.CreationHeight,
CompletionTime: oldEntry.CompletionTime,
InitialBalance: oldEntry.InitialBalance,
SharesDst: oldEntry.SharesDst,
}
}

newRedelegations[i] = Redelegation{
newRedelegations[i] = v040staking.Redelegation{
DelegatorAddress: oldRedelegation.DelegatorAddress.String(),
ValidatorSrcAddress: oldRedelegation.ValidatorSrcAddress.String(),
ValidatorDstAddress: oldRedelegation.ValidatorDstAddress.String(),
Entries: newEntries,
}
}

return &GenesisState{
Params: Params{
return &v040staking.GenesisState{
Params: v040staking.Params{
UnbondingTime: stakingState.Params.UnbondingTime,
MaxValidators: uint32(stakingState.Params.MaxValidators),
MaxEntries: uint32(stakingState.Params.MaxEntries),
HistoricalEntries: uint32(stakingState.Params.HistoricalEntries),
BondDenom: stakingState.Params.BondDenom,
MinCommissionRate: v040staking.DefaultMinCommissionRate,
},
LastTotalPower: stakingState.LastTotalPower,
LastValidatorPowers: newLastValidatorPowers,
Expand Down
1 change: 1 addition & 0 deletions x/staking/legacy/v040/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func TestMigrate(t *testing.T) {
"historical_entries": 0,
"max_entries": 0,
"max_validators": 0,
"min_commission_rate": "0.000000000000000000",
"unbonding_time": "0s"
},
"redelegations": [],
Expand Down
12 changes: 10 additions & 2 deletions x/staking/simulation/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
unbondingTime = "unbonding_time"
maxValidators = "max_validators"
historicalEntries = "historical_entries"
minCommissionRate = "min_commission_rate"
)

// genUnbondingTime returns randomized UnbondingTime
Expand All @@ -43,6 +44,7 @@ func RandomizedGenState(simState *module.SimulationState) {
unbondTime time.Duration
maxVals uint32
histEntries uint32
minComRate sdk.Dec
)

simState.AppParams.GetOrGenerate(
Expand All @@ -60,10 +62,15 @@ func RandomizedGenState(simState *module.SimulationState) {
func(r *rand.Rand) { histEntries = getHistEntries(r) },
)

simState.AppParams.GetOrGenerate(
simState.Cdc, minCommissionRate, &minComRate, simState.Rand,
func(r *rand.Rand) { minComRate = sdk.NewDec(0) },
)

// NOTE: the slashing module need to be defined after the staking module on the
// NewSimulationManager constructor for this to work
simState.UnbondTime = unbondTime
params := types.NewParams(simState.UnbondTime, maxVals, 7, histEntries, sdk.DefaultBondDenom)
params := types.NewParams(simState.UnbondTime, maxVals, 7, histEntries, sdk.DefaultBondDenom, minComRate)

// validators & delegations
var (
Expand All @@ -78,8 +85,9 @@ func RandomizedGenState(simState *module.SimulationState) {
valAddrs[i] = valAddr

maxCommission := sdk.NewDecWithPrec(int64(simulation.RandIntBetween(simState.Rand, 1, 100)), 2)
curCommissionRate := simulation.RandomDecAmount(simState.Rand, maxCommission.Sub(minComRate)).Add(minComRate)
commission := types.NewCommission(
simulation.RandomDecAmount(simState.Rand, maxCommission),
curCommissionRate,
maxCommission,
simulation.RandomDecAmount(simState.Rand, maxCommission),
)
Expand Down
1 change: 1 addition & 0 deletions x/staking/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@ var (
ErrInvalidHistoricalInfo = sdkerrors.Register(ModuleName, 37, "invalid historical info")
ErrNoHistoricalInfo = sdkerrors.Register(ModuleName, 38, "no historical info found")
ErrEmptyValidatorPubKey = sdkerrors.Register(ModuleName, 39, "empty validator public key")
ErrCommissionLTMinRate = sdkerrors.Register(ModuleName, 40, "commission cannot be less than min rate")
)
Loading

0 comments on commit 33617b5

Please sign in to comment.