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

[Feature] minimum commission rate upgrade #47

Closed
wants to merge 12 commits into from
3 changes: 3 additions & 0 deletions app/ante/ante.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ante

import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
Expand All @@ -19,6 +20,7 @@ type HandlerOptions struct {
IBCkeeper *ibckeeper.Keeper
TxCounterStoreKey sdk.StoreKey
WasmConfig wasmTypes.WasmConfig
Cdc codec.BinaryCodec
}

// NewAnteHandler returns an AnteHandler that checks and increments sequence
Expand All @@ -44,6 +46,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {

anteDecorators := []sdk.AnteDecorator{
ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
NewMinCommissionDecorator(options.Cdc),
wasmkeeper.NewLimitSimulationGasDecorator(options.WasmConfig.SimulationGasLimit),
wasmkeeper.NewCountTXDecorator(options.TxCounterStoreKey),
ante.NewRejectExtensionOptionsDecorator(),
Expand Down
98 changes: 98 additions & 0 deletions app/ante/min_commission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package ante

import (
"fmt"

"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/authz"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

var (
// MinimumCommissionRate enforced minimum commission rate
// to avoid commission competition between validators.
MinimumCommissionRate = sdk.NewDecWithPrec(5, 2)
)

// MinCommissionDecorator checks whether the validator's commission rate
// is smaller than hard limit(= MinimumCommissionRate) or not
type MinCommissionDecorator struct {
cdc codec.BinaryCodec
}

// NewMinCommissionDecorator return MinCommissionDecorator instance
func NewMinCommissionDecorator(cdc codec.BinaryCodec) MinCommissionDecorator {
return MinCommissionDecorator{cdc}
}

// AnteHandle implement interface
func (min MinCommissionDecorator) AnteHandle(
ctx sdk.Context, tx sdk.Tx,
simulate bool, next sdk.AnteHandler,
) (newCtx sdk.Context, err error) {
msgs := tx.GetMsgs()

validMsg := func(m sdk.Msg) error {
switch msg := m.(type) {
case *stakingtypes.MsgCreateValidator:
// prevent new validators joining the set with
// commission set below 5%
c := msg.Commission
if c.Rate.LT(MinimumCommissionRate) {
return sdkerrors.Wrap(sdkerrors.ErrUnauthorized,
fmt.Sprintf("commission can't be lower than %s%%", MinimumCommissionRate.String()),
)
}
case *stakingtypes.MsgEditValidator:
// if commission rate is nil, it means only
// other fields are affected - skip
if msg.CommissionRate == nil {
break
}

if msg.CommissionRate.LT(MinimumCommissionRate) {
return sdkerrors.Wrap(sdkerrors.ErrUnauthorized,
fmt.Sprintf("commission can't be lower than %s%%", MinimumCommissionRate.String()),
)
}
}

return nil
}

validAuthz := func(execMsg *authz.MsgExec) error {
for _, v := range execMsg.Msgs {
var innerMsg sdk.Msg
err := min.cdc.UnpackAny(v, &innerMsg)
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "cannot unmarshal authz exec msgs")
}

err = validMsg(innerMsg)
if err != nil {
return err
}
}

return nil
}

for _, m := range msgs {
if msg, ok := m.(*authz.MsgExec); ok {
if err := validAuthz(msg); err != nil {
return ctx, err
}
continue
}

// validate normal msgs
err = validMsg(m)
if err != nil {
return ctx, err
}
}

return next(ctx, tx, simulate)
}
93 changes: 93 additions & 0 deletions app/ante/min_commission_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package ante_test

import (
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/authz"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

"github.com/terra-money/core/v2/app/ante"
)

func (suite *AnteTestSuite) TestMinCommission() {
suite.SetupTest(true) // setup
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()

// make block height non-zero to ensure account numbers part of signBytes
suite.ctx = suite.ctx.WithBlockHeight(1)

// keys and addresses
_, pub1, addr1 := testdata.KeyTestPubAddr()
_, _, addr2 := testdata.KeyTestPubAddr()

min := ante.NewMinCommissionDecorator(suite.app.AppCodec())
lowCommission := sdk.NewDecWithPrec(2, 2)
highCommission := sdk.NewDecWithPrec(5, 2)

// create validator
createValidator, err := stakingtypes.NewMsgCreateValidator(
sdk.ValAddress(addr1),
pub1,
sdk.NewInt64Coin("foo", 1),
stakingtypes.Description{},
stakingtypes.NewCommissionRates(
lowCommission,
sdk.NewDecWithPrec(100, 2), // 100%
sdk.NewDecWithPrec(1, 2), // 1%
),
sdk.NewInt(1),
)
suite.NoError(err)

antehandler := sdk.ChainAnteDecorators(min)

// with low commission
suite.txBuilder.SetMsgs(createValidator)
_, err = antehandler(suite.ctx, suite.txBuilder.GetTx(), false)
suite.Error(err)

execMsg := authz.NewMsgExec(addr1, []sdk.Msg{createValidator})
suite.txBuilder.SetMsgs(&execMsg)
_, err = antehandler(suite.ctx, suite.txBuilder.GetTx(), false)
suite.Error(err)

// with high commission
createValidator.Commission.Rate = highCommission
suite.txBuilder.SetMsgs(createValidator)
_, err = antehandler(suite.ctx, suite.txBuilder.GetTx(), false)
suite.NoError(err)

execMsg = authz.NewMsgExec(addr1, []sdk.Msg{createValidator})
suite.txBuilder.SetMsgs(&execMsg)
_, err = antehandler(suite.ctx, suite.txBuilder.GetTx(), false)
suite.NoError(err)

// edit validator
editValidator := stakingtypes.NewMsgEditValidator(
sdk.ValAddress(addr2),
stakingtypes.Description{},
&lowCommission,
nil,
)

// with low commission
suite.txBuilder.SetMsgs(editValidator)
_, err = antehandler(suite.ctx, suite.txBuilder.GetTx(), false)
suite.Error(err)

execMsg = authz.NewMsgExec(addr1, []sdk.Msg{editValidator})
suite.txBuilder.SetMsgs(&execMsg)
_, err = antehandler(suite.ctx, suite.txBuilder.GetTx(), false)
suite.Error(err)

// with high commission
editValidator.CommissionRate = &highCommission
suite.txBuilder.SetMsgs(editValidator)
_, err = antehandler(suite.ctx, suite.txBuilder.GetTx(), false)
suite.NoError(err)

execMsg = authz.NewMsgExec(addr1, []sdk.Msg{editValidator})
suite.txBuilder.SetMsgs(&execMsg)
_, err = antehandler(suite.ctx, suite.txBuilder.GetTx(), false)
suite.NoError(err)
}
1 change: 1 addition & 0 deletions app/ante/sigverify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ func (suite *AnteTestSuite) TestSigVerification_ExplicitAmino() {
SignModeHandler: txConfig.SignModeHandler(),
SigGasConsumer: cosmosante.DefaultSigVerificationGasConsumer,
},
Cdc: suite.app.AppCodec(),
},
)

Expand Down
11 changes: 10 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ import (

"github.com/terra-money/core/v2/app/ante"
terraappparams "github.com/terra-money/core/v2/app/params"
appupgrade "github.com/terra-money/core/v2/app/upgrade"
"github.com/terra-money/core/v2/app/wasmconfig"

// unnamed import of statik for swagger UI support
Expand Down Expand Up @@ -627,6 +628,7 @@ func NewTerraApp(
IBCkeeper: app.IBCKeeper,
TxCounterStoreKey: keys[wasm.StoreKey],
WasmConfig: wasmConfig.ToWasmConfig(),
Cdc: app.appCodec,
},
)
if err != nil {
Expand Down Expand Up @@ -788,7 +790,14 @@ func (app *TerraApp) RegisterTendermintService(clientCtx client.Context) {
}

// RegisterUpgradeHandlers returns upgrade handlers
func (app *TerraApp) RegisterUpgradeHandlers(cfg module.Configurator) {}
func (app *TerraApp) RegisterUpgradeHandlers(_ module.Configurator) {
app.UpgradeKeeper.SetUpgradeHandler(
appupgrade.UpgradeName,
appupgrade.CreateUpgradeHandler(
&app.StakingKeeper,
),
)
}

// RegisterSwaggerAPI registers swagger route with API Server
func RegisterSwaggerAPI(rtr *mux.Router) {
Expand Down
6 changes: 6 additions & 0 deletions app/upgrade/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package upgrade

const (
// UpgradeName gov proposal name
UpgradeName = "MinCommission"
)
40 changes: 40 additions & 0 deletions app/upgrade/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package upgrade

import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"

"github.com/terra-money/core/v2/app/ante"
)

// CreateUpgradeHandler make upgrade handler
func CreateUpgradeHandler(stakingKeeper *stakingkeeper.Keeper) upgradetypes.UpgradeHandler {
return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) {
allValidators := stakingKeeper.GetAllValidators(ctx)
for _, validator := range allValidators {
// increase commission rate
if validator.Commission.CommissionRates.Rate.LT(ante.MinimumCommissionRate) {
commission, err := stakingKeeper.UpdateValidatorCommission(ctx, validator, ante.MinimumCommissionRate)
if err != nil {
return nil, err
}

// call the before-modification hook since we're about to update the commission
stakingKeeper.BeforeValidatorModified(ctx, validator.GetOperator())

validator.Commission = commission
}

// increase max commission rate
if validator.Commission.CommissionRates.MaxRate.LT(ante.MinimumCommissionRate) {
validator.Commission.MaxRate = ante.MinimumCommissionRate
}

stakingKeeper.SetValidator(ctx, validator)
}

return vm, nil
}
}