diff --git a/CHANGELOG.md b/CHANGELOG.md index 20a640e4..4df4be05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,9 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] +## Features +- (evmutil) [#1590] Add allow list param of sdk native denoms that can be transferred to evm + ## [v0.23.0] ### Improvements @@ -237,6 +240,7 @@ the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.38.4/CHANGELOG.md). - [#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run large-scale simulations remotely using aws-batch +[#1590]: https://github.com/Kava-Labs/kava/pull/1590 [#1568]: https://github.com/Kava-Labs/kava/pull/1568 [#1567]: https://github.com/Kava-Labs/kava/pull/1567 [#1566]: https://github.com/Kava-Labs/kava/pull/1566 diff --git a/app/ante/eip712_test.go b/app/ante/eip712_test.go index e842e2ac..a2ed4e27 100644 --- a/app/ante/eip712_test.go +++ b/app/ante/eip712_test.go @@ -335,15 +335,16 @@ func (suite *EIP712TestSuite) SetupTest() { suite.usdcEVMAddr = pair.GetAddress() // Add a contract to evmutil conversion pair - suite.evmutilKeeper.SetParams(suite.ctx, evmutiltypes.NewParams( + evmutilParams := suite.evmutilKeeper.GetParams(suite.ctx) + evmutilParams.EnabledConversionPairs = evmutiltypes.NewConversionPairs( evmutiltypes.NewConversionPair( // First contract evmutil module deploys evmutiltestutil.MustNewInternalEVMAddressFromString("0x15932E26f5BD4923d46a2b205191C4b5d5f43FE3"), "erc20/usdc", ), - ), - )) + ) + suite.evmutilKeeper.SetParams(suite.ctx, evmutilParams) // allow msgs through evm eip712 evmKeeper := suite.tApp.GetEvmKeeper() diff --git a/ci/env/kava-protonet/genesis.json b/ci/env/kava-protonet/genesis.json index 0425cd7c..f332285e 100644 --- a/ci/env/kava-protonet/genesis.json +++ b/ci/env/kava-protonet/genesis.json @@ -2004,6 +2004,7 @@ "evmutil": { "accounts": [], "params": { + "allowed_native_denoms": [], "enabled_conversion_pairs": [ { "kava_erc20_address": "0xBb304f44b7EFD865361F2AD973d8ebA433893ABC", diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index c4ba3320..dfb9ebda 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -247,6 +247,7 @@ - [Msg](#kava.earn.v1beta1.Msg) - [kava/evmutil/v1beta1/conversion_pair.proto](#kava/evmutil/v1beta1/conversion_pair.proto) + - [AllowedNativeCoinERC20Token](#kava.evmutil.v1beta1.AllowedNativeCoinERC20Token) - [ConversionPair](#kava.evmutil.v1beta1.ConversionPair) - [kava/evmutil/v1beta1/genesis.proto](#kava/evmutil/v1beta1/genesis.proto) @@ -3658,6 +3659,27 @@ Msg defines the earn Msg service. + + +### AllowedNativeCoinERC20Token +AllowedNativeCoinERC20Token defines allowed sdk denom & metadata +for evm token representations of sdk assets. +NOTE: once evm token contracts are deployed, changes to metadata for a given +sdk_denom will not change metadata of deployed contract. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `sdk_denom` | [string](#string) | | Denom of the sdk.Coin | +| `name` | [string](#string) | | Name of ERC20 contract | +| `symbol` | [string](#string) | | Symbol of ERC20 contract | +| `decimals` | [uint32](#uint32) | | Number of decimals ERC20 contract is deployed with. | + + + + + + ### ConversionPair @@ -3732,6 +3754,7 @@ Params defines the evmutil module params | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | `enabled_conversion_pairs` | [ConversionPair](#kava.evmutil.v1beta1.ConversionPair) | repeated | enabled_conversion_pairs defines the list of conversion pairs allowed to be converted between Kava ERC20 and sdk.Coin | +| `allowed_native_denoms` | [AllowedNativeCoinERC20Token](#kava.evmutil.v1beta1.AllowedNativeCoinERC20Token) | repeated | allowed_native_denoms is a list of denom & erc20 token metadata pairs. if a denom is in the list, it is allowed to be converted to an erc20 in the evm. | diff --git a/proto/kava/evmutil/v1beta1/conversion_pair.proto b/proto/kava/evmutil/v1beta1/conversion_pair.proto index 60ccce8f..968233bc 100644 --- a/proto/kava/evmutil/v1beta1/conversion_pair.proto +++ b/proto/kava/evmutil/v1beta1/conversion_pair.proto @@ -21,3 +21,20 @@ message ConversionPair { // Denom of the corresponding sdk.Coin string denom = 2; } + +// AllowedNativeCoinERC20Token defines allowed sdk denom & metadata +// for evm token representations of sdk assets. +// NOTE: once evm token contracts are deployed, changes to metadata for a given +// sdk_denom will not change metadata of deployed contract. +message AllowedNativeCoinERC20Token { + option (gogoproto.goproto_getters) = false; + + // Denom of the sdk.Coin + string sdk_denom = 1; + // Name of ERC20 contract + string name = 2; + // Symbol of ERC20 contract + string symbol = 3; + // Number of decimals ERC20 contract is deployed with. + uint32 decimals = 4; +} diff --git a/proto/kava/evmutil/v1beta1/genesis.proto b/proto/kava/evmutil/v1beta1/genesis.proto index 0a7872d2..0831db15 100644 --- a/proto/kava/evmutil/v1beta1/genesis.proto +++ b/proto/kava/evmutil/v1beta1/genesis.proto @@ -44,4 +44,11 @@ message Params { (gogoproto.nullable) = false, (gogoproto.castrepeated) = "ConversionPairs" ]; + + // allowed_native_denoms is a list of denom & erc20 token metadata pairs. + // if a denom is in the list, it is allowed to be converted to an erc20 in the evm. + repeated AllowedNativeCoinERC20Token allowed_native_denoms = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "AllowedNativeCoinERC20Tokens" + ]; } diff --git a/x/evmutil/genesis_test.go b/x/evmutil/genesis_test.go index 7f5e01f3..9df72bbd 100644 --- a/x/evmutil/genesis_test.go +++ b/x/evmutil/genesis_test.go @@ -79,6 +79,14 @@ func (s *genesisTestSuite) TestExportGenesis() { KavaERC20Address: testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").Bytes(), Denom: "weth"}, } + params.AllowedNativeDenoms = []types.AllowedNativeCoinERC20Token{ + { + SdkDenom: "hard", + Name: "Kava EVM HARD", + Symbol: "HARD", + Decimals: 6, + }, + } s.Keeper.SetParams(s.Ctx, params) gs := evmutil.ExportGenesis(s.Ctx, s.Keeper) s.Require().Equal(gs.Accounts, accounts) diff --git a/x/evmutil/keeper/migrations.go b/x/evmutil/keeper/migrations.go new file mode 100644 index 00000000..f74aa0ca --- /dev/null +++ b/x/evmutil/keeper/migrations.go @@ -0,0 +1,23 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + v2 "github.com/kava-labs/kava/x/evmutil/migrations/v2" +) + +// Migrator is a struct for handling in-place store migrations. +type Migrator struct { + keeper Keeper +} + +// NewMigrator returns a new Migrator. +func NewMigrator(keeper Keeper) Migrator { + return Migrator{ + keeper: keeper, + } +} + +// Migrate1to2 migrates from version 1 to 2. +func (m Migrator) Migrate1to2(ctx sdk.Context) error { + return v2.MigrateStore(ctx, m.keeper.paramSubspace) +} diff --git a/x/evmutil/keeper/params.go b/x/evmutil/keeper/params.go index b45e6cae..2aae4112 100644 --- a/x/evmutil/keeper/params.go +++ b/x/evmutil/keeper/params.go @@ -11,7 +11,7 @@ import ( // GetParams returns the total set of evm parameters. func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { - k.paramSubspace.GetParamSet(ctx, ¶ms) + k.paramSubspace.GetParamSetIfExists(ctx, ¶ms) return params } diff --git a/x/evmutil/keeper/params_test.go b/x/evmutil/keeper/params_test.go index e6c74c99..0d822117 100644 --- a/x/evmutil/keeper/params_test.go +++ b/x/evmutil/keeper/params_test.go @@ -5,6 +5,9 @@ import ( "github.com/stretchr/testify/suite" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/evmutil/keeper" "github.com/kava-labs/kava/x/evmutil/testutil" "github.com/kava-labs/kava/x/evmutil/types" ) @@ -34,3 +37,26 @@ func (suite *ParamsTestSuite) TestEnabledConversionPair() { suite.Require().NoError(err) suite.Require().Equal(expPair, actualPair) } + +func (suite *ParamsTestSuite) TestHistoricParamsQuery() { + // setup a params store that lacks allowed_native_denoms param (as was the case in v1) + oldParamStore := suite.App.GetParamsKeeper().Subspace("test_subspace_for_evmutil") + oldParamStore.WithKeyTable(types.ParamKeyTable()) + oldParamStore.Set(suite.Ctx, types.KeyEnabledConversionPairs, types.ConversionPairs{}) + + suite.True(oldParamStore.Has(suite.Ctx, types.KeyEnabledConversionPairs)) + suite.False(oldParamStore.Has(suite.Ctx, types.KeyAllowedNativeDenoms)) + + oldStateKeeper := keeper.NewKeeper( + suite.App.AppCodec(), + sdk.NewKVStoreKey(types.StoreKey), + oldParamStore, + suite.App.GetBankKeeper(), + suite.App.GetAccountKeeper(), + ) + + // prior to making GetParams() use GetParamSetIfExists, this would panic. + suite.NotPanics(func() { + _ = oldStateKeeper.GetParams(suite.Ctx) + }) +} diff --git a/x/evmutil/migrations/v2/store.go b/x/evmutil/migrations/v2/store.go new file mode 100644 index 00000000..80529068 --- /dev/null +++ b/x/evmutil/migrations/v2/store.go @@ -0,0 +1,23 @@ +package v2 + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + "github.com/kava-labs/kava/x/evmutil/types" +) + +// MigrateStore performs in-place store migrations for consensus version 2 +// V2 adds the allowed_native_denoms param to parameters. +func MigrateStore(ctx sdk.Context, paramstore paramtypes.Subspace) error { + migrateParamsStore(ctx, paramstore) + return nil +} + +// migrateParamsStore ensures the param key table exists and has the allowed_native_denoms property +func migrateParamsStore(ctx sdk.Context, paramstore paramtypes.Subspace) { + if !paramstore.HasKeyTable() { + paramstore.WithKeyTable(types.ParamKeyTable()) + } + paramstore.Set(ctx, types.KeyAllowedNativeDenoms, types.DefaultAllowedNativeDenoms) +} diff --git a/x/evmutil/migrations/v2/store_test.go b/x/evmutil/migrations/v2/store_test.go new file mode 100644 index 00000000..16455eac --- /dev/null +++ b/x/evmutil/migrations/v2/store_test.go @@ -0,0 +1,54 @@ +package v2_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + v2evmutil "github.com/kava-labs/kava/x/evmutil/migrations/v2" + "github.com/kava-labs/kava/x/evmutil/types" +) + +func TestStoreMigrationAddsKeyTableIncludingNewParam(t *testing.T) { + encCfg := simapp.MakeTestEncodingConfig() + evmutilKey := sdk.NewKVStoreKey(types.ModuleName) + tEvmutilKey := sdk.NewTransientStoreKey("transient_test") + ctx := testutil.DefaultContext(evmutilKey, tEvmutilKey) + paramstore := paramtypes.NewSubspace(encCfg.Codec, encCfg.Amino, evmutilKey, tEvmutilKey, types.ModuleName) + + // Check param doesn't exist before + require.False(t, paramstore.Has(ctx, types.KeyAllowedNativeDenoms)) + + // Run migrations. + err := v2evmutil.MigrateStore(ctx, paramstore) + require.NoError(t, err) + + // Make sure the new params are set. + require.True(t, paramstore.Has(ctx, types.KeyAllowedNativeDenoms)) +} + +func TestStoreMigrationSetsNewParamOnExistingKeyTable(t *testing.T) { + encCfg := simapp.MakeTestEncodingConfig() + evmutilKey := sdk.NewKVStoreKey(types.ModuleName) + tEvmutilKey := sdk.NewTransientStoreKey("transient_test") + ctx := testutil.DefaultContext(evmutilKey, tEvmutilKey) + paramstore := paramtypes.NewSubspace(encCfg.Codec, encCfg.Amino, evmutilKey, tEvmutilKey, types.ModuleName) + paramstore.WithKeyTable(types.ParamKeyTable()) + + // expect it to have key table + require.True(t, paramstore.HasKeyTable()) + // expect it to not have new param + require.False(t, paramstore.Has(ctx, types.KeyAllowedNativeDenoms)) + + // Run migrations. + err := v2evmutil.MigrateStore(ctx, paramstore) + require.NoError(t, err) + + // Make sure the new params are set. + require.True(t, paramstore.Has(ctx, types.KeyAllowedNativeDenoms)) +} diff --git a/x/evmutil/module.go b/x/evmutil/module.go index a9ceaac6..709795bb 100644 --- a/x/evmutil/module.go +++ b/x/evmutil/module.go @@ -21,7 +21,7 @@ import ( ) // ConsensusVersion defines the current module consensus version. -const ConsensusVersion = 1 +const ConsensusVersion = 2 var ( _ module.AppModule = AppModule{} @@ -127,6 +127,9 @@ func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sd 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) + cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2) } // RegisterInvariants registers evmutil module's invariants. diff --git a/x/evmutil/spec/02_state.md b/x/evmutil/spec/02_state.md index 3f2c2d48..8d785629 100644 --- a/x/evmutil/spec/02_state.md +++ b/x/evmutil/spec/02_state.md @@ -6,7 +6,7 @@ order: 2 ## Parameters and Genesis State -`Parameters` define the list of conversion pairs allowed to be converted between Kava ERC20 tokens and sdk.Coins. +`Parameters` define the list of conversion pairs allowed to be converted between Kava ERC20 tokens & sdk.Coins, and the list of native sdk.Coins that are allowed to be converted to ERC20s. ```protobuf // Params defines the evmutil module params @@ -14,6 +14,10 @@ message Params { // enabled_conversion_pairs defines the list of conversion pairs allowed to be // converted between Kava ERC20 and sdk.Coin repeated ConversionPair enabled_conversion_pairs = 4; + + // allowed_native_denoms is a list of denom & erc20 token metadata pairs. + // if a denom is in the list, it is allowed to be converted to an erc20 in the evm. + repeated AllowedNativeCoinERC20Token allowed_native_denoms = 1; } // ConversionPair defines a Kava ERC20 address and corresponding denom that is @@ -24,6 +28,24 @@ message ConversionPair { // Denom of the corresponding sdk.Coin string denom = 2; } + +// AllowedNativeCoinERC20Token defines allowed sdk denom & metadata +// for evm token representations of sdk assets. +// NOTE: once evm token contracts are deployed, changes to metadata for a given +// sdk_denom will not change metadata of deployed contract. +message AllowedNativeCoinERC20Token { + option (gogoproto.goproto_getters) = false; + + // Denom of the sdk.Coin + string sdk_denom = 1; + // Name of ERC20 contract + string name = 2; + // Symbol of ERC20 contract + string symbol = 3; + // Number of decimals ERC20 contract is deployed with. + uint32 decimal = 4; +} + ``` `GenesisState` defines the state that must be persisted when the blockchain stops/restarts in order for normal function of the evmutil module to resume. diff --git a/x/evmutil/spec/05_params.md b/x/evmutil/spec/05_params.md index d1edf4fe..3913da27 100644 --- a/x/evmutil/spec/05_params.md +++ b/x/evmutil/spec/05_params.md @@ -6,9 +6,10 @@ order: 5 The evmutil module contains the following parameters: -| Key | Type | Example | -| ---------------------- | ---------------------- | ------------- | -| EnabledConversionPairs | array (ConversionPair) | [{see below}] | +| Key | Type | Example | +| ---------------------- | ------------------------------------ | ------------- | +| EnabledConversionPairs | array (ConversionPair) | [{see below}] | +| AllowedNativeDenoms | array (AllowedNativeCoinERC20Tokens) | [{see below}] | Example parameters for `ConversionPair`: @@ -17,6 +18,19 @@ Example parameters for `ConversionPair`: | kava_erc20_Address | string | "0x43d8814fdfb9b8854422df13f1c66e34e4fa91fd" | ERC20 contract address | | denom | string | "erc20/chain/usdc" | sdk.Coin denom for the ERC20 token | +Example parameters for `AllowedNativeCoinERC20Token`: + +| Key | Type | Example | Description | +| --------- | ------ | ---------------------------------------------------------------------- | -------------------------------------------------- | +| sdk_denom | string | "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2" | denom of the sdk.Coin | +| name | string | "Kava-wrapped Atom" | name field of the erc20 token | +| symbol | string | "kATOM" | symbol field of the erc20 token | +| decimal | uint32 | 6 | decimal field of the erc20 token, for display only | + ## EnabledConversionPairs The enabled conversion pairs parameter is an array of ConversionPair entries mapping an erc20 address to a sdk.Coin denom. Only erc20 contract addresses that are in this list can be converted to sdk.Coin and vice versa. + +## AllowedNativeDenoms + +The allowed native denoms parameter is an array of AllowedNativeCoinERC20Token entries. They include the sdk.Coin denom and metadata for the ERC20 representation of the asset in Kava's EVM. Coins may only be transferred to the EVM if they are included in this list. A token in this list will have an ERC20 token contract deployed on first conversion. The token will be deployed with the metadata included in the AllowedNativeCoinERC20Token. Once deployed, changes to the metadata will not affect or change the deployed contract. diff --git a/x/evmutil/testutil/suite.go b/x/evmutil/testutil/suite.go index d8b7e1f1..c36882bd 100644 --- a/x/evmutil/testutil/suite.go +++ b/x/evmutil/testutil/suite.go @@ -156,6 +156,7 @@ func (suite *Suite) SetupTest() { "erc20/usdc", ), ), + types.NewAllowedNativeCoinERC20Tokens(), )) queryHelper := baseapp.NewQueryServerTestHelper(suite.Ctx, suite.App.InterfaceRegistry()) diff --git a/x/evmutil/types/conversion_pair.go b/x/evmutil/types/conversion_pair.go index d6716df4..0d74a7fe 100644 --- a/x/evmutil/types/conversion_pair.go +++ b/x/evmutil/types/conversion_pair.go @@ -3,12 +3,18 @@ package types import ( bytes "bytes" "encoding/hex" + "errors" "fmt" + "math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" ) +/////////////// +// EVM -> SDK +/////////////// + // NewConversionPair returns a new ConversionPair. func NewConversionPair(address InternalEVMAddress, denom string) ConversionPair { return ConversionPair{ @@ -87,3 +93,87 @@ func validateConversionPairs(i interface{}) error { return pairs.Validate() } + +/////////////// +// SDK -> EVM +/////////////// + +// NewAllowedNativeCoinERC20Token returns an AllowedNativeCoinERC20Token +func NewAllowedNativeCoinERC20Token( + sdkDenom, name, symbol string, + decimal uint32, +) AllowedNativeCoinERC20Token { + return AllowedNativeCoinERC20Token{ + SdkDenom: sdkDenom, + Name: name, + Symbol: symbol, + Decimals: decimal, + } +} + +// Validate validates the fields of a single AllowedNativeCoinERC20Token +func (token AllowedNativeCoinERC20Token) Validate() error { + // disallow empty string fields + if err := sdk.ValidateDenom(token.SdkDenom); err != nil { + return fmt.Errorf("allowed native coin erc20 token's sdk denom is invalid: %v", err) + } + + if token.Name == "" { + return errors.New("allowed native coin erc20 token's name cannot be empty") + } + + if token.Symbol == "" { + return errors.New("allowed native coin erc20 token's symbol cannot be empty") + } + + // ensure decimals will properly cast to uint8 of erc20 spec + if token.Decimals > math.MaxUint8 { + return fmt.Errorf("allowed native coin erc20 token's decimals must be less than 256, found %d", token.Decimals) + } + + return nil +} + +// AllowedNativeCoinERC20Tokens defines a slice of AllowedNativeCoinERC20Token +type AllowedNativeCoinERC20Tokens []AllowedNativeCoinERC20Token + +// NewAllowedNativeCoinERC20Tokens returns AllowedNativeCoinERC20Tokens from the provided values. +func NewAllowedNativeCoinERC20Tokens(pairs ...AllowedNativeCoinERC20Token) AllowedNativeCoinERC20Tokens { + return AllowedNativeCoinERC20Tokens(pairs) +} + +// Validate checks that all containing tokens are valid and that there are +// no duplicate denoms or symbols. +func (tokens AllowedNativeCoinERC20Tokens) Validate() error { + // Disallow multiple instances of a single sdk_denom or evm symbol + denoms := make(map[string]struct{}, len(tokens)) + symbols := make(map[string]struct{}, len(tokens)) + + for i, t := range tokens { + if err := t.Validate(); err != nil { + return fmt.Errorf("invalid token at index %d: %s", i, err) + } + + if _, found := denoms[t.SdkDenom]; found { + return fmt.Errorf("found duplicate token with sdk denom %s", t.SdkDenom) + } + if _, found := symbols[t.Symbol]; found { + return fmt.Errorf("found duplicate token with symbol %s", t.Symbol) + } + + denoms[t.SdkDenom] = struct{}{} + symbols[t.Symbol] = struct{}{} + } + + return nil +} + +// validateAllowedNativeCoinERC20Tokens validates an interface as AllowedNativeCoinERC20Tokens +func validateAllowedNativeCoinERC20Tokens(i interface{}) error { + pairs, ok := i.(AllowedNativeCoinERC20Tokens) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + return pairs.Validate() +} diff --git a/x/evmutil/types/conversion_pair.pb.go b/x/evmutil/types/conversion_pair.pb.go index ffc5c5c4..851a945d 100644 --- a/x/evmutil/types/conversion_pair.pb.go +++ b/x/evmutil/types/conversion_pair.pb.go @@ -66,8 +66,57 @@ func (m *ConversionPair) XXX_DiscardUnknown() { var xxx_messageInfo_ConversionPair proto.InternalMessageInfo +// AllowedNativeCoinERC20Token defines allowed sdk denom & metadata +// for evm token representations of sdk assets. +// NOTE: once evm token contracts are deployed, changes to metadata for a given +// sdk_denom will not change metadata of deployed contract. +type AllowedNativeCoinERC20Token struct { + // Denom of the sdk.Coin + SdkDenom string `protobuf:"bytes,1,opt,name=sdk_denom,json=sdkDenom,proto3" json:"sdk_denom,omitempty"` + // Name of ERC20 contract + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Symbol of ERC20 contract + Symbol string `protobuf:"bytes,3,opt,name=symbol,proto3" json:"symbol,omitempty"` + // Number of decimals ERC20 contract is deployed with. + Decimals uint32 `protobuf:"varint,4,opt,name=decimals,proto3" json:"decimals,omitempty"` +} + +func (m *AllowedNativeCoinERC20Token) Reset() { *m = AllowedNativeCoinERC20Token{} } +func (m *AllowedNativeCoinERC20Token) String() string { return proto.CompactTextString(m) } +func (*AllowedNativeCoinERC20Token) ProtoMessage() {} +func (*AllowedNativeCoinERC20Token) Descriptor() ([]byte, []int) { + return fileDescriptor_e1396d08199817d0, []int{1} +} +func (m *AllowedNativeCoinERC20Token) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AllowedNativeCoinERC20Token) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AllowedNativeCoinERC20Token.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AllowedNativeCoinERC20Token) XXX_Merge(src proto.Message) { + xxx_messageInfo_AllowedNativeCoinERC20Token.Merge(m, src) +} +func (m *AllowedNativeCoinERC20Token) XXX_Size() int { + return m.Size() +} +func (m *AllowedNativeCoinERC20Token) XXX_DiscardUnknown() { + xxx_messageInfo_AllowedNativeCoinERC20Token.DiscardUnknown(m) +} + +var xxx_messageInfo_AllowedNativeCoinERC20Token proto.InternalMessageInfo + func init() { proto.RegisterType((*ConversionPair)(nil), "kava.evmutil.v1beta1.ConversionPair") + proto.RegisterType((*AllowedNativeCoinERC20Token)(nil), "kava.evmutil.v1beta1.AllowedNativeCoinERC20Token") } func init() { @@ -75,24 +124,30 @@ func init() { } var fileDescriptor_e1396d08199817d0 = []byte{ - // 264 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xca, 0x4e, 0x2c, 0x4b, - 0xd4, 0x4f, 0x2d, 0xcb, 0x2d, 0x2d, 0xc9, 0xcc, 0xd1, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, - 0xd4, 0x4f, 0xce, 0xcf, 0x2b, 0x4b, 0x2d, 0x2a, 0xce, 0xcc, 0xcf, 0x8b, 0x2f, 0x48, 0xcc, 0x2c, - 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x01, 0xa9, 0xd5, 0x83, 0xaa, 0xd5, 0x83, 0xaa, - 0x95, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0x2b, 0xd0, 0x07, 0xb1, 0x20, 0x6a, 0x95, 0x6a, 0xb8, - 0xf8, 0x9c, 0xe1, 0x86, 0x04, 0x24, 0x66, 0x16, 0x09, 0xf9, 0x71, 0x09, 0x81, 0xf4, 0xc7, 0xa7, - 0x16, 0x25, 0x1b, 0x19, 0xc4, 0x27, 0xa6, 0xa4, 0x14, 0xa5, 0x16, 0x17, 0x4b, 0x30, 0x2a, 0x30, - 0x6a, 0xf0, 0x38, 0x29, 0x3c, 0xba, 0x27, 0x2f, 0xe0, 0x9d, 0x58, 0x96, 0xe8, 0x1a, 0xe4, 0x6c, - 0x64, 0xe0, 0x08, 0x91, 0xfb, 0x75, 0x4f, 0x9e, 0xc3, 0x23, 0xb5, 0xc2, 0xa9, 0xb2, 0x24, 0xb5, - 0x38, 0x48, 0x00, 0xa4, 0xd7, 0x15, 0xa4, 0x15, 0x2a, 0x2b, 0x24, 0xc2, 0xc5, 0x9a, 0x92, 0x9a, - 0x97, 0x9f, 0x2b, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x19, 0x04, 0xe1, 0x58, 0xb1, 0x74, 0x2c, 0x90, - 0x67, 0x70, 0xf2, 0x7e, 0xf0, 0x50, 0x8e, 0x71, 0xc5, 0x23, 0x39, 0xc6, 0x13, 0x8f, 0xe4, 0x18, - 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, - 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0xd2, 0x4c, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, - 0xcf, 0xd5, 0x07, 0x19, 0xad, 0x9b, 0x93, 0x98, 0x54, 0x0c, 0x66, 0xe9, 0x57, 0xc0, 0x83, 0xa3, - 0xa4, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, 0xec, 0x23, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, - 0x60, 0x36, 0x37, 0x7f, 0x2b, 0x01, 0x00, 0x00, + // 355 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x3c, 0x91, 0xcf, 0x6a, 0xea, 0x40, + 0x18, 0xc5, 0x33, 0xf7, 0x7a, 0x45, 0x87, 0xdb, 0x22, 0x83, 0x94, 0xa0, 0x30, 0x06, 0x57, 0xb6, + 0xd0, 0x44, 0xed, 0xae, 0x3b, 0xb5, 0x42, 0x41, 0x90, 0x12, 0xba, 0xea, 0x26, 0x4c, 0x92, 0x0f, + 0x3b, 0xe4, 0xcf, 0x48, 0x26, 0xa6, 0x0a, 0x7d, 0x00, 0x97, 0x7d, 0x84, 0x2e, 0xfb, 0x28, 0x5d, + 0xba, 0xec, 0x4a, 0x6c, 0x7c, 0x8b, 0xae, 0x4a, 0xfe, 0xe0, 0xee, 0x9c, 0xf9, 0xce, 0xef, 0x30, + 0x33, 0x1f, 0xbe, 0xf2, 0x58, 0xc2, 0x0c, 0x48, 0x82, 0x55, 0xcc, 0x7d, 0x23, 0x19, 0xd8, 0x10, + 0xb3, 0x81, 0xe1, 0x88, 0x30, 0x81, 0x48, 0x72, 0x11, 0x5a, 0x4b, 0xc6, 0x23, 0x7d, 0x19, 0x89, + 0x58, 0x90, 0x66, 0x96, 0xd5, 0xcb, 0xac, 0x5e, 0x66, 0x5b, 0xcd, 0x85, 0x58, 0x88, 0x3c, 0x60, + 0x64, 0xaa, 0xc8, 0x76, 0x5f, 0xf1, 0xf9, 0xe4, 0x54, 0xf2, 0xc0, 0x78, 0x44, 0xe6, 0x98, 0x64, + 0xbc, 0x05, 0x91, 0x33, 0xec, 0x5b, 0xcc, 0x75, 0x23, 0x90, 0x52, 0x45, 0x1a, 0xea, 0xfd, 0x1f, + 0x6b, 0xe9, 0xbe, 0xd3, 0x98, 0xb1, 0x84, 0x4d, 0xcd, 0xc9, 0xb0, 0x3f, 0x2a, 0x66, 0x3f, 0xfb, + 0x4e, 0xed, 0x1e, 0xd6, 0xe3, 0x4d, 0x0c, 0xd2, 0x6c, 0x64, 0xec, 0x34, 0x43, 0xcb, 0x29, 0x69, + 0xe2, 0x7f, 0x2e, 0x84, 0x22, 0x50, 0xff, 0x68, 0xa8, 0x57, 0x37, 0x0b, 0x73, 0x5b, 0xd9, 0xbe, + 0x77, 0x94, 0xee, 0x16, 0xe1, 0xf6, 0xc8, 0xf7, 0xc5, 0x0b, 0xb8, 0x73, 0x16, 0xf3, 0x04, 0x26, + 0x82, 0x87, 0x79, 0xf7, 0xa3, 0xf0, 0x20, 0x24, 0x6d, 0x5c, 0x97, 0xae, 0x67, 0x15, 0x3c, 0xca, + 0xf9, 0x9a, 0x74, 0xbd, 0xbb, 0xcc, 0x13, 0x82, 0x2b, 0x21, 0x0b, 0xa0, 0xec, 0xcd, 0x35, 0xb9, + 0xc0, 0x55, 0xb9, 0x09, 0x6c, 0xe1, 0xab, 0x7f, 0xf3, 0xd3, 0xd2, 0x91, 0x16, 0xae, 0xb9, 0xe0, + 0xf0, 0x80, 0xf9, 0x52, 0xad, 0x68, 0xa8, 0x77, 0x66, 0x9e, 0x7c, 0x71, 0x95, 0xf1, 0xec, 0xf0, + 0x4d, 0xd1, 0x47, 0x4a, 0xd1, 0x67, 0x4a, 0xd1, 0x2e, 0xa5, 0xe8, 0x90, 0x52, 0xf4, 0x76, 0xa4, + 0xca, 0xee, 0x48, 0x95, 0xaf, 0x23, 0x55, 0x9e, 0x2e, 0x17, 0x3c, 0x7e, 0x5e, 0xd9, 0xba, 0x23, + 0x02, 0x23, 0x7b, 0xe5, 0xb5, 0xcf, 0x6c, 0x99, 0x2b, 0x63, 0x7d, 0xda, 0x4c, 0xbc, 0x59, 0x82, + 0xb4, 0xab, 0xf9, 0xe7, 0xde, 0xfc, 0x06, 0x00, 0x00, 0xff, 0xff, 0x49, 0x37, 0x2c, 0xa6, 0xb6, + 0x01, 0x00, 0x00, } func (this *ConversionPair) VerboseEqual(that interface{}) error { @@ -155,6 +210,78 @@ func (this *ConversionPair) Equal(that interface{}) bool { } return true } +func (this *AllowedNativeCoinERC20Token) VerboseEqual(that interface{}) error { + if that == nil { + if this == nil { + return nil + } + return fmt.Errorf("that == nil && this != nil") + } + + that1, ok := that.(*AllowedNativeCoinERC20Token) + if !ok { + that2, ok := that.(AllowedNativeCoinERC20Token) + if ok { + that1 = &that2 + } else { + return fmt.Errorf("that is not of type *AllowedNativeCoinERC20Token") + } + } + if that1 == nil { + if this == nil { + return nil + } + return fmt.Errorf("that is type *AllowedNativeCoinERC20Token but is nil && this != nil") + } else if this == nil { + return fmt.Errorf("that is type *AllowedNativeCoinERC20Token but is not nil && this == nil") + } + if this.SdkDenom != that1.SdkDenom { + return fmt.Errorf("SdkDenom this(%v) Not Equal that(%v)", this.SdkDenom, that1.SdkDenom) + } + if this.Name != that1.Name { + return fmt.Errorf("Name this(%v) Not Equal that(%v)", this.Name, that1.Name) + } + if this.Symbol != that1.Symbol { + return fmt.Errorf("Symbol this(%v) Not Equal that(%v)", this.Symbol, that1.Symbol) + } + if this.Decimals != that1.Decimals { + return fmt.Errorf("Decimals this(%v) Not Equal that(%v)", this.Decimals, that1.Decimals) + } + return nil +} +func (this *AllowedNativeCoinERC20Token) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*AllowedNativeCoinERC20Token) + if !ok { + that2, ok := that.(AllowedNativeCoinERC20Token) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.SdkDenom != that1.SdkDenom { + return false + } + if this.Name != that1.Name { + return false + } + if this.Symbol != that1.Symbol { + return false + } + if this.Decimals != that1.Decimals { + return false + } + return true +} func (m *ConversionPair) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -192,6 +319,55 @@ func (m *ConversionPair) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *AllowedNativeCoinERC20Token) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AllowedNativeCoinERC20Token) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AllowedNativeCoinERC20Token) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Decimals != 0 { + i = encodeVarintConversionPair(dAtA, i, uint64(m.Decimals)) + i-- + dAtA[i] = 0x20 + } + if len(m.Symbol) > 0 { + i -= len(m.Symbol) + copy(dAtA[i:], m.Symbol) + i = encodeVarintConversionPair(dAtA, i, uint64(len(m.Symbol))) + i-- + dAtA[i] = 0x1a + } + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintConversionPair(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0x12 + } + if len(m.SdkDenom) > 0 { + i -= len(m.SdkDenom) + copy(dAtA[i:], m.SdkDenom) + i = encodeVarintConversionPair(dAtA, i, uint64(len(m.SdkDenom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintConversionPair(dAtA []byte, offset int, v uint64) int { offset -= sovConversionPair(v) base := offset @@ -220,6 +396,30 @@ func (m *ConversionPair) Size() (n int) { return n } +func (m *AllowedNativeCoinERC20Token) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SdkDenom) + if l > 0 { + n += 1 + l + sovConversionPair(uint64(l)) + } + l = len(m.Name) + if l > 0 { + n += 1 + l + sovConversionPair(uint64(l)) + } + l = len(m.Symbol) + if l > 0 { + n += 1 + l + sovConversionPair(uint64(l)) + } + if m.Decimals != 0 { + n += 1 + sovConversionPair(uint64(m.Decimals)) + } + return n +} + func sovConversionPair(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -342,6 +542,171 @@ func (m *ConversionPair) Unmarshal(dAtA []byte) error { } return nil } +func (m *AllowedNativeCoinERC20Token) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConversionPair + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AllowedNativeCoinERC20Token: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AllowedNativeCoinERC20Token: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SdkDenom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConversionPair + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConversionPair + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConversionPair + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SdkDenom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConversionPair + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConversionPair + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConversionPair + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Symbol", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConversionPair + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConversionPair + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConversionPair + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Symbol = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Decimals", wireType) + } + m.Decimals = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConversionPair + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Decimals |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipConversionPair(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthConversionPair + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipConversionPair(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/evmutil/types/conversion_pairs_test.go b/x/evmutil/types/conversion_pairs_test.go index 0e349d1d..2a0020d0 100644 --- a/x/evmutil/types/conversion_pairs_test.go +++ b/x/evmutil/types/conversion_pairs_test.go @@ -231,3 +231,125 @@ func TestConversionPairs_Validate(t *testing.T) { }) } } + +func TestAllowedNativeCoinERC20Token_Validate(t *testing.T) { + testCases := []struct { + name string + token types.AllowedNativeCoinERC20Token + expErr string + }{ + { + name: "valid token", + token: types.NewAllowedNativeCoinERC20Token("uatom", "Kava-wrapped ATOM", "kATOM", 6), + expErr: "", + }, + { + name: "valid - highest allowed decimals", + token: types.NewAllowedNativeCoinERC20Token("uatom", "Kava-wrapped ATOM", "kATOM", 255), + expErr: "", + }, + { + name: "invalid - Empty SdkDenom", + token: types.AllowedNativeCoinERC20Token{ + SdkDenom: "", + Name: "Example Token", + Symbol: "ETK", + Decimals: 0, + }, + expErr: "sdk denom is invalid", + }, + { + name: "invalid - Empty Name", + token: types.AllowedNativeCoinERC20Token{ + SdkDenom: "example_denom", + Name: "", + Symbol: "ETK", + Decimals: 6, + }, + expErr: "name cannot be empty", + }, + { + name: "invalid - Empty Symbol", + token: types.AllowedNativeCoinERC20Token{ + SdkDenom: "example_denom", + Name: "Example Token", + Symbol: "", + Decimals: 6, + }, + expErr: "symbol cannot be empty", + }, + { + name: "invalid - decimals higher than uint8", + token: types.NewAllowedNativeCoinERC20Token("uatom", "Kava-wrapped ATOM", "kATOM", 256), + expErr: "decimals must be less than 256", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.token.Validate() + if tc.expErr != "" { + require.ErrorContains(t, err, tc.expErr, "Expected validation error") + } else { + require.NoError(t, err, "Expected no validation error") + } + }) + } +} + +func TestAllowedNativeCoinERC20Tokens_Validate(t *testing.T) { + token1 := types.NewAllowedNativeCoinERC20Token("denom1", "Token 1", "TK1", 6) + token2 := types.NewAllowedNativeCoinERC20Token("denom2", "Token 2", "TK2", 0) + invalidToken := types.NewAllowedNativeCoinERC20Token("", "No SDK Denom Token", "TK3", 18) + + testCases := []struct { + name string + tokens types.AllowedNativeCoinERC20Tokens + expErr string + }{ + { + name: "valid - no tokens", + tokens: types.NewAllowedNativeCoinERC20Tokens(), + expErr: "", + }, + { + name: "valid - one token", + tokens: types.NewAllowedNativeCoinERC20Tokens(token1), + expErr: "", + }, + { + name: "valid - multiple tokens", + tokens: types.NewAllowedNativeCoinERC20Tokens(token1, token2), + expErr: "", + }, + { + name: "invalid - contains invalid token", + tokens: types.NewAllowedNativeCoinERC20Tokens(token1, token2, invalidToken), + expErr: "invalid token at index 2", + }, + { + name: "invalid - duplicate denoms", + tokens: types.NewAllowedNativeCoinERC20Tokens(token1, token2, token1), + expErr: "found duplicate token with sdk denom denom1", + }, + { + name: "invalid - duplicate symbol", + tokens: types.NewAllowedNativeCoinERC20Tokens( + token1, + types.NewAllowedNativeCoinERC20Token("diff", "Diff Denom, Same Symbol", "TK1", 6), + ), + expErr: "found duplicate token with symbol TK1", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.tokens.Validate() + if tc.expErr != "" { + require.ErrorContains(t, err, tc.expErr, "Expected validation error") + } else { + require.NoError(t, err, "Expected no validation error") + } + }) + } +} diff --git a/x/evmutil/types/genesis.pb.go b/x/evmutil/types/genesis.pb.go index 0ab036ab..d3d71a59 100644 --- a/x/evmutil/types/genesis.pb.go +++ b/x/evmutil/types/genesis.pb.go @@ -111,6 +111,9 @@ type Params struct { // enabled_conversion_pairs defines the list of conversion pairs allowed to be // converted between Kava ERC20 and sdk.Coin EnabledConversionPairs ConversionPairs `protobuf:"bytes,4,rep,name=enabled_conversion_pairs,json=enabledConversionPairs,proto3,castrepeated=ConversionPairs" json:"enabled_conversion_pairs"` + // allowed_native_denoms is a list of denom & erc20 token metadata pairs. + // if a denom is in the list, it is allowed to be converted to an erc20 in the evm. + AllowedNativeDenoms AllowedNativeCoinERC20Tokens `protobuf:"bytes,1,rep,name=allowed_native_denoms,json=allowedNativeDenoms,proto3,castrepeated=AllowedNativeCoinERC20Tokens" json:"allowed_native_denoms"` } func (m *Params) Reset() { *m = Params{} } @@ -153,6 +156,13 @@ func (m *Params) GetEnabledConversionPairs() ConversionPairs { return nil } +func (m *Params) GetAllowedNativeDenoms() AllowedNativeCoinERC20Tokens { + if m != nil { + return m.AllowedNativeDenoms + } + return nil +} + func init() { proto.RegisterType((*GenesisState)(nil), "kava.evmutil.v1beta1.GenesisState") proto.RegisterType((*Account)(nil), "kava.evmutil.v1beta1.Account") @@ -164,35 +174,38 @@ func init() { } var fileDescriptor_d916ab97b8e628c2 = []byte{ - // 434 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x41, 0x8b, 0xd3, 0x40, - 0x14, 0xc7, 0x33, 0x5a, 0x5a, 0x9d, 0x5d, 0x10, 0xe2, 0xa2, 0x71, 0xd1, 0xc9, 0x52, 0x44, 0xaa, - 0x90, 0x84, 0x5d, 0x6f, 0x45, 0x90, 0xc6, 0x83, 0x16, 0x2f, 0x25, 0x82, 0x07, 0x2f, 0x65, 0x92, - 0x0c, 0x31, 0x34, 0x99, 0x09, 0x99, 0x69, 0xb4, 0x47, 0x6f, 0x1e, 0xf5, 0x1b, 0x78, 0x14, 0xcf, - 0xfd, 0x10, 0x05, 0x2f, 0xa5, 0x27, 0xf1, 0x50, 0x6b, 0xfa, 0x2d, 0x3c, 0x49, 0x32, 0xd3, 0xa2, - 0x25, 0x87, 0x3d, 0x65, 0xf2, 0xe7, 0xff, 0x7f, 0xef, 0xf7, 0xde, 0x0c, 0xec, 0x4e, 0x70, 0x81, - 0x1d, 0x52, 0xa4, 0x53, 0x11, 0x27, 0x4e, 0x71, 0xee, 0x13, 0x81, 0xcf, 0x9d, 0x88, 0x50, 0xc2, - 0x63, 0x6e, 0x67, 0x39, 0x13, 0x4c, 0x3f, 0xa9, 0x3c, 0xb6, 0xf2, 0xd8, 0xca, 0x73, 0x7a, 0x27, - 0x60, 0x3c, 0x65, 0x7c, 0x5c, 0x7b, 0x1c, 0xf9, 0x23, 0x03, 0xa7, 0x27, 0x11, 0x8b, 0x98, 0xd4, - 0xab, 0x93, 0x52, 0x1f, 0x35, 0xb6, 0x0a, 0x18, 0x2d, 0x48, 0xce, 0x63, 0x46, 0xc7, 0x19, 0x8e, - 0x73, 0xe9, 0xed, 0x7e, 0x06, 0xf0, 0xf8, 0xb9, 0x84, 0x78, 0x25, 0xb0, 0x20, 0xfa, 0x53, 0x78, - 0x0d, 0x07, 0x01, 0x9b, 0x52, 0xc1, 0x0d, 0x70, 0x76, 0xb5, 0x77, 0x74, 0x71, 0xcf, 0x6e, 0xc2, - 0xb2, 0x07, 0xd2, 0xe5, 0xb6, 0x16, 0x6b, 0x53, 0xf3, 0xf6, 0x21, 0xbd, 0x0f, 0xdb, 0x19, 0xce, - 0x71, 0xca, 0x8d, 0x2b, 0x67, 0xa0, 0x77, 0x74, 0x71, 0xb7, 0x39, 0x3e, 0xaa, 0x3d, 0x2a, 0xad, - 0x12, 0xfd, 0xd6, 0xc7, 0x2f, 0xa6, 0xd6, 0xfd, 0x0e, 0x60, 0x47, 0x55, 0xd7, 0x7d, 0xd8, 0xc1, - 0x61, 0x98, 0x13, 0x5e, 0xd1, 0x80, 0xde, 0xb1, 0xfb, 0xe2, 0xcf, 0xda, 0xb4, 0xa2, 0x58, 0xbc, - 0x9d, 0xfa, 0x76, 0xc0, 0x52, 0xb5, 0x0f, 0xf5, 0xb1, 0x78, 0x38, 0x71, 0xc4, 0x2c, 0x23, 0xbc, - 0xc2, 0x1b, 0xc8, 0xe0, 0x6a, 0x6e, 0xdd, 0x54, 0x5b, 0x53, 0x8a, 0x3b, 0x13, 0x84, 0x7b, 0xbb, - 0xc2, 0xfa, 0x6b, 0xd8, 0xf1, 0x71, 0x82, 0x69, 0x40, 0x6a, 0xe4, 0xeb, 0xee, 0x93, 0x0a, 0xea, - 0xe7, 0xda, 0x7c, 0x70, 0x89, 0x3e, 0x43, 0x2a, 0x56, 0x73, 0x0b, 0xaa, 0x06, 0x43, 0x2a, 0xbc, - 0x5d, 0x31, 0x35, 0xcd, 0x07, 0x00, 0xdb, 0x72, 0x58, 0xfd, 0x1d, 0x34, 0x08, 0xc5, 0x7e, 0x42, - 0xc2, 0xf1, 0xc1, 0x6d, 0x70, 0xa3, 0x55, 0xef, 0xfa, 0x7e, 0xf3, 0xb2, 0x9e, 0xed, 0xdd, 0x23, - 0x1c, 0xe7, 0xee, 0xed, 0x8a, 0xef, 0xdb, 0x2f, 0xf3, 0xc6, 0xff, 0x3a, 0xf7, 0x6e, 0xa9, 0xf2, - 0x07, 0xba, 0xfb, 0x72, 0xf3, 0x1b, 0x81, 0xaf, 0x25, 0x02, 0x8b, 0x12, 0x81, 0x65, 0x89, 0xc0, - 0xa6, 0x44, 0xe0, 0xd3, 0x16, 0x69, 0xcb, 0x2d, 0xd2, 0x7e, 0x6c, 0x91, 0xf6, 0xe6, 0xe1, 0x3f, - 0xa3, 0x56, 0x08, 0x56, 0x82, 0x7d, 0x5e, 0x9f, 0x9c, 0xf7, 0xfb, 0xa7, 0x54, 0x4f, 0xec, 0xb7, - 0xeb, 0x97, 0xf3, 0xf8, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x32, 0x69, 0x99, 0x1c, 0xd2, 0x02, - 0x00, 0x00, + // 492 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0x41, 0x6b, 0xd4, 0x40, + 0x14, 0xce, 0xd4, 0x65, 0x57, 0xa7, 0x05, 0x21, 0xad, 0xba, 0x96, 0x9a, 0x94, 0xa5, 0xc8, 0x2a, + 0x24, 0x71, 0xd7, 0x5b, 0x11, 0xa4, 0x59, 0x45, 0x8b, 0x20, 0x25, 0x8a, 0x07, 0x2f, 0xe1, 0x25, + 0x19, 0xd6, 0xb0, 0xc9, 0xcc, 0x92, 0x99, 0x4d, 0xed, 0x3f, 0x10, 0x3c, 0xa8, 0xff, 0xc0, 0xa3, + 0x78, 0xee, 0x8f, 0x28, 0x78, 0x29, 0x3d, 0x89, 0x87, 0xb5, 0xee, 0xfe, 0x0b, 0x4f, 0x92, 0x99, + 0xd9, 0xa5, 0x96, 0x55, 0x7a, 0xca, 0xe4, 0x9b, 0xef, 0x7b, 0xef, 0x9b, 0xef, 0x3d, 0xdc, 0x1a, + 0x40, 0x09, 0x1e, 0x29, 0xf3, 0x91, 0x48, 0x33, 0xaf, 0xec, 0x44, 0x44, 0x40, 0xc7, 0xeb, 0x13, + 0x4a, 0x78, 0xca, 0xdd, 0x61, 0xc1, 0x04, 0x33, 0xd7, 0x2a, 0x8e, 0xab, 0x39, 0xae, 0xe6, 0xac, + 0xdf, 0x8c, 0x19, 0xcf, 0x19, 0x0f, 0x25, 0xc7, 0x53, 0x3f, 0x4a, 0xb0, 0xbe, 0xd6, 0x67, 0x7d, + 0xa6, 0xf0, 0xea, 0xa4, 0xd1, 0xbb, 0x0b, 0x5b, 0xc5, 0x8c, 0x96, 0xa4, 0xe0, 0x29, 0xa3, 0xe1, + 0x10, 0xd2, 0x42, 0x71, 0x5b, 0x9f, 0x10, 0x5e, 0x79, 0xa2, 0x4c, 0xbc, 0x10, 0x20, 0x88, 0xf9, + 0x10, 0x5f, 0x86, 0x38, 0x66, 0x23, 0x2a, 0x78, 0x13, 0x6d, 0x5e, 0x6a, 0x2f, 0x77, 0x6f, 0xb9, + 0x8b, 0x6c, 0xb9, 0x3b, 0x8a, 0xe5, 0xd7, 0x8e, 0xc6, 0xb6, 0x11, 0xcc, 0x45, 0xe6, 0x36, 0xae, + 0x0f, 0xa1, 0x80, 0x9c, 0x37, 0x97, 0x36, 0x51, 0x7b, 0xb9, 0xbb, 0xb1, 0x58, 0xbe, 0x27, 0x39, + 0x5a, 0xad, 0x15, 0xdb, 0xb5, 0x77, 0x9f, 0x6d, 0xa3, 0xf5, 0x0d, 0xe1, 0x86, 0xae, 0x6e, 0x46, + 0xb8, 0x01, 0x49, 0x52, 0x10, 0x5e, 0xb9, 0x41, 0xed, 0x15, 0xff, 0xe9, 0xef, 0xb1, 0xed, 0xf4, + 0x53, 0xf1, 0x66, 0x14, 0xb9, 0x31, 0xcb, 0x75, 0x1e, 0xfa, 0xe3, 0xf0, 0x64, 0xe0, 0x89, 0x83, + 0x21, 0xe1, 0x95, 0xbd, 0x1d, 0x25, 0x3c, 0x39, 0x74, 0x56, 0x75, 0x6a, 0x1a, 0xf1, 0x0f, 0x04, + 0xe1, 0xc1, 0xac, 0xb0, 0xf9, 0x0a, 0x37, 0x22, 0xc8, 0x80, 0xc6, 0x44, 0x5a, 0xbe, 0xe2, 0x3f, + 0xa8, 0x4c, 0xfd, 0x18, 0xdb, 0xb7, 0x2f, 0xd0, 0x67, 0x97, 0x8a, 0x93, 0x43, 0x07, 0xeb, 0x06, + 0xbb, 0x54, 0x04, 0xb3, 0x62, 0xfa, 0x35, 0x1f, 0x96, 0x70, 0x5d, 0x3d, 0xd6, 0xdc, 0xc7, 0x4d, + 0x42, 0x21, 0xca, 0x48, 0x12, 0x9e, 0x9b, 0x06, 0x6f, 0xd6, 0x64, 0xd6, 0x5b, 0x8b, 0xc3, 0xea, + 0xcd, 0xd9, 0x7b, 0x90, 0x16, 0xfe, 0x8d, 0xca, 0xdf, 0xd7, 0x9f, 0xf6, 0xd5, 0xbf, 0x71, 0x1e, + 0x5c, 0xd7, 0xe5, 0xcf, 0xe1, 0xe6, 0x7b, 0x84, 0xaf, 0x41, 0x96, 0xb1, 0x7d, 0x92, 0x84, 0x14, + 0x44, 0x5a, 0x92, 0x30, 0x21, 0x94, 0xe5, 0xb3, 0x11, 0x77, 0xfe, 0x31, 0x62, 0x25, 0x79, 0x2e, + 0x15, 0x3d, 0x96, 0xd2, 0xc7, 0x41, 0xaf, 0x7b, 0xef, 0x25, 0x1b, 0x10, 0xea, 0x6f, 0x69, 0x0f, + 0x1b, 0xff, 0x21, 0xf1, 0x60, 0x15, 0xce, 0xde, 0x3e, 0x92, 0x3d, 0xfd, 0x67, 0xa7, 0xbf, 0x2c, + 0xf4, 0x65, 0x62, 0xa1, 0xa3, 0x89, 0x85, 0x8e, 0x27, 0x16, 0x3a, 0x9d, 0x58, 0xe8, 0xe3, 0xd4, + 0x32, 0x8e, 0xa7, 0x96, 0xf1, 0x7d, 0x6a, 0x19, 0xaf, 0xef, 0x9c, 0x09, 0xbe, 0x72, 0xe6, 0x64, + 0x10, 0x71, 0x79, 0xf2, 0xde, 0xce, 0x17, 0x5b, 0xe6, 0x1f, 0xd5, 0xe5, 0x1e, 0xdf, 0xff, 0x13, + 0x00, 0x00, 0xff, 0xff, 0xa3, 0x52, 0xb0, 0x81, 0x60, 0x03, 0x00, 0x00, } func (this *GenesisState) VerboseEqual(that interface{}) error { @@ -358,6 +371,14 @@ func (this *Params) VerboseEqual(that interface{}) error { return fmt.Errorf("EnabledConversionPairs this[%v](%v) Not Equal that[%v](%v)", i, this.EnabledConversionPairs[i], i, that1.EnabledConversionPairs[i]) } } + if len(this.AllowedNativeDenoms) != len(that1.AllowedNativeDenoms) { + return fmt.Errorf("AllowedNativeDenoms this(%v) Not Equal that(%v)", len(this.AllowedNativeDenoms), len(that1.AllowedNativeDenoms)) + } + for i := range this.AllowedNativeDenoms { + if !this.AllowedNativeDenoms[i].Equal(&that1.AllowedNativeDenoms[i]) { + return fmt.Errorf("AllowedNativeDenoms this[%v](%v) Not Equal that[%v](%v)", i, this.AllowedNativeDenoms[i], i, that1.AllowedNativeDenoms[i]) + } + } return nil } func (this *Params) Equal(that interface{}) bool { @@ -387,6 +408,14 @@ func (this *Params) Equal(that interface{}) bool { return false } } + if len(this.AllowedNativeDenoms) != len(that1.AllowedNativeDenoms) { + return false + } + for i := range this.AllowedNativeDenoms { + if !this.AllowedNativeDenoms[i].Equal(&that1.AllowedNativeDenoms[i]) { + return false + } + } return true } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -510,6 +539,20 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x22 } } + if len(m.AllowedNativeDenoms) > 0 { + for iNdEx := len(m.AllowedNativeDenoms) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.AllowedNativeDenoms[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } return len(dAtA) - i, nil } @@ -562,6 +605,12 @@ func (m *Params) Size() (n int) { } var l int _ = l + if len(m.AllowedNativeDenoms) > 0 { + for _, e := range m.AllowedNativeDenoms { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } if len(m.EnabledConversionPairs) > 0 { for _, e := range m.EnabledConversionPairs { l = e.Size() @@ -841,6 +890,40 @@ func (m *Params) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowedNativeDenoms", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllowedNativeDenoms = append(m.AllowedNativeDenoms, AllowedNativeCoinERC20Token{}) + if err := m.AllowedNativeDenoms[len(m.AllowedNativeDenoms)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field EnabledConversionPairs", wireType) diff --git a/x/evmutil/types/genesis_test.go b/x/evmutil/types/genesis_test.go index d61d8135..a36fcff3 100644 --- a/x/evmutil/types/genesis_test.go +++ b/x/evmutil/types/genesis_test.go @@ -47,9 +47,12 @@ func TestGenesisState_Validate(t *testing.T) { {Address: addrs[0], Balance: sdkmath.NewInt(100)}, {Address: addrs[1], Balance: sdkmath.NewInt(150)}, }, - params: types.NewParams(types.NewConversionPairs( - types.NewConversionPair(types.NewInternalEVMAddress(common.HexToAddress("0xinvalidaddress")), "weth"), - )), + params: types.NewParams( + types.NewConversionPairs( + types.NewConversionPair(types.NewInternalEVMAddress(common.HexToAddress("0xinvalidaddress")), "weth"), + ), + types.NewAllowedNativeCoinERC20Tokens(), + ), success: false, }, { diff --git a/x/evmutil/types/params.go b/x/evmutil/types/params.go index 6b229ae0..7bb5f47b 100644 --- a/x/evmutil/types/params.go +++ b/x/evmutil/types/params.go @@ -6,8 +6,10 @@ import ( // Parameter keys and default values var ( - KeyEnabledConversionPairs = []byte("EnabledConversionPairs") - DefaultConversionPairs = ConversionPairs{} + KeyEnabledConversionPairs = []byte("EnabledConversionPairs") + DefaultConversionPairs = ConversionPairs{} + KeyAllowedNativeDenoms = []byte("AllowedNativeDenoms") + DefaultAllowedNativeDenoms = AllowedNativeCoinERC20Tokens{} ) // ParamKeyTable for evmutil module. @@ -20,15 +22,18 @@ func ParamKeyTable() paramtypes.KeyTable { func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { return paramtypes.ParamSetPairs{ paramtypes.NewParamSetPair(KeyEnabledConversionPairs, &p.EnabledConversionPairs, validateConversionPairs), + paramtypes.NewParamSetPair(KeyAllowedNativeDenoms, &p.AllowedNativeDenoms, validateAllowedNativeCoinERC20Tokens), } } // NewParams returns new evmutil module Params. func NewParams( conversionPairs ConversionPairs, + allowedNativeDenoms AllowedNativeCoinERC20Tokens, ) Params { return Params{ EnabledConversionPairs: conversionPairs, + AllowedNativeDenoms: allowedNativeDenoms, } } @@ -36,13 +41,17 @@ func NewParams( func DefaultParams() Params { return NewParams( DefaultConversionPairs, + DefaultAllowedNativeDenoms, ) } -// Validate returns an error if the Parmas is invalid. +// Validate returns an error if the Params is invalid. func (p *Params) Validate() error { if err := p.EnabledConversionPairs.Validate(); err != nil { return err } + if err := p.AllowedNativeDenoms.Validate(); err != nil { + return err + } return nil } diff --git a/x/evmutil/types/params_test.go b/x/evmutil/types/params_test.go index 9a417fe5..3b5f48f4 100644 --- a/x/evmutil/types/params_test.go +++ b/x/evmutil/types/params_test.go @@ -34,9 +34,13 @@ func (suite *ParamsTestSuite) TestMarshalYAML() { "usdc", ), ) + allowedNativeDenoms := types.NewAllowedNativeCoinERC20Tokens( + types.NewAllowedNativeCoinERC20Token("denom", "Sdk Denom!", "DENOM", 6), + ) p := types.NewParams( conversionPairs, + allowedNativeDenoms, ) data, err := yaml.Marshal(p) @@ -47,6 +51,8 @@ func (suite *ParamsTestSuite) TestMarshalYAML() { suite.Require().NoError(err) _, ok := params["enabled_conversion_pairs"] suite.Require().True(ok, "enabled_conversion_pairs should exist in yaml") + _, ok = params["allowed_native_denoms"] + suite.Require().True(ok, "allowed_native_denoms should exist in yaml") } func (suite *ParamsTestSuite) TestParamSetPairs_EnabledConversionPairs() { @@ -70,6 +76,90 @@ func (suite *ParamsTestSuite) TestParamSetPairs_EnabledConversionPairs() { suite.Require().EqualError(paramSetPair.ValidatorFn(struct{}{}), "invalid parameter type: struct {}") } +func (suite *ParamsTestSuite) TestParamSetPairs_AllowedNativeDenoms() { + suite.Require().Equal([]byte("AllowedNativeDenoms"), types.KeyAllowedNativeDenoms) + defaultParams := types.DefaultParams() + + var paramSetPair *paramstypes.ParamSetPair + for _, pair := range defaultParams.ParamSetPairs() { + if bytes.Equal(pair.Key, types.KeyAllowedNativeDenoms) { + paramSetPair = &pair + break + } + } + suite.Require().NotNil(paramSetPair) + + pairs, ok := paramSetPair.Value.(*types.AllowedNativeCoinERC20Tokens) + suite.Require().True(ok) + suite.Require().Equal(pairs, &defaultParams.AllowedNativeDenoms) + + suite.Require().Nil(paramSetPair.ValidatorFn(*pairs)) + suite.Require().EqualError(paramSetPair.ValidatorFn(struct{}{}), "invalid parameter type: struct {}") +} + +func (suite *ParamsTestSuite) TestParams_Validate() { + validConversionPairs := types.NewConversionPairs( + types.NewConversionPair( + testutil.MustNewInternalEVMAddressFromString("0x0000000000000000000000000000000000000001"), + "usdc", + ), + ) + invalidConversionPairs := types.NewConversionPairs( + types.NewConversionPair( + testutil.MustNewInternalEVMAddressFromString("0x000000000000000000000000000000000000000A"), + "kava", + ), + types.NewConversionPair( + testutil.MustNewInternalEVMAddressFromString("0x000000000000000000000000000000000000000B"), + "kava", // duplicate denom! + ), + ) + validAllowedNativeDenoms := types.NewAllowedNativeCoinERC20Tokens( + types.NewAllowedNativeCoinERC20Token("hard", "EVM Hard", "HARD", 6), + ) + invalidAllowedNativeDenoms := types.NewAllowedNativeCoinERC20Tokens( + types.NewAllowedNativeCoinERC20Token("", "Invalid Token", "NOPE", 0), // empty sdk denom + ) + + testCases := []struct { + name string + params types.Params + expErr string + }{ + { + name: "valid - empty", + params: types.NewParams(types.NewConversionPairs(), types.NewAllowedNativeCoinERC20Tokens()), + expErr: "", + }, + { + name: "valid - with data", + params: types.NewParams(validConversionPairs, validAllowedNativeDenoms), + expErr: "", + }, + { + name: "invalid - invalid conversion pair", + params: types.NewParams(invalidConversionPairs, validAllowedNativeDenoms), + expErr: "found duplicate", + }, + { + name: "invalid - invalid allowed native denoms", + params: types.NewParams(validConversionPairs, invalidAllowedNativeDenoms), + expErr: "invalid token", + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.params.Validate() + if tc.expErr != "" { + suite.ErrorContains(err, tc.expErr, "Expected validation error") + } else { + suite.NoError(err, "Expected no validation error") + } + }) + } +} + func TestParamsTestSuite(t *testing.T) { suite.Run(t, new(ParamsTestSuite)) }