diff --git a/CHANGELOG.md b/CHANGELOG.md index e1bdbfd9f5..274b1d72f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,32 +36,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog -## [v0.53.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.3) - 2025-07-25 - -This patch update also includes minor dependency bumps. - -### Features - -* (abci_utils) [#25008](https://github.com/cosmos/cosmos-sdk/pull/24861) add the ability to assign a custom signer extraction adapter in `DefaultProposalHandler`. -* (x/tx) [#25539](https://github.com/cosmos/cosmos-sdk/pull/25539) Expose `NullSliceAsEmptyEncoder` as a public function and add `null_slice_as_empty` encoding option for protobuf annotations. - -## [v0.53.3](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.3) - 2025-07-08 - -### Bug Fixes - -* [GHSA-p22h-3m2v-cmgh](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-p22h-3m2v-cmgh) Fix x/distribution can halt when historical rewards overflow. - - -## [v0.53.2](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.2) - 2025-06-02 - -This patch update also includes minor dependency bumps. - -### Bug Fixes - -* (x/epochs) [#24770](https://github.com/cosmos/cosmos-sdk/pull/24770) Fix register of epoch hooks in `InvokeSetHooks`. - -## [v0.53.0](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.0) - 2025-04-29 - +## Unreleased ### Breaking Changes @@ -90,6 +65,7 @@ This patch update also includes minor dependency bumps. * (proto) [#24161](https://github.com/cosmos/cosmos-sdk/pull/24161) Remove unnecessary annotations from `x/staking` authz proto. * (x/bank) [#24660](https://github.com/cosmos/cosmos-sdk/pull/24660) Improve performance of the `GetAllBalances` and `GetAccountsBalances` keeper methods. * (collections) [#25464](https://github.com/cosmos/cosmos-sdk/pull/25464) Add `IterateRaw` method to `Multi` index type to satisfty query `Collection` interface. +* (x/mint) [#25562](https://github.com/cosmos/cosmos-sdk/pull/25562) Improve and test `x/mint` params validation. ### Bug Fixes @@ -109,6 +85,29 @@ This patch update also includes minor dependency bumps. * (x/group) [#24571](https://github.com/cosmos/cosmos-sdk/pull/24571) Deprecate the `x/group` module in the Cosmos SDK repository. This module will not be maintained to the extent that our core modules will and will be kept in a [legacy repo](https://github.com/cosmos/cosmos-legacy). * (types) [#24664](https://github.com/cosmos/cosmos-sdk/pull/24664) Deprecate the `Invariant` type in the Cosmos SDK. +## [v0.53.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.3) - 2025-07-25 + +This patch update also includes minor dependency bumps. + +### Features + +* (abci_utils) [#25008](https://github.com/cosmos/cosmos-sdk/pull/24861) add the ability to assign a custom signer extraction adapter in `DefaultProposalHandler`. + +## [v0.53.3](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.3) - 2025-07-08 + +### Bug Fixes + +* [GHSA-p22h-3m2v-cmgh](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-p22h-3m2v-cmgh) Fix x/distribution can halt when historical rewards overflow. + + +## [v0.53.2](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.2) - 2025-06-02 + +This patch update also includes minor dependency bumps. + +### Bug Fixes + +* (x/epochs) [#24770](https://github.com/cosmos/cosmos-sdk/pull/24770) Fix register of epoch hooks in `InvokeSetHooks`. + ## [v0.53.0](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.0) - 2025-04-29 ### Features diff --git a/x/mint/keeper/migrator.go b/x/mint/keeper/migrator.go index 3e85e6edba..d07e9ff507 100644 --- a/x/mint/keeper/migrator.go +++ b/x/mint/keeper/migrator.go @@ -1,9 +1,7 @@ package keeper import ( - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/mint/exported" - v2 "github.com/cosmos/cosmos-sdk/x/mint/migrations/v2" ) // Migrator is a struct for handling in-place state migrations. @@ -19,11 +17,3 @@ func NewMigrator(k Keeper, ss exported.Subspace) Migrator { legacySubspace: ss, } } - -// Migrate1to2 migrates the x/mint module state from the consensus version 1 to -// version 2. Specifically, it takes the parameters that are currently stored -// and managed by the x/params modules and stores them directly into the x/mint -// module state. -func (m Migrator) Migrate1to2(ctx sdk.Context) error { - return v2.Migrate(ctx, m.keeper.storeService.OpenKVStore(ctx), m.legacySubspace, m.keeper.cdc) -} diff --git a/x/mint/migrations/v2/migrate.go b/x/mint/migrations/v2/migrate.go deleted file mode 100644 index 82f267f3c7..0000000000 --- a/x/mint/migrations/v2/migrate.go +++ /dev/null @@ -1,37 +0,0 @@ -package v2 - -import ( - storetypes "cosmossdk.io/core/store" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/mint/exported" - "github.com/cosmos/cosmos-sdk/x/mint/types" -) - -const ( - ModuleName = "mint" -) - -var ParamsKey = []byte{0x01} - -// Migrate migrates the x/mint module state from the consensus version 1 to -// version 2. Specifically, it takes the parameters that are currently stored -// and managed by the x/params modules and stores them directly into the x/mint -// module state. -func Migrate( - ctx sdk.Context, - store storetypes.KVStore, - legacySubspace exported.Subspace, - cdc codec.BinaryCodec, -) error { - var currParams types.Params - legacySubspace.GetParamSet(ctx, &currParams) - - if err := currParams.Validate(); err != nil { - return err - } - - bz := cdc.MustMarshal(&currParams) - return store.Set(ParamsKey, bz) -} diff --git a/x/mint/migrations/v2/migrator_test.go b/x/mint/migrations/v2/migrator_test.go deleted file mode 100644 index 70fd27084a..0000000000 --- a/x/mint/migrations/v2/migrator_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package v2_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - storetypes "cosmossdk.io/store/types" - - "github.com/cosmos/cosmos-sdk/runtime" - "github.com/cosmos/cosmos-sdk/testutil" - sdk "github.com/cosmos/cosmos-sdk/types" - moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" - "github.com/cosmos/cosmos-sdk/x/mint" - "github.com/cosmos/cosmos-sdk/x/mint/exported" - v2 "github.com/cosmos/cosmos-sdk/x/mint/migrations/v2" - "github.com/cosmos/cosmos-sdk/x/mint/types" -) - -type mockSubspace struct { - ps types.Params -} - -func newMockSubspace(ps types.Params) mockSubspace { - return mockSubspace{ps: ps} -} - -func (ms mockSubspace) GetParamSet(ctx sdk.Context, ps exported.ParamSet) { - *ps.(*types.Params) = ms.ps -} - -func TestMigrate(t *testing.T) { - encCfg := moduletestutil.MakeTestEncodingConfig(mint.AppModuleBasic{}) - cdc := encCfg.Codec - - storeKey := storetypes.NewKVStoreKey(v2.ModuleName) - tKey := storetypes.NewTransientStoreKey("transient_test") - ctx := testutil.DefaultContext(storeKey, tKey) - kvStoreService := runtime.NewKVStoreService(storeKey) - store := kvStoreService.OpenKVStore(ctx) - - legacySubspace := newMockSubspace(types.DefaultParams()) - require.NoError(t, v2.Migrate(ctx, store, legacySubspace, cdc)) - - var res types.Params - bz, err := store.Get(v2.ParamsKey) - require.NoError(t, err) - require.NoError(t, cdc.Unmarshal(bz, &res)) - require.Equal(t, legacySubspace.ps, res) -} diff --git a/x/mint/module.go b/x/mint/module.go index d7dffe35f3..11dfac4bfd 100644 --- a/x/mint/module.go +++ b/x/mint/module.go @@ -128,11 +128,7 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) types.RegisterQueryServer(cfg.QueryServer(), keeper.NewQueryServerImpl(am.keeper)) - m := keeper.NewMigrator(am.keeper, am.legacySubspace) - - if err := cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2); err != nil { - panic(fmt.Sprintf("failed to migrate x/%s from version 1 to 2: %v", types.ModuleName, err)) - } + _ = keeper.NewMigrator(am.keeper, am.legacySubspace) } // InitGenesis performs genesis initialization for the mint module. It returns diff --git a/x/mint/types/params.go b/x/mint/types/params.go index b25d5aac7d..e82bf6fefe 100644 --- a/x/mint/types/params.go +++ b/x/mint/types/params.go @@ -3,15 +3,16 @@ package types import ( "errors" "fmt" + "math" "strings" - "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" ) // NewParams returns Params instance with the given values. -func NewParams(mintDenom string, inflationRateChange, inflationMax, inflationMin, goalBonded math.LegacyDec, blocksPerYear uint64) Params { +func NewParams(mintDenom string, inflationRateChange, inflationMax, inflationMin, goalBonded sdkmath.LegacyDec, blocksPerYear uint64) Params { return Params{ MintDenom: mintDenom, InflationRateChange: inflationRateChange, @@ -26,10 +27,10 @@ func NewParams(mintDenom string, inflationRateChange, inflationMax, inflationMin func DefaultParams() Params { return Params{ MintDenom: sdk.DefaultBondDenom, - InflationRateChange: math.LegacyNewDecWithPrec(13, 2), - InflationMax: math.LegacyNewDecWithPrec(20, 2), - InflationMin: math.LegacyNewDecWithPrec(7, 2), - GoalBonded: math.LegacyNewDecWithPrec(67, 2), + InflationRateChange: sdkmath.LegacyNewDecWithPrec(13, 2), + InflationMax: sdkmath.LegacyNewDecWithPrec(20, 2), + InflationMin: sdkmath.LegacyNewDecWithPrec(7, 2), + GoalBonded: sdkmath.LegacyNewDecWithPrec(67, 2), BlocksPerYear: uint64(60 * 60 * 8766 / 5), // assuming 5 second block times } } @@ -64,106 +65,80 @@ func (p Params) Validate() error { return nil } -func validateMintDenom(i any) error { - v, ok := i.(string) - if !ok { - return fmt.Errorf("invalid parameter type: %T", i) - } - - if strings.TrimSpace(v) == "" { +func validateMintDenom(denom string) error { + if strings.TrimSpace(denom) == "" { return errors.New("mint denom cannot be blank") } - if err := sdk.ValidateDenom(v); err != nil { + if err := sdk.ValidateDenom(denom); err != nil { return err } return nil } -func validateInflationRateChange(i any) error { - v, ok := i.(math.LegacyDec) - if !ok { - return fmt.Errorf("invalid parameter type: %T", i) +func validateInflationRateChange(rateChange sdkmath.LegacyDec) error { + if rateChange.IsNil() { + return fmt.Errorf("inflation rate change cannot be nil: %s", rateChange) } - - if v.IsNil() { - return fmt.Errorf("inflation rate change cannot be nil: %s", v) + if rateChange.IsNegative() { + return fmt.Errorf("inflation rate change cannot be negative: %s", rateChange) } - if v.IsNegative() { - return fmt.Errorf("inflation rate change cannot be negative: %s", v) - } - if v.GT(math.LegacyOneDec()) { - return fmt.Errorf("inflation rate change too large: %s", v) + if rateChange.GT(sdkmath.LegacyOneDec()) { + return fmt.Errorf("inflation rate change too large: %s", rateChange) } return nil } -func validateInflationMax(i any) error { - v, ok := i.(math.LegacyDec) - if !ok { - return fmt.Errorf("invalid parameter type: %T", i) +func validateInflationMax(inflationMax sdkmath.LegacyDec) error { + if inflationMax.IsNil() { + return fmt.Errorf("max inflation cannot be nil: %s", inflationMax) } - - if v.IsNil() { - return fmt.Errorf("max inflation cannot be nil: %s", v) + if inflationMax.IsNegative() { + return fmt.Errorf("max inflation cannot be negative: %s", inflationMax) } - if v.IsNegative() { - return fmt.Errorf("max inflation cannot be negative: %s", v) - } - if v.GT(math.LegacyOneDec()) { - return fmt.Errorf("max inflation too large: %s", v) + if inflationMax.GT(sdkmath.LegacyOneDec()) { + return fmt.Errorf("max inflation too large: %s", inflationMax) } return nil } -func validateInflationMin(i any) error { - v, ok := i.(math.LegacyDec) - if !ok { - return fmt.Errorf("invalid parameter type: %T", i) +func validateInflationMin(inflationMin sdkmath.LegacyDec) error { + if inflationMin.IsNil() { + return fmt.Errorf("min inflation cannot be nil: %s", inflationMin) } - - if v.IsNil() { - return fmt.Errorf("min inflation cannot be nil: %s", v) + if inflationMin.IsNegative() { + return fmt.Errorf("min inflation cannot be negative: %s", inflationMin) } - if v.IsNegative() { - return fmt.Errorf("min inflation cannot be negative: %s", v) - } - if v.GT(math.LegacyOneDec()) { - return fmt.Errorf("min inflation too large: %s", v) + if inflationMin.GT(sdkmath.LegacyOneDec()) { + return fmt.Errorf("min inflation too large: %s", inflationMin) } return nil } -func validateGoalBonded(i any) error { - v, ok := i.(math.LegacyDec) - if !ok { - return fmt.Errorf("invalid parameter type: %T", i) +func validateGoalBonded(goalBonded sdkmath.LegacyDec) error { + if goalBonded.IsNil() { + return fmt.Errorf("goal bonded cannot be nil: %s", goalBonded) } - - if v.IsNil() { - return fmt.Errorf("goal bonded cannot be nil: %s", v) + if goalBonded.IsNegative() || goalBonded.IsZero() { + return fmt.Errorf("goal bonded must be positive: %s", goalBonded) } - if v.IsNegative() || v.IsZero() { - return fmt.Errorf("goal bonded must be positive: %s", v) - } - if v.GT(math.LegacyOneDec()) { - return fmt.Errorf("goal bonded too large: %s", v) + if goalBonded.GT(sdkmath.LegacyOneDec()) { + return fmt.Errorf("goal bonded too large: %s", goalBonded) } return nil } -func validateBlocksPerYear(i any) error { - v, ok := i.(uint64) - if !ok { - return fmt.Errorf("invalid parameter type: %T", i) +func validateBlocksPerYear(blocksPerYear uint64) error { + if blocksPerYear == 0 { + return fmt.Errorf("blocks per year must be positive: %d", blocksPerYear) } - if v == 0 { - return fmt.Errorf("blocks per year must be positive: %d", v) + if blocksPerYear > math.MaxInt64 { + return fmt.Errorf("blocks per year too large: %d, maximum value is: %d", blocksPerYear, math.MaxInt64) } return nil diff --git a/x/mint/types/params_legacy.go b/x/mint/types/params_legacy.go deleted file mode 100644 index afb84f7eb2..0000000000 --- a/x/mint/types/params_legacy.go +++ /dev/null @@ -1,41 +0,0 @@ -/* -NOTE: Usage of x/params to manage parameters is deprecated in favor of x/gov -controlled execution of MsgUpdateParams messages. These types remains solely -for migration purposes and will be removed in a future release. -*/ -package types - -import ( - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" -) - -// Parameter store keys -var ( - KeyMintDenom = []byte("MintDenom") - KeyInflationRateChange = []byte("InflationRateChange") - KeyInflationMax = []byte("InflationMax") - KeyInflationMin = []byte("InflationMin") - KeyGoalBonded = []byte("GoalBonded") - KeyBlocksPerYear = []byte("BlocksPerYear") -) - -// ParamKeyTable is the parameter key table for mint. -// -// Deprecated: will be removed with x/params -func ParamKeyTable() paramtypes.KeyTable { - return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) -} - -// ParamSetPairs implements params.ParamSet -// -// Deprecated: will be removed with x/params -func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { - return paramtypes.ParamSetPairs{ - paramtypes.NewParamSetPair(KeyMintDenom, &p.MintDenom, validateMintDenom), - paramtypes.NewParamSetPair(KeyInflationRateChange, &p.InflationRateChange, validateInflationRateChange), - paramtypes.NewParamSetPair(KeyInflationMax, &p.InflationMax, validateInflationMax), - paramtypes.NewParamSetPair(KeyInflationMin, &p.InflationMin, validateInflationMin), - paramtypes.NewParamSetPair(KeyGoalBonded, &p.GoalBonded, validateGoalBonded), - paramtypes.NewParamSetPair(KeyBlocksPerYear, &p.BlocksPerYear, validateBlocksPerYear), - } -} diff --git a/x/mint/types/params_test.go b/x/mint/types/params_test.go new file mode 100644 index 0000000000..085d911246 --- /dev/null +++ b/x/mint/types/params_test.go @@ -0,0 +1,315 @@ +package types + +import ( + "math" + "testing" + + "github.com/stretchr/testify/require" + + sdkmath "cosmossdk.io/math" +) + +func TestDefaultParams_ValidateOK(t *testing.T) { + t.Parallel() + require.NoError(t, DefaultParams().Validate()) +} + +func TestNewParams_Validate(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + params Params + wantErr bool + errContains string + }{ + { + name: "valid typical config", + params: NewParams( + "uatom", + sdkmath.LegacyMustNewDecFromStr("0.13"), + sdkmath.LegacyMustNewDecFromStr("0.20"), + sdkmath.LegacyMustNewDecFromStr("0.07"), + sdkmath.LegacyMustNewDecFromStr("0.67"), + uint64(60*60*8766/5), + ), + wantErr: false, + errContains: "", + }, + { + name: "max < min inflation", + params: NewParams( + "uatom", + sdkmath.LegacyMustNewDecFromStr("0.10"), + sdkmath.LegacyMustNewDecFromStr("0.05"), + sdkmath.LegacyMustNewDecFromStr("0.06"), + sdkmath.LegacyMustNewDecFromStr("0.67"), + 1, + ), + wantErr: true, + errContains: "must be greater than or equal to min inflation", + }, + { + name: "invalid denom", + params: NewParams( + "", + sdkmath.LegacyMustNewDecFromStr("0.10"), + sdkmath.LegacyMustNewDecFromStr("0.20"), + sdkmath.LegacyMustNewDecFromStr("0.07"), + sdkmath.LegacyMustNewDecFromStr("0.67"), + 1, + ), + wantErr: true, + errContains: "", + }, + { + name: "goal bonded > 1", + params: NewParams( + "uatom", + sdkmath.LegacyMustNewDecFromStr("0.10"), + sdkmath.LegacyMustNewDecFromStr("0.20"), + sdkmath.LegacyMustNewDecFromStr("0.07"), + sdkmath.LegacyMustNewDecFromStr("1.01"), + 1, + ), + wantErr: true, + errContains: "", + }, + { + name: "blocks per year zero", + params: NewParams( + "uatom", + sdkmath.LegacyMustNewDecFromStr("0.10"), + sdkmath.LegacyMustNewDecFromStr("0.20"), + sdkmath.LegacyMustNewDecFromStr("0.07"), + sdkmath.LegacyMustNewDecFromStr("0.67"), + 0, + ), + wantErr: true, + errContains: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := tt.params.Validate() + if tt.wantErr { + require.Error(t, err) + if tt.errContains != "" { + require.ErrorContains(t, err, tt.errContains) + } + } else { + require.NoError(t, err) + } + }) + } +} + +func TestValidateMintDenom(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + denom string + wantErr bool + errContains string + }{ + {name: "blank", denom: "", wantErr: true, errContains: "cannot be blank"}, + {name: "spaces", denom: " ", wantErr: true, errContains: "cannot be blank"}, + {name: "valid lower", denom: "uatom", wantErr: false, errContains: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := validateMintDenom(tt.denom) + if tt.wantErr { + require.Error(t, err) + if tt.errContains != "" { + require.ErrorContains(t, err, tt.errContains) + } + } else { + require.NoError(t, err) + } + }) + } +} + +func TestValidateInflationRateChange(t *testing.T) { + t.Parallel() + + var nilDec sdkmath.LegacyDec // zero value => IsNil() == true + + tests := []struct { + name string + val sdkmath.LegacyDec + wantErr bool + errContains string + }{ + {name: "nil", val: nilDec, wantErr: true, errContains: "cannot be nil"}, + {name: "negative", val: sdkmath.LegacyMustNewDecFromStr("-0.01"), wantErr: true, errContains: "cannot be negative"}, + {name: "too large > 1", val: sdkmath.LegacyMustNewDecFromStr("1.01"), wantErr: true, errContains: "too large"}, + {name: "zero ok", val: sdkmath.LegacyMustNewDecFromStr("0"), wantErr: false, errContains: ""}, + {name: "one ok", val: sdkmath.LegacyMustNewDecFromStr("1.0"), wantErr: false, errContains: ""}, + {name: "mid ok", val: sdkmath.LegacyMustNewDecFromStr("0.13"), wantErr: false, errContains: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := validateInflationRateChange(tt.val) + if tt.wantErr { + require.Error(t, err) + if tt.errContains != "" { + require.ErrorContains(t, err, tt.errContains) + } + } else { + require.NoError(t, err) + } + }) + } +} + +func TestValidateInflationMax(t *testing.T) { + t.Parallel() + + var nilDec sdkmath.LegacyDec + + tests := []struct { + name string + val sdkmath.LegacyDec + wantErr bool + errContains string + }{ + {name: "nil", val: nilDec, wantErr: true, errContains: "cannot be nil"}, + {name: "negative", val: sdkmath.LegacyMustNewDecFromStr("-0.01"), wantErr: true, errContains: "cannot be negative"}, + {name: "too large > 1", val: sdkmath.LegacyMustNewDecFromStr("1.1"), wantErr: true, errContains: "too large"}, + {name: "zero ok", val: sdkmath.LegacyMustNewDecFromStr("0"), wantErr: false, errContains: ""}, + {name: "one ok", val: sdkmath.LegacyMustNewDecFromStr("1"), wantErr: false, errContains: ""}, + {name: "typical ok", val: sdkmath.LegacyMustNewDecFromStr("0.20"), wantErr: false, errContains: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := validateInflationMax(tt.val) + if tt.wantErr { + require.Error(t, err) + if tt.errContains != "" { + require.ErrorContains(t, err, tt.errContains) + } + } else { + require.NoError(t, err) + } + }) + } +} + +func TestValidateInflationMin(t *testing.T) { + t.Parallel() + + var nilDec sdkmath.LegacyDec + + tests := []struct { + name string + val sdkmath.LegacyDec + wantErr bool + errContains string + }{ + {name: "nil", val: nilDec, wantErr: true, errContains: "cannot be nil"}, + {name: "negative", val: sdkmath.LegacyMustNewDecFromStr("-0.01"), wantErr: true, errContains: "cannot be negative"}, + {name: "too large > 1", val: sdkmath.LegacyMustNewDecFromStr("1.1"), wantErr: true, errContains: "too large"}, + {name: "zero ok", val: sdkmath.LegacyMustNewDecFromStr("0"), wantErr: false, errContains: ""}, + {name: "one ok", val: sdkmath.LegacyMustNewDecFromStr("1"), wantErr: false, errContains: ""}, + {name: "typical ok", val: sdkmath.LegacyMustNewDecFromStr("0.07"), wantErr: false, errContains: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := validateInflationMin(tt.val) + if tt.wantErr { + require.Error(t, err) + if tt.errContains != "" { + require.ErrorContains(t, err, tt.errContains) + } + } else { + require.NoError(t, err) + } + }) + } +} + +func TestValidateGoalBonded(t *testing.T) { + t.Parallel() + + var nilDec sdkmath.LegacyDec + + tests := []struct { + name string + val sdkmath.LegacyDec + wantErr bool + errContains string + }{ + {name: "nil", val: nilDec, wantErr: true, errContains: "cannot be nil"}, + {name: "negative", val: sdkmath.LegacyMustNewDecFromStr("-0.01"), wantErr: true, errContains: "must be positive"}, + {name: "zero", val: sdkmath.LegacyMustNewDecFromStr("0"), wantErr: true, errContains: "must be positive"}, + {name: "too large > 1", val: sdkmath.LegacyMustNewDecFromStr("1.0000000001"), wantErr: true, errContains: "too large"}, + {name: "exactly one ok", val: sdkmath.LegacyMustNewDecFromStr("1.0"), wantErr: false, errContains: ""}, + {name: "typical ok", val: sdkmath.LegacyMustNewDecFromStr("0.67"), wantErr: false, errContains: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := validateGoalBonded(tt.val) + if tt.wantErr { + require.Error(t, err) + if tt.errContains != "" { + require.ErrorContains(t, err, tt.errContains) + } + } else { + require.NoError(t, err) + } + }) + } +} + +func TestValidateBlocksPerYear(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + val uint64 + wantErr bool + errContains string + }{ + {name: "zero", val: 0, wantErr: true, errContains: "must be positive"}, + {name: "one ok", val: 1, wantErr: false, errContains: ""}, + {name: "maxInt64 ok", val: uint64(math.MaxInt64), wantErr: false, errContains: ""}, + {name: "maxInt64+1 too large", val: uint64(math.MaxInt64) + 1, wantErr: true, errContains: "too large"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := validateBlocksPerYear(tt.val) + if tt.wantErr { + require.Error(t, err) + if tt.errContains != "" { + require.ErrorContains(t, err, tt.errContains) + } + } else { + require.NoError(t, err) + } + }) + } +}