From 1d0266dae79260e342e5c35e78f49700c3ce358d Mon Sep 17 00:00:00 2001 From: aljo242 Date: Fri, 15 May 2026 11:21:08 -0400 Subject: [PATCH 1/3] test: remove dual-denom test paths and add invariants coverage Drop remaining 6/12-decimal test-support paths now that only 18-decimal configs are supported, and add randomized conversion invariants plus a scaling fuzz target to harden regression coverage. Co-authored-by: Cursor --- .../precompiles/werc20/test_events.go | 6 -- tests/integration/testutil/test_config.go | 8 -- .../test_conversion_invariants_randomized.go | 90 +++++++++++++++++++ testutil/constants/constants.go | 55 ------------ utils/utils_test.go | 17 +++- x/vm/types/configurator_test.go | 14 ++- x/vm/types/scaling_fuzz_test.go | 52 +++++++++++ 7 files changed, 168 insertions(+), 74 deletions(-) create mode 100644 tests/integration/x/erc20/test_conversion_invariants_randomized.go create mode 100644 x/vm/types/scaling_fuzz_test.go diff --git a/tests/integration/precompiles/werc20/test_events.go b/tests/integration/precompiles/werc20/test_events.go index 5f33087462..91e51cc31c 100644 --- a/tests/integration/precompiles/werc20/test_events.go +++ b/tests/integration/precompiles/werc20/test_events.go @@ -101,9 +101,6 @@ func (s *PrecompileUnitTestSuite) TestEmitDepositEvent() { { name: "mainnet", chainID: testconstants.ExampleChainID, - }, { - name: "six decimals", - chainID: testconstants.SixDecimalsChainID, }, } @@ -160,9 +157,6 @@ func (s *PrecompileUnitTestSuite) TestEmitWithdrawalEvent() { { name: "mainnet", chainID: testconstants.ExampleChainID, - }, { - name: "six decimals", - chainID: testconstants.SixDecimalsChainID, }, } diff --git a/tests/integration/testutil/test_config.go b/tests/integration/testutil/test_config.go index dac835b447..7890797884 100644 --- a/tests/integration/testutil/test_config.go +++ b/tests/integration/testutil/test_config.go @@ -17,7 +17,6 @@ import ( func (s *TestSuite) TestWithChainID() { eighteenDecimalsCoinInfo := testconstants.ExampleChainCoinInfo[testconstants.ExampleChainID] - sixDecimalsCoinInfo := testconstants.ExampleChainCoinInfo[testconstants.SixDecimalsChainID] testCases := []struct { name string @@ -34,13 +33,6 @@ func (s *TestSuite) TestWithChainID() { expBaseFee: math.LegacyNewDec(875_000_000), expCosmosAmount: network.GetInitialAmount(evmtypes.EighteenDecimals), }, - { - name: "6 decimals", - chainID: testconstants.SixDecimalsChainID, - coinInfo: sixDecimalsCoinInfo, - expBaseFee: math.LegacyNewDecWithPrec(875, 6), - expCosmosAmount: network.GetInitialAmount(evmtypes.SixDecimals), - }, } for _, tc := range testCases { diff --git a/tests/integration/x/erc20/test_conversion_invariants_randomized.go b/tests/integration/x/erc20/test_conversion_invariants_randomized.go new file mode 100644 index 0000000000..cb141ea658 --- /dev/null +++ b/tests/integration/x/erc20/test_conversion_invariants_randomized.go @@ -0,0 +1,90 @@ +package erc20 + +import ( + "fmt" + "math/big" + "math/rand" + + "github.com/cosmos/evm/contracts" + "github.com/cosmos/evm/x/erc20/types" + + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// TestRandomizedConvertRoundTripInvariant runs randomized convert sequences and +// asserts that sender coin/token balances always conserve value. +func (s *KeeperTestSuite) TestRandomizedConvertRoundTripInvariant() { + const ( + initialMint = int64(5_000) + steps = 50 + ) + + for _, seed := range []int64{1, 7, 42, 777, 2026} { + s.Run(fmt.Sprintf("seed-%d", seed), func() { + s.mintFeeCollector = true + defer func() { + s.mintFeeCollector = false + }() + s.SetupTest() + + contractAddr, err := s.setupRegisterERC20Pair(contractMinterBurner) + s.Require().NoError(err) + + senderAcc := s.keyring.GetAccAddr(0) + senderHex := s.keyring.GetAddr(0) + denom := types.CreateDenom(contractAddr.String()) + total := math.NewInt(initialMint) + + _, err = s.MintERC20Token(contractAddr, senderHex, big.NewInt(initialMint)) + s.Require().NoError(err) + + rng := rand.New(rand.NewSource(seed)) + erc20Keeper := s.network.App.GetErc20Keeper() + bankKeeper := s.network.App.GetBankKeeper() + erc20ABI := contracts.ERC20MinterBurnerDecimalsContract.ABI + + for i := 0; i < steps; i++ { + ctx := s.network.GetContext() + erc20Bal := erc20Keeper.BalanceOf(ctx, erc20ABI, contractAddr, senderHex) + coinBal := bankKeeper.GetBalance(ctx, senderAcc, denom).Amount + + combined := new(big.Int).Add(erc20Bal, coinBal.BigInt()) + s.Require().Equal(total.String(), combined.String(), "value should be conserved before step") + + convertFromERC20 := rng.Intn(2) == 0 + if erc20Bal.Sign() == 0 { + convertFromERC20 = false + } + if coinBal.IsZero() { + convertFromERC20 = true + } + + if convertFromERC20 { + max := erc20Bal.Int64() + amount := int64(rng.Intn(int(max)) + 1) + _, err = erc20Keeper.ConvertERC20( + ctx, + types.NewMsgConvertERC20(math.NewInt(amount), senderAcc, contractAddr, senderHex), + ) + s.Require().NoError(err) + } else { + max := coinBal.Int64() + amount := int64(rng.Intn(int(max)) + 1) + _, err = erc20Keeper.ConvertCoin( + ctx, + types.NewMsgConvertCoin(sdk.NewCoin(denom, math.NewInt(amount)), senderHex, senderAcc), + ) + s.Require().NoError(err) + } + } + + ctx := s.network.GetContext() + finalERC20Bal := erc20Keeper.BalanceOf(ctx, erc20ABI, contractAddr, senderHex) + finalCoinBal := bankKeeper.GetBalance(ctx, senderAcc, denom).Amount + finalCombined := new(big.Int).Add(finalERC20Bal, finalCoinBal.BigInt()) + s.Require().Equal(total.String(), finalCombined.String(), "value should be conserved after sequence") + }) + } +} diff --git a/testutil/constants/constants.go b/testutil/constants/constants.go index 764ae4dbf6..fe67c0fe30 100644 --- a/testutil/constants/constants.go +++ b/testutil/constants/constants.go @@ -47,13 +47,6 @@ var ChainsCoinInfo = map[uint64]evmtypes.EvmCoinInfo{ // TODO:VLAD - deduplicate DisplayDenom: ExampleDisplayDenom, Decimals: evmtypes.EighteenDecimals.Uint32(), }, - // SixDecimalsChainID provides a chain ID which is being set up with 6 decimals - SixDecimalsChainID.EVMChainID: { - Denom: "utest", - ExtendedDenom: "atest", - DisplayDenom: "test", - Decimals: evmtypes.SixDecimals.Uint32(), - }, // EVMChainID provides a chain ID used for internal testing config.DefaultEVMChainID: { Denom: "atest", @@ -61,18 +54,6 @@ var ChainsCoinInfo = map[uint64]evmtypes.EvmCoinInfo{ // TODO:VLAD - deduplicate DisplayDenom: "test", Decimals: evmtypes.EighteenDecimals.Uint32(), }, - TwelveDecimalsChainID.EVMChainID: { - Denom: "ptest2", - ExtendedDenom: "atest2", - DisplayDenom: "test2", - Decimals: evmtypes.TwelveDecimals.Uint32(), - }, - TwoDecimalsChainID.EVMChainID: { - Denom: "ctest3", - ExtendedDenom: "atest3", - DisplayDenom: "test3", - Decimals: evmtypes.TwoDecimals.Uint32(), - }, } type ChainID struct { @@ -90,24 +71,6 @@ var ( EVMChainID: 9001, } - // SixDecimalsChainID provides a chain ID which is being set up with 6 decimals - SixDecimalsChainID = ChainID{ - ChainID: "ossix-2", - EVMChainID: 9002, - } - - // TwelveDecimalsChainID provides a chain ID which is being set up with 12 decimals - TwelveDecimalsChainID = ChainID{ - ChainID: "ostwelve-3", - EVMChainID: 9003, - } - - // TwoDecimalsChainID provides a chain ID which is being set up with 2 decimals - TwoDecimalsChainID = ChainID{ - ChainID: "ostwo-4", - EVMChainID: 9004, - } - // ExampleChainCoinInfo provides the coin info for the example chain // // It is a map of the chain id and its corresponding EvmCoinInfo @@ -120,24 +83,6 @@ var ( DisplayDenom: ExampleDisplayDenom, Decimals: evmtypes.EighteenDecimals.Uint32(), }, - SixDecimalsChainID: { - Denom: "utest", - ExtendedDenom: "atest", - DisplayDenom: "test", - Decimals: evmtypes.SixDecimals.Uint32(), - }, - TwelveDecimalsChainID: { - Denom: "ptest2", - ExtendedDenom: "atest2", - DisplayDenom: "test2", - Decimals: evmtypes.TwelveDecimals.Uint32(), - }, - TwoDecimalsChainID: { - Denom: "ctest3", - ExtendedDenom: "atest3", - DisplayDenom: "test3", - Decimals: evmtypes.TwoDecimals.Uint32(), - }, } // OtherCoinDenoms provides a list of other coin denoms that can be used in tests diff --git a/utils/utils_test.go b/utils/utils_test.go index b072848583..8e31d5d131 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -793,10 +793,21 @@ func TestCalcBaseFee(t *testing.T) { } func TestCalcBaseFeeRejectsUnsupportedDecimals(t *testing.T) { - for _, chainID := range []constants.ChainID{constants.TwelveDecimalsChainID, constants.SixDecimalsChainID} { - t.Run(chainID.ChainID, func(t *testing.T) { + for _, tc := range []struct { + name string + decimals uint32 + }{ + {name: "twelve", decimals: evmtypes.TwelveDecimals.Uint32()}, + {name: "six", decimals: evmtypes.SixDecimals.Uint32()}, + } { + t.Run(tc.name, func(t *testing.T) { evmConfigurator := evmtypes.NewEVMConfigurator(). - WithEVMCoinInfo(constants.ExampleChainCoinInfo[chainID]) + WithEVMCoinInfo(evmtypes.EvmCoinInfo{ + Denom: constants.ExampleAttoDenom, + ExtendedDenom: constants.ExampleAttoDenom, + DisplayDenom: "custom", + Decimals: tc.decimals, + }) evmConfigurator.ResetTestConfig() err := evmConfigurator.Configure() require.ErrorContains(t, err, "only 18 is supported") diff --git a/x/vm/types/configurator_test.go b/x/vm/types/configurator_test.go index 33d3dca1d6..4dbc672008 100644 --- a/x/vm/types/configurator_test.go +++ b/x/vm/types/configurator_test.go @@ -23,8 +23,18 @@ func TestEVMConfigurator(t *testing.T) { func TestEVMConfiguratorRejectsNon18Decimals(t *testing.T) { for _, coinInfo := range []types.EvmCoinInfo{ - testconstants.ExampleChainCoinInfo[testconstants.SixDecimalsChainID], - testconstants.ExampleChainCoinInfo[testconstants.TwelveDecimalsChainID], + { + Denom: testconstants.ExampleAttoDenom, + ExtendedDenom: testconstants.ExampleAttoDenom, + DisplayDenom: "custom6", + Decimals: types.SixDecimals.Uint32(), + }, + { + Denom: testconstants.ExampleAttoDenom, + ExtendedDenom: testconstants.ExampleAttoDenom, + DisplayDenom: "custom12", + Decimals: types.TwelveDecimals.Uint32(), + }, } { t.Run(coinInfo.DisplayDenom, func(t *testing.T) { ec := types.NewEVMConfigurator().WithEVMCoinInfo(coinInfo) diff --git a/x/vm/types/scaling_fuzz_test.go b/x/vm/types/scaling_fuzz_test.go new file mode 100644 index 0000000000..a1f6260ee6 --- /dev/null +++ b/x/vm/types/scaling_fuzz_test.go @@ -0,0 +1,52 @@ +package types_test + +import ( + "testing" + + testconstants "github.com/cosmos/evm/testutil/constants" + evmtypes "github.com/cosmos/evm/x/vm/types" + + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func FuzzConvertCoinsDenomToExtendedDenomWithEvmParams(f *testing.F) { + f.Add(uint64(0), false) + f.Add(uint64(1), false) + f.Add(uint64(1_000_000_000_000_000_000), true) + f.Add(uint64(42), true) + + coinInfo := testconstants.ExampleChainCoinInfo[testconstants.ExampleChainID] + params := evmtypes.Params{ + EvmDenom: coinInfo.Denom, + ExtendedDenomOptions: &evmtypes.ExtendedDenomOptions{ + ExtendedDenom: coinInfo.ExtendedDenom, + }, + } + + f.Fuzz(func(t *testing.T, amount uint64, includeOtherDenom bool) { + input := sdk.NewCoins(sdk.NewCoin(coinInfo.Denom, math.NewIntFromUint64(amount))) + if includeOtherDenom { + input = input.Add(sdk.NewCoin("other", math.NewIntFromUint64(amount+1))).Sort() + } + + converted := evmtypes.ConvertCoinsDenomToExtendedDenomWithEvmParams(input, params) + convertedAgain := evmtypes.ConvertCoinsDenomToExtendedDenomWithEvmParams(converted, params) + + // Conversion should be idempotent. + if !convertedAgain.Equal(converted) { + t.Fatalf("expected idempotent conversion, got %s then %s", converted, convertedAgain) + } + + // EVM coin amount should be preserved under denom relabeling. + if converted.AmountOf(coinInfo.ExtendedDenom).String() != math.NewIntFromUint64(amount).String() { + t.Fatalf("unexpected converted amount: %s", converted.AmountOf(coinInfo.ExtendedDenom)) + } + + // Non-EVM denoms should be preserved. + if includeOtherDenom && converted.AmountOf("other").String() != math.NewIntFromUint64(amount+1).String() { + t.Fatalf("unexpected non-evm denom amount: %s", converted.AmountOf("other")) + } + }) +} From 0eefde44cebfc853b04110e11ee1c18d6b41200c Mon Sep 17 00:00:00 2001 From: aljo242 Date: Fri, 15 May 2026 11:45:36 -0400 Subject: [PATCH 2/3] test: fix fuzz edge case and lint naming Address PR feedback by guarding the fuzz input against uint64 overflow and rename local variables that shadow Go builtins so lint passes cleanly. Co-authored-by: Cursor --- .../x/erc20/test_conversion_invariants_randomized.go | 8 ++++---- x/vm/types/scaling_fuzz_test.go | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/integration/x/erc20/test_conversion_invariants_randomized.go b/tests/integration/x/erc20/test_conversion_invariants_randomized.go index cb141ea658..bc7ffcc7c1 100644 --- a/tests/integration/x/erc20/test_conversion_invariants_randomized.go +++ b/tests/integration/x/erc20/test_conversion_invariants_randomized.go @@ -62,16 +62,16 @@ func (s *KeeperTestSuite) TestRandomizedConvertRoundTripInvariant() { } if convertFromERC20 { - max := erc20Bal.Int64() - amount := int64(rng.Intn(int(max)) + 1) + maxAmount := erc20Bal.Int64() + amount := int64(rng.Intn(int(maxAmount)) + 1) _, err = erc20Keeper.ConvertERC20( ctx, types.NewMsgConvertERC20(math.NewInt(amount), senderAcc, contractAddr, senderHex), ) s.Require().NoError(err) } else { - max := coinBal.Int64() - amount := int64(rng.Intn(int(max)) + 1) + maxAmount := coinBal.Int64() + amount := int64(rng.Intn(int(maxAmount)) + 1) _, err = erc20Keeper.ConvertCoin( ctx, types.NewMsgConvertCoin(sdk.NewCoin(denom, math.NewInt(amount)), senderHex, senderAcc), diff --git a/x/vm/types/scaling_fuzz_test.go b/x/vm/types/scaling_fuzz_test.go index a1f6260ee6..88b415911b 100644 --- a/x/vm/types/scaling_fuzz_test.go +++ b/x/vm/types/scaling_fuzz_test.go @@ -1,6 +1,7 @@ package types_test import ( + stdmath "math" "testing" testconstants "github.com/cosmos/evm/testutil/constants" @@ -27,7 +28,7 @@ func FuzzConvertCoinsDenomToExtendedDenomWithEvmParams(f *testing.F) { f.Fuzz(func(t *testing.T, amount uint64, includeOtherDenom bool) { input := sdk.NewCoins(sdk.NewCoin(coinInfo.Denom, math.NewIntFromUint64(amount))) - if includeOtherDenom { + if includeOtherDenom && amount < stdmath.MaxUint64 { input = input.Add(sdk.NewCoin("other", math.NewIntFromUint64(amount+1))).Sort() } @@ -45,7 +46,7 @@ func FuzzConvertCoinsDenomToExtendedDenomWithEvmParams(f *testing.F) { } // Non-EVM denoms should be preserved. - if includeOtherDenom && converted.AmountOf("other").String() != math.NewIntFromUint64(amount+1).String() { + if includeOtherDenom && amount < stdmath.MaxUint64 && converted.AmountOf("other").String() != math.NewIntFromUint64(amount+1).String() { t.Fatalf("unexpected non-evm denom amount: %s", converted.AmountOf("other")) } }) From 100f0b8ec905c540ff5d98af1f5a17d8140bd4f7 Mon Sep 17 00:00:00 2001 From: aljo242 Date: Fri, 15 May 2026 14:01:44 -0400 Subject: [PATCH 3/3] test: strengthen conversion invariants and fuzz guidance Add deterministic boundary coverage around conversion invariants, include a regression test for the MaxUint64 fuzz edge case, and document local commands for running the denom conversion fuzz target. Co-authored-by: Cursor --- README.md | 12 ++++ .../test_conversion_invariants_randomized.go | 65 +++++++++++++++++-- x/vm/types/scaling_fuzz_test.go | 27 ++++++-- 3 files changed, 93 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6d188c63a7..68d0e8e6e5 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,18 @@ make test-unit-cover make test-fuzz ``` +Run a specific fuzz target locally (example for denom relabeling invariants): + +```bash +go test -tags=test ./x/vm/types -run '^$' -fuzz FuzzConvertCoinsDenomToExtendedDenomWithEvmParams -fuzztime=30s +``` + +Re-run with a longer fuzz window: + +```bash +go test -tags=test ./x/vm/types -run '^$' -fuzz FuzzConvertCoinsDenomToExtendedDenomWithEvmParams -fuzztime=5m +``` + #### Solidity Tests ```bash diff --git a/tests/integration/x/erc20/test_conversion_invariants_randomized.go b/tests/integration/x/erc20/test_conversion_invariants_randomized.go index bc7ffcc7c1..11dc95b6af 100644 --- a/tests/integration/x/erc20/test_conversion_invariants_randomized.go +++ b/tests/integration/x/erc20/test_conversion_invariants_randomized.go @@ -4,6 +4,7 @@ import ( "fmt" "math/big" "math/rand" + "strings" "github.com/cosmos/evm/contracts" "github.com/cosmos/evm/x/erc20/types" @@ -45,13 +46,66 @@ func (s *KeeperTestSuite) TestRandomizedConvertRoundTripInvariant() { bankKeeper := s.network.App.GetBankKeeper() erc20ABI := contracts.ERC20MinterBurnerDecimalsContract.ABI - for i := 0; i < steps; i++ { + assertInvariants := func(phase string) { ctx := s.network.GetContext() erc20Bal := erc20Keeper.BalanceOf(ctx, erc20ABI, contractAddr, senderHex) coinBal := bankKeeper.GetBalance(ctx, senderAcc, denom).Amount combined := new(big.Int).Add(erc20Bal, coinBal.BigInt()) - s.Require().Equal(total.String(), combined.String(), "value should be conserved before step") + s.Require().Equal(total.String(), combined.String(), "value should be conserved at %s", phase) + + for _, bal := range bankKeeper.GetAllBalances(ctx, senderAcc) { + if strings.HasPrefix(bal.Denom, types.Erc20NativeCoinDenomPrefix) { + s.Require().Equal(denom, bal.Denom, "unexpected erc20 native denom at %s", phase) + } + } + } + + // Deterministic edge sequence before random steps: + // smallest amount in/out, then full-balance in/out. + assertInvariants("initial") + + _, err = erc20Keeper.ConvertERC20( + s.network.GetContext(), + types.NewMsgConvertERC20(math.NewInt(1), senderAcc, contractAddr, senderHex), + ) + s.Require().NoError(err) + assertInvariants("after convertERC20(1)") + + _, err = erc20Keeper.ConvertCoin( + s.network.GetContext(), + types.NewMsgConvertCoin(sdk.NewCoin(denom, math.NewInt(1)), senderHex, senderAcc), + ) + s.Require().NoError(err) + assertInvariants("after convertCoin(1)") + + ctx := s.network.GetContext() + allERC20 := erc20Keeper.BalanceOf(ctx, erc20ABI, contractAddr, senderHex) + if allERC20.Sign() > 0 { + _, err = erc20Keeper.ConvertERC20( + ctx, + types.NewMsgConvertERC20(math.NewIntFromBigInt(allERC20), senderAcc, contractAddr, senderHex), + ) + s.Require().NoError(err) + assertInvariants("after full convertERC20") + } + + allCoin := bankKeeper.GetBalance(s.network.GetContext(), senderAcc, denom).Amount + if !allCoin.IsZero() { + _, err = erc20Keeper.ConvertCoin( + s.network.GetContext(), + types.NewMsgConvertCoin(sdk.NewCoin(denom, allCoin), senderHex, senderAcc), + ) + s.Require().NoError(err) + assertInvariants("after full convertCoin") + } + + for i := 0; i < steps; i++ { + ctx := s.network.GetContext() + erc20Bal := erc20Keeper.BalanceOf(ctx, erc20ABI, contractAddr, senderHex) + coinBal := bankKeeper.GetBalance(ctx, senderAcc, denom).Amount + + assertInvariants(fmt.Sprintf("before randomized step %d", i)) convertFromERC20 := rng.Intn(2) == 0 if erc20Bal.Sign() == 0 { @@ -78,13 +132,10 @@ func (s *KeeperTestSuite) TestRandomizedConvertRoundTripInvariant() { ) s.Require().NoError(err) } + assertInvariants(fmt.Sprintf("after randomized step %d", i)) } - ctx := s.network.GetContext() - finalERC20Bal := erc20Keeper.BalanceOf(ctx, erc20ABI, contractAddr, senderHex) - finalCoinBal := bankKeeper.GetBalance(ctx, senderAcc, denom).Amount - finalCombined := new(big.Int).Add(finalERC20Bal, finalCoinBal.BigInt()) - s.Require().Equal(total.String(), finalCombined.String(), "value should be conserved after sequence") + assertInvariants("final") }) } } diff --git a/x/vm/types/scaling_fuzz_test.go b/x/vm/types/scaling_fuzz_test.go index 88b415911b..c3fd3261b6 100644 --- a/x/vm/types/scaling_fuzz_test.go +++ b/x/vm/types/scaling_fuzz_test.go @@ -4,6 +4,8 @@ import ( stdmath "math" "testing" + "github.com/stretchr/testify/require" + testconstants "github.com/cosmos/evm/testutil/constants" evmtypes "github.com/cosmos/evm/x/vm/types" @@ -12,6 +14,16 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +func buildFuzzInputCoins(evmDenom string, amount uint64, includeOtherDenom bool) sdk.Coins { + input := sdk.NewCoins(sdk.NewCoin(evmDenom, math.NewIntFromUint64(amount))) + // Guard against uint64 wraparound for amount+1 when amount == MaxUint64. + if includeOtherDenom && amount < stdmath.MaxUint64 { + input = input.Add(sdk.NewCoin("other", math.NewIntFromUint64(amount+1))).Sort() + } + + return input +} + func FuzzConvertCoinsDenomToExtendedDenomWithEvmParams(f *testing.F) { f.Add(uint64(0), false) f.Add(uint64(1), false) @@ -27,10 +39,7 @@ func FuzzConvertCoinsDenomToExtendedDenomWithEvmParams(f *testing.F) { } f.Fuzz(func(t *testing.T, amount uint64, includeOtherDenom bool) { - input := sdk.NewCoins(sdk.NewCoin(coinInfo.Denom, math.NewIntFromUint64(amount))) - if includeOtherDenom && amount < stdmath.MaxUint64 { - input = input.Add(sdk.NewCoin("other", math.NewIntFromUint64(amount+1))).Sort() - } + input := buildFuzzInputCoins(coinInfo.Denom, amount, includeOtherDenom) converted := evmtypes.ConvertCoinsDenomToExtendedDenomWithEvmParams(input, params) convertedAgain := evmtypes.ConvertCoinsDenomToExtendedDenomWithEvmParams(converted, params) @@ -51,3 +60,13 @@ func FuzzConvertCoinsDenomToExtendedDenomWithEvmParams(f *testing.F) { } }) } + +func TestBuildFuzzInputCoinsMaxUint64Guard(t *testing.T) { + coinInfo := testconstants.ExampleChainCoinInfo[testconstants.ExampleChainID] + + inputAtMax := buildFuzzInputCoins(coinInfo.Denom, stdmath.MaxUint64, true) + require.Equal(t, "0", inputAtMax.AmountOf("other").String()) + + inputBelowMax := buildFuzzInputCoins(coinInfo.Denom, stdmath.MaxUint64-1, true) + require.Equal(t, math.NewIntFromUint64(stdmath.MaxUint64).String(), inputBelowMax.AmountOf("other").String()) +}