chore: cleanup and improve x/mint params validation and test (#25562)

This commit is contained in:
Alex | Cosmos Labs 2025-11-17 10:03:13 -05:00 committed by GitHub
parent 94aebcd8ab
commit b3c25ca79c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 384 additions and 237 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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),
}
}

315
x/mint/types/params_test.go Normal file
View File

@ -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)
}
})
}
}