diff --git a/CHANGELOG.md b/CHANGELOG.md index 51fa8261..0fab74c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,11 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog +## [Unreleased] + +### Improvements + +- (chore) [#157](https://github.com/realiotech/realio-network/pull/157): update the validators' commissions if their commissions are less than 5% ## [v0.9.0] - 2024-03-27 diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go index 5c7d1845..17e1adf7 100644 --- a/app/ante/utils_test.go +++ b/app/ante/utils_test.go @@ -72,7 +72,7 @@ func (suite *AnteTestSuite) SetupTest() { consAddress := sdk.ConsAddress(privCons.PubKey().Address()) isCheckTx := false - suite.app = app.Setup(isCheckTx, feemarkettypes.DefaultGenesisState()) + suite.app = app.Setup(isCheckTx, feemarkettypes.DefaultGenesisState(), 1) suite.Require().NotNil(suite.app.AppCodec()) suite.ctx = suite.app.BaseApp.NewContext(isCheckTx, tmproto.Header{ diff --git a/app/fork_test.go b/app/fork_test.go index 7ea76277..6466ddb2 100644 --- a/app/fork_test.go +++ b/app/fork_test.go @@ -13,7 +13,7 @@ import ( ) func TestFork(t *testing.T) { - realio := Setup(false, nil) + realio := Setup(false, nil, 1) ctx := realio.BaseApp.NewContext(false, tmproto.Header{Height: ForkHeight}) stakingKeeper := realio.StakingKeeper diff --git a/app/test_helpers.go b/app/test_helpers.go index 970f9c2b..c9541181 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -81,14 +81,11 @@ func init() { func Setup( isCheckTx bool, feemarketGenesis *feemarkettypes.GenesisState, + numberVals int, ) *RealioNetwork { - privVal := mock.NewPV() - pubKey, _ := privVal.GetPubKey() encCdc := MakeEncodingConfig() - // create validator set with single validator - validator := tmtypes.NewValidator(pubKey, 1) - valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) + valSet := GenValSet(numberVals) // generate genesis account senderPrivKey := secp256k1.GenPrivKey() @@ -242,3 +239,16 @@ func SetupTestingApp() (ibctesting.TestingApp, map[string]json.RawMessage) { app := New(log.NewNopLogger(), db, nil, true, map[int64]bool{}, DefaultNodeHome, 5, cfg, simapp.EmptyAppOptions{}) return app, simapp.NewDefaultGenesisState(cfg.Codec) } + +func GenValSet(nums int) *tmtypes.ValidatorSet { + vals := []*tmtypes.Validator{} + + for i := 0; i < nums; i++ { + privVal := mock.NewPV() + pubKey, _ := privVal.GetPubKey() + vals = append(vals, tmtypes.NewValidator(pubKey, 1)) + } + valSet := tmtypes.NewValidatorSet(vals) + + return valSet +} diff --git a/app/upgrades.go b/app/upgrades.go index 577b7b68..a4ff9556 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -6,6 +6,7 @@ import ( servertypes "github.com/cosmos/cosmos-sdk/server/types" storetypes "github.com/cosmos/cosmos-sdk/store/types" multistakingtypes "github.com/realio-tech/multi-staking-module/x/multi-staking/types" + "github.com/realiotech/realio-network/app/upgrades/commission" multistaking "github.com/realiotech/realio-network/app/upgrades/multi-staking" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" @@ -24,6 +25,15 @@ func (app *RealioNetwork) setupUpgradeHandlers(appOpts servertypes.AppOptions) { ), ) + app.UpgradeKeeper.SetUpgradeHandler( + commission.UpgradeName, + commission.CreateUpgradeHandler( + app.mm, + app.configurator, + &app.StakingKeeper, + ), + ) + upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk() if err != nil { panic(fmt.Errorf("failed to read upgrade info from disk: %w", err)) diff --git a/app/upgrades/commission/constants.go b/app/upgrades/commission/constants.go new file mode 100644 index 00000000..ad3cb6f0 --- /dev/null +++ b/app/upgrades/commission/constants.go @@ -0,0 +1,7 @@ +package commission + +const ( + // UpgradeName defines the on-chain upgrade name. + UpgradeName = "Commission" + NewMinCommisionRate = "0.05" +) diff --git a/app/upgrades/commission/upgrades.go b/app/upgrades/commission/upgrades.go new file mode 100644 index 00000000..e2cedaa0 --- /dev/null +++ b/app/upgrades/commission/upgrades.go @@ -0,0 +1,64 @@ +package commission + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" +) + +func CreateUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + sk *stakingkeeper.Keeper, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + ctx.Logger().Info("Starting upgrade for multi staking...") + fixMinCommisionRate(ctx, sk) + return mm.RunMigrations(ctx, configurator, vm) + } +} + +func fixMinCommisionRate(ctx sdk.Context, staking *stakingkeeper.Keeper) { + // Upgrade every validators min-commission rate + validators := staking.GetAllValidators(ctx) + minComm := sdk.MustNewDecFromStr(NewMinCommisionRate) + + for _, v := range validators { + //nolint + if v.Commission.Rate.LT(minComm) { + comm, err := updateValidatorCommission(ctx, staking, v, minComm) + if err != nil { + panic(err) + } + + // call the before-modification hook since we're about to update the commission + staking.BeforeValidatorModified(ctx, v.GetOperator()) + v.Commission = comm + staking.SetValidator(ctx, v) + } + } +} + +func updateValidatorCommission(ctx sdk.Context, staking *stakingkeeper.Keeper, + validator stakingtypes.Validator, newRate sdk.Dec, +) (stakingtypes.Commission, error) { + commission := validator.Commission + blockTime := ctx.BlockHeader().Time + + if newRate.LT(staking.MinCommissionRate(ctx)) { + return commission, fmt.Errorf("cannot set validator commission to less than minimum rate of %s", staking.MinCommissionRate(ctx)) + } + + commission.Rate = newRate + if commission.MaxRate.LT(newRate) { + commission.MaxRate = newRate + } + + commission.UpdateTime = blockTime + + return commission, nil +} diff --git a/app/upgrades_test.go b/app/upgrades_test.go new file mode 100644 index 00000000..6fdf8432 --- /dev/null +++ b/app/upgrades_test.go @@ -0,0 +1,70 @@ +package app + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + "github.com/realiotech/realio-network/app/upgrades/commission" + "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) + +func TestCommissionUpgrade(t *testing.T) { + app := Setup(false, nil, 4) + ctx := app.BaseApp.NewContext(false, tmproto.Header{Height: app.LastBlockHeight() + 1}) + validators := app.StakingKeeper.GetAllValidators(ctx) + + comm0 := stakingtypes.CommissionRates{ + Rate: sdk.MustNewDecFromStr("0.01"), + MaxRate: sdk.MustNewDecFromStr("0.01"), + MaxChangeRate: sdk.MustNewDecFromStr("0.02"), + } + comm1 := stakingtypes.CommissionRates{ + Rate: sdk.MustNewDecFromStr("0.02"), + MaxRate: sdk.MustNewDecFromStr("0.03"), + MaxChangeRate: sdk.MustNewDecFromStr("0.02"), + } + comm2 := stakingtypes.CommissionRates{ + Rate: sdk.MustNewDecFromStr("0.06"), + MaxRate: sdk.MustNewDecFromStr("0.07"), + MaxChangeRate: sdk.MustNewDecFromStr("0.02"), + } + comm3 := stakingtypes.CommissionRates{ + Rate: sdk.MustNewDecFromStr("0.1"), + MaxRate: sdk.MustNewDecFromStr("0.2"), + MaxChangeRate: sdk.MustNewDecFromStr("0.1"), + } + + validators[0].Commission.CommissionRates = comm0 + validators[1].Commission.CommissionRates = comm1 + validators[2].Commission.CommissionRates = comm2 + validators[3].Commission.CommissionRates = comm3 + + app.StakingKeeper.SetValidator(ctx, validators[0]) + app.StakingKeeper.SetValidator(ctx, validators[1]) + app.StakingKeeper.SetValidator(ctx, validators[2]) + app.StakingKeeper.SetValidator(ctx, validators[3]) + + upgradePlan := upgradetypes.Plan{ + Name: commission.UpgradeName, + Height: ctx.BlockHeight(), + } + err := app.UpgradeKeeper.ScheduleUpgrade(ctx, upgradePlan) + + require.NoError(t, err) + ctx = ctx.WithBlockHeader(tmproto.Header{Time: time.Now()}) + app.UpgradeKeeper.ApplyUpgrade(ctx, upgradePlan) + + validatorsAfter := app.StakingKeeper.GetAllValidators(ctx) + + upgradeMinCommRate := sdk.MustNewDecFromStr(commission.NewMinCommisionRate) + require.Equal(t, validatorsAfter[0].Commission.CommissionRates.Rate, upgradeMinCommRate) + require.Equal(t, validatorsAfter[1].Commission.CommissionRates.Rate, upgradeMinCommRate) + require.Equal(t, validatorsAfter[0].Commission.CommissionRates.MaxRate, upgradeMinCommRate) + require.Equal(t, validatorsAfter[1].Commission.CommissionRates.MaxRate, upgradeMinCommRate) + require.Equal(t, validatorsAfter[2].Commission.CommissionRates.Rate, validators[2].Commission.CommissionRates.Rate) + require.Equal(t, validatorsAfter[3].Commission.CommissionRates.Rate, validators[3].Commission.CommissionRates.Rate) +} diff --git a/x/asset/genesis_test.go b/x/asset/genesis_test.go index 82416a45..d8c982a4 100644 --- a/x/asset/genesis_test.go +++ b/x/asset/genesis_test.go @@ -32,7 +32,7 @@ func (suite *GenesisTestSuite) SetupTest() { // consensus key consAddress := sdk.ConsAddress(testutil.GenAddress().Bytes()) - suite.app = app.Setup(false, feemarkettypes.DefaultGenesisState()) + suite.app = app.Setup(false, feemarkettypes.DefaultGenesisState(), 1) suite.ctx = suite.app.BaseApp.NewContext(false, tmproto.Header{ Height: 1, ChainID: realiotypes.MainnetChainID, diff --git a/x/asset/keeper/keeper_test.go b/x/asset/keeper/keeper_test.go index 4715b08f..34364f76 100644 --- a/x/asset/keeper/keeper_test.go +++ b/x/asset/keeper/keeper_test.go @@ -59,7 +59,7 @@ func (suite *KeeperTestSuite) DoSetupTest(t *testing.T) { consAddress := sdk.ConsAddress(priv.PubKey().Address()) // init app - suite.app = app.Setup(checkTx, nil) + suite.app = app.Setup(checkTx, nil, 1) // Set Context suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{ diff --git a/x/mint/keeper/keeper_test.go b/x/mint/keeper/keeper_test.go index ed476e94..011dcdbb 100644 --- a/x/mint/keeper/keeper_test.go +++ b/x/mint/keeper/keeper_test.go @@ -53,7 +53,7 @@ func (suite *KeeperTestSuite) DoSetupTest(t *testing.T) { consAddress := sdk.ConsAddress(priv.PubKey().Address()) // init app - suite.app = app.Setup(checkTx, nil) + suite.app = app.Setup(checkTx, nil, 1) // Set Context suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{ diff --git a/x/mint/module_test.go b/x/mint/module_test.go index 7115a762..1a0afc8b 100644 --- a/x/mint/module_test.go +++ b/x/mint/module_test.go @@ -12,7 +12,7 @@ import ( ) func TestItCreatesModuleAccountOnInitBlock(t *testing.T) { - realio := app.Setup(false, nil) + realio := app.Setup(false, nil, 1) ctx := realio.BaseApp.NewContext(false, tmproto.Header{}) acc := realio.AccountKeeper.GetAccount(ctx, authtypes.NewModuleAddress(types.ModuleName)) require.NotNil(t, acc)