From 5775d349e65e9d08368984c9b7cd4f2d6fb0f9e3 Mon Sep 17 00:00:00 2001 From: Kevin Pita Date: Tue, 28 Apr 2026 17:45:30 +0200 Subject: [PATCH 1/3] test: add ante routing tests and inject handler factories --- .testcoverage.yml | 3 +- Makefile | 2 +- app/ante/ante.go | 16 ++++-- app/ante/ante_test.go | 121 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 app/ante/ante_test.go diff --git a/.testcoverage.yml b/.testcoverage.yml index 5434e75a..f0c7d229 100644 --- a/.testcoverage.yml +++ b/.testcoverage.yml @@ -36,7 +36,8 @@ exclude: paths: - cmd - docs - - app + - ^app$ + - ^app/upgrades/ - tools - tests - \.pb\.go$ diff --git a/Makefile b/Makefile index 89536925..927e6239 100644 --- a/Makefile +++ b/Makefile @@ -132,7 +132,7 @@ lint-fix: ### Testing ### ############################################################################### EXCLUDED_POA_PACKAGES=$(shell go list ./x/poa/... | grep -v /x/poa/testutil | grep -v /x/poa/client | grep -v /x/poa/simulation | grep -v /x/poa/types) -EXCLUDED_UNIT_PACKAGES=$(shell go list ./... | grep -v tests | grep -v testutil | grep -v tools | grep -v app | grep -v docs | grep -v cmd | grep -v /x/poa/testutil | grep -v /x/poa/client | grep -v /x/poa/simulation | grep -v /x/poa/types) +EXCLUDED_UNIT_PACKAGES=$(shell go list ./... | grep -v tests | grep -v testutil | grep -v tools | grep -v '/app$$' | grep -v /app/upgrades | grep -v docs | grep -v cmd | grep -v /x/poa/testutil | grep -v /x/poa/client | grep -v /x/poa/simulation | grep -v /x/poa/types) mocks: @echo "--> Installing mockgen" diff --git a/app/ante/ante.go b/app/ante/ante.go index 59f5c6d7..834731a5 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -10,11 +10,21 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/ante" ) +type anteHandlerFactory func(sdk.Context, baseevmante.HandlerOptions) sdk.AnteHandler + // NewAnteHandler returns an ante handler responsible for attempting to route an // Ethereum or SDK transaction to an internal ante handler for performing // transaction-level processing (e.g. fee payment, signature verification) before // being passed onto it's respective handler. func NewAnteHandler(options baseevmante.HandlerOptions) sdk.AnteHandler { + return newAnteHandler(options, newCosmosAnteHandler, newMonoEVMAnteHandler) +} + +func newAnteHandler( + options baseevmante.HandlerOptions, + cosmosHandler anteHandlerFactory, + evmHandler anteHandlerFactory, +) sdk.AnteHandler { return func( ctx sdk.Context, tx sdk.Tx, sim bool, ) (newCtx sdk.Context, err error) { @@ -27,10 +37,10 @@ func NewAnteHandler(options baseevmante.HandlerOptions) sdk.AnteHandler { switch typeURL := opts[0].GetTypeUrl(); typeURL { case "/cosmos.evm.vm.v1.ExtensionOptionsEthereumTx": // handle as *evmtypes.MsgEthereumTx - anteHandler = newMonoEVMAnteHandler(ctx, options) + anteHandler = evmHandler(ctx, options) case "/cosmos.evm.ante.v1.ExtensionOptionDynamicFeeTx": // cosmos-sdk tx with dynamic fee extension - anteHandler = newCosmosAnteHandler(ctx, options) + anteHandler = cosmosHandler(ctx, options) default: return ctx, errorsmod.Wrapf( errortypes.ErrUnknownExtensionOptions, @@ -45,7 +55,7 @@ func NewAnteHandler(options baseevmante.HandlerOptions) sdk.AnteHandler { // handle as totally normal Cosmos SDK tx switch tx.(type) { case sdk.Tx: - anteHandler = newCosmosAnteHandler(ctx, options) + anteHandler = cosmosHandler(ctx, options) default: return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid transaction type: %T", tx) } diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go new file mode 100644 index 00000000..b2b325b9 --- /dev/null +++ b/app/ante/ante_test.go @@ -0,0 +1,121 @@ +package ante + +import ( + "errors" + "testing" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + authante "github.com/cosmos/cosmos-sdk/x/auth/ante" + baseevmante "github.com/cosmos/evm/ante" + antetypes "github.com/cosmos/evm/ante/types" + evmtypes "github.com/cosmos/evm/x/vm/types" + "github.com/stretchr/testify/require" + protov2 "google.golang.org/protobuf/proto" +) + +// Sentinel errors returned by the stub ante handlers to signal which branch +// the router selected, so tests can assert routing without a real ante chain. +var ( + errRoutedToCosmosAnte = errors.New("routed to cosmos ante") + errRoutedToEVMAnte = errors.New("routed to evm ante") +) + +type routingTx struct { + msgs []sdk.Msg + extensionOptions []*codectypes.Any +} + +var _ sdk.Tx = routingTx{} +var _ authante.HasExtensionOptionsTx = routingTx{} + +func (tx routingTx) GetMsgs() []sdk.Msg { + return tx.msgs +} + +func (tx routingTx) GetMsgsV2() ([]protov2.Message, error) { + return nil, nil +} + +func (tx routingTx) GetExtensionOptions() []*codectypes.Any { + return tx.extensionOptions +} + +func (tx routingTx) GetNonCriticalExtensionOptions() []*codectypes.Any { + return nil +} + +func stubAnteHandlerFactory(routeErr error) anteHandlerFactory { + return func(sdk.Context, baseevmante.HandlerOptions) sdk.AnteHandler { + return func(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { + return ctx, routeErr + } + } +} + +func TestNewAnteHandlerRouting(t *testing.T) { + ethExtension, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{}) + require.NoError(t, err) + dynamicFeeExtension, err := codectypes.NewAnyWithValue(&antetypes.ExtensionOptionDynamicFeeTx{}) + require.NoError(t, err) + + cases := []struct { + name string + tx sdk.Tx + wantErr error + wantErrContains string + }{ + { + name: "ethereum extension routes to evm ante", + tx: routingTx{ + msgs: []sdk.Msg{&evmtypes.MsgEthereumTx{}}, + extensionOptions: []*codectypes.Any{ethExtension}, + }, + wantErr: errRoutedToEVMAnte, + }, + { + name: "dynamic fee extension routes to cosmos ante", + tx: routingTx{extensionOptions: []*codectypes.Any{dynamicFeeExtension}}, + wantErr: errRoutedToCosmosAnte, + }, + { + name: "no extension routes to cosmos ante", + tx: routingTx{}, + wantErr: errRoutedToCosmosAnte, + }, + { + name: "unknown extension is rejected", + tx: routingTx{ + extensionOptions: []*codectypes.Any{{ + TypeUrl: "/cosmos.evm.test.v1.UnknownExtensionOption", + }}, + }, + wantErr: errortypes.ErrUnknownExtensionOptions, + wantErrContains: "rejecting tx with unsupported extension option: /cosmos.evm.test.v1.UnknownExtensionOption", + }, + { + name: "nil tx is rejected", + tx: nil, + wantErr: errortypes.ErrUnknownRequest, + wantErrContains: "invalid transaction type: ", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + handler := newAnteHandler( + baseevmante.HandlerOptions{}, + stubAnteHandlerFactory(errRoutedToCosmosAnte), + stubAnteHandlerFactory(errRoutedToEVMAnte), + ) + + _, err := handler(sdk.Context{}, tc.tx, false) + + require.ErrorIs(t, err, tc.wantErr) + if tc.wantErrContains != "" { + require.ErrorContains(t, err, tc.wantErrContains) + } + }) + } +} From 394e196b5460bd0cbb8fa59061c3e7c42ea32c46 Mon Sep 17 00:00:00 2001 From: Kevin Pita Date: Tue, 28 Apr 2026 18:06:42 +0200 Subject: [PATCH 2/3] refactor: simplify ante handler routing --- app/ante/ante.go | 47 +++++++++++++++++++------------------------ app/ante/ante_test.go | 8 +++++--- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/app/ante/ante.go b/app/ante/ante.go index 834731a5..8ccfd3d5 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -5,6 +5,7 @@ import ( errorsmod "cosmossdk.io/errors" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth/ante" @@ -28,38 +29,32 @@ func newAnteHandler( return func( ctx sdk.Context, tx sdk.Tx, sim bool, ) (newCtx sdk.Context, err error) { - var anteHandler sdk.AnteHandler - - txWithExtensions, ok := tx.(ante.HasExtensionOptionsTx) - if ok { - opts := txWithExtensions.GetExtensionOptions() - if len(opts) > 0 { - switch typeURL := opts[0].GetTypeUrl(); typeURL { - case "/cosmos.evm.vm.v1.ExtensionOptionsEthereumTx": - // handle as *evmtypes.MsgEthereumTx - anteHandler = evmHandler(ctx, options) - case "/cosmos.evm.ante.v1.ExtensionOptionDynamicFeeTx": - // cosmos-sdk tx with dynamic fee extension - anteHandler = cosmosHandler(ctx, options) - default: - return ctx, errorsmod.Wrapf( - errortypes.ErrUnknownExtensionOptions, - "rejecting tx with unsupported extension option: %s", typeURL, - ) - } + if tx == nil { + return ctx, errorsmod.Wrap(errortypes.ErrUnknownRequest, "tx is nil") + } - return anteHandler(ctx, tx, sim) - } + var opts []*codectypes.Any + if txExt, ok := tx.(ante.HasExtensionOptionsTx); ok { + opts = txExt.GetExtensionOptions() + } + if len(opts) == 0 { + return cosmosHandler(ctx, options)(ctx, tx, sim) } - // handle as totally normal Cosmos SDK tx - switch tx.(type) { - case sdk.Tx: + var anteHandler sdk.AnteHandler + switch typeURL := opts[0].GetTypeUrl(); typeURL { + case "/cosmos.evm.vm.v1.ExtensionOptionsEthereumTx": + // handle as *evmtypes.MsgEthereumTx + anteHandler = evmHandler(ctx, options) + case "/cosmos.evm.ante.v1.ExtensionOptionDynamicFeeTx": + // cosmos-sdk tx with dynamic fee extension anteHandler = cosmosHandler(ctx, options) default: - return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid transaction type: %T", tx) + return ctx, errorsmod.Wrapf( + errortypes.ErrUnknownExtensionOptions, + "rejecting tx with unsupported extension option: %s", typeURL, + ) } - return anteHandler(ctx, tx, sim) } } diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go index b2b325b9..66a8bb09 100644 --- a/app/ante/ante_test.go +++ b/app/ante/ante_test.go @@ -27,8 +27,10 @@ type routingTx struct { extensionOptions []*codectypes.Any } -var _ sdk.Tx = routingTx{} -var _ authante.HasExtensionOptionsTx = routingTx{} +var ( + _ sdk.Tx = routingTx{} + _ authante.HasExtensionOptionsTx = routingTx{} +) func (tx routingTx) GetMsgs() []sdk.Msg { return tx.msgs @@ -98,7 +100,7 @@ func TestNewAnteHandlerRouting(t *testing.T) { name: "nil tx is rejected", tx: nil, wantErr: errortypes.ErrUnknownRequest, - wantErrContains: "invalid transaction type: ", + wantErrContains: "tx is nil", }, } From 9dc2f5abffe0181fe3d29cd54966f2f0c18c1880 Mon Sep 17 00:00:00 2001 From: Kevin Pita Date: Tue, 5 May 2026 11:48:10 +0200 Subject: [PATCH 3/3] test: add upgrade handler tests for v5-v10 --- .testcoverage.yml | 5 +- Makefile | 2 +- app/upgrades/v10/upgrades_test.go | 53 +++++++ app/upgrades/v5/upgrades_test.go | 35 +++++ app/upgrades/v6/upgrades_test.go | 35 +++++ app/upgrades/v7/upgrades_test.go | 35 +++++ app/upgrades/v8/upgrades_test.go | 35 +++++ app/upgrades/v9/upgrades_test.go | 244 ++++++++++++++++++++++++++++++ 8 files changed, 440 insertions(+), 4 deletions(-) create mode 100644 app/upgrades/v10/upgrades_test.go create mode 100644 app/upgrades/v5/upgrades_test.go create mode 100644 app/upgrades/v6/upgrades_test.go create mode 100644 app/upgrades/v7/upgrades_test.go create mode 100644 app/upgrades/v8/upgrades_test.go create mode 100644 app/upgrades/v9/upgrades_test.go diff --git a/.testcoverage.yml b/.testcoverage.yml index f0c7d229..6bf10b2c 100644 --- a/.testcoverage.yml +++ b/.testcoverage.yml @@ -36,8 +36,7 @@ exclude: paths: - cmd - docs - - ^app$ - - ^app/upgrades/ + - ^app/app\.go$ - tools - tests - \.pb\.go$ @@ -53,4 +52,4 @@ breakdown-file-name: '' diff: # File name of go-test-coverage breakdown file which will be used to # report coverage difference. - base-breakdown-file-name: '' \ No newline at end of file + base-breakdown-file-name: '' diff --git a/Makefile b/Makefile index 927e6239..5cbc7070 100644 --- a/Makefile +++ b/Makefile @@ -132,7 +132,7 @@ lint-fix: ### Testing ### ############################################################################### EXCLUDED_POA_PACKAGES=$(shell go list ./x/poa/... | grep -v /x/poa/testutil | grep -v /x/poa/client | grep -v /x/poa/simulation | grep -v /x/poa/types) -EXCLUDED_UNIT_PACKAGES=$(shell go list ./... | grep -v tests | grep -v testutil | grep -v tools | grep -v '/app$$' | grep -v /app/upgrades | grep -v docs | grep -v cmd | grep -v /x/poa/testutil | grep -v /x/poa/client | grep -v /x/poa/simulation | grep -v /x/poa/types) +EXCLUDED_UNIT_PACKAGES=$(shell go list ./... | grep -v tests | grep -v testutil | grep -v tools | grep -v '/app$$' | grep -v docs | grep -v cmd | grep -v /x/poa/testutil | grep -v /x/poa/client | grep -v /x/poa/simulation | grep -v /x/poa/types) mocks: @echo "--> Installing mockgen" diff --git a/app/upgrades/v10/upgrades_test.go b/app/upgrades/v10/upgrades_test.go new file mode 100644 index 00000000..e10b5484 --- /dev/null +++ b/app/upgrades/v10/upgrades_test.go @@ -0,0 +1,53 @@ +package v10 + +import ( + "testing" + + "cosmossdk.io/log" + upgradetypes "cosmossdk.io/x/upgrade/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + evmtypes "github.com/cosmos/evm/x/vm/types" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" +) + +var _ EvmKeeper = (*mockEvmKeeper)(nil) + +type mockEvmKeeper struct { + initErr error + initCalled bool +} + +func (m *mockEvmKeeper) GetParams(sdk.Context) evmtypes.Params { return evmtypes.Params{} } +func (m *mockEvmKeeper) SetParams(sdk.Context, evmtypes.Params) error { return nil } +func (m *mockEvmKeeper) SetCodeHash(sdk.Context, []byte, []byte) {} +func (m *mockEvmKeeper) InitEvmCoinInfo(sdk.Context) error { + m.initCalled = true + return m.initErr +} + +func TestCreateUpgradeHandlerInitializesEvmCoinInfo(t *testing.T) { + ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()) + moduleManager := module.NewManager() + configurator := module.NewConfigurator( + codec.NewProtoCodec(codectypes.NewInterfaceRegistry()), + grpc.NewServer(), + grpc.NewServer(), + ) + keeper := &mockEvmKeeper{} + versions := module.VersionMap{"bank": 1} + + updatedVersions, err := CreateUpgradeHandler(moduleManager, configurator, keeper)( + ctx, + upgradetypes.Plan{Name: UpgradeName}, + versions, + ) + + require.NoError(t, err) + require.True(t, keeper.initCalled) + require.Empty(t, updatedVersions) +} diff --git a/app/upgrades/v5/upgrades_test.go b/app/upgrades/v5/upgrades_test.go new file mode 100644 index 00000000..385d4165 --- /dev/null +++ b/app/upgrades/v5/upgrades_test.go @@ -0,0 +1,35 @@ +package v5 + +import ( + "testing" + + "cosmossdk.io/log" + upgradetypes "cosmossdk.io/x/upgrade/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" +) + +func TestCreateUpgradeHandlerCompletes(t *testing.T) { + ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()) + moduleManager := module.NewManager() + configurator := module.NewConfigurator( + codec.NewProtoCodec(codectypes.NewInterfaceRegistry()), + grpc.NewServer(), + grpc.NewServer(), + ) + versions := module.VersionMap{"bank": 1} + + updatedVersions, err := CreateUpgradeHandler(moduleManager, configurator)( + ctx, + upgradetypes.Plan{Name: UpgradeName}, + versions, + ) + + require.NoError(t, err) + require.Empty(t, updatedVersions) +} diff --git a/app/upgrades/v6/upgrades_test.go b/app/upgrades/v6/upgrades_test.go new file mode 100644 index 00000000..1a204b4e --- /dev/null +++ b/app/upgrades/v6/upgrades_test.go @@ -0,0 +1,35 @@ +package v6 + +import ( + "testing" + + "cosmossdk.io/log" + upgradetypes "cosmossdk.io/x/upgrade/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" +) + +func TestCreateUpgradeHandlerCompletes(t *testing.T) { + ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()) + moduleManager := module.NewManager() + configurator := module.NewConfigurator( + codec.NewProtoCodec(codectypes.NewInterfaceRegistry()), + grpc.NewServer(), + grpc.NewServer(), + ) + versions := module.VersionMap{"bank": 1} + + updatedVersions, err := CreateUpgradeHandler(moduleManager, configurator)( + ctx, + upgradetypes.Plan{Name: UpgradeName}, + versions, + ) + + require.NoError(t, err) + require.Empty(t, updatedVersions) +} diff --git a/app/upgrades/v7/upgrades_test.go b/app/upgrades/v7/upgrades_test.go new file mode 100644 index 00000000..1bd7f3d8 --- /dev/null +++ b/app/upgrades/v7/upgrades_test.go @@ -0,0 +1,35 @@ +package v7 + +import ( + "testing" + + "cosmossdk.io/log" + upgradetypes "cosmossdk.io/x/upgrade/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" +) + +func TestCreateUpgradeHandlerCompletes(t *testing.T) { + ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()) + moduleManager := module.NewManager() + configurator := module.NewConfigurator( + codec.NewProtoCodec(codectypes.NewInterfaceRegistry()), + grpc.NewServer(), + grpc.NewServer(), + ) + versions := module.VersionMap{"bank": 1} + + updatedVersions, err := CreateUpgradeHandler(moduleManager, configurator)( + ctx, + upgradetypes.Plan{Name: UpgradeName}, + versions, + ) + + require.NoError(t, err) + require.Empty(t, updatedVersions) +} diff --git a/app/upgrades/v8/upgrades_test.go b/app/upgrades/v8/upgrades_test.go new file mode 100644 index 00000000..e10e2a01 --- /dev/null +++ b/app/upgrades/v8/upgrades_test.go @@ -0,0 +1,35 @@ +package v8 + +import ( + "testing" + + "cosmossdk.io/log" + upgradetypes "cosmossdk.io/x/upgrade/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" +) + +func TestCreateUpgradeHandlerCompletes(t *testing.T) { + ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()) + moduleManager := module.NewManager() + configurator := module.NewConfigurator( + codec.NewProtoCodec(codectypes.NewInterfaceRegistry()), + grpc.NewServer(), + grpc.NewServer(), + ) + versions := module.VersionMap{"bank": 1} + + updatedVersions, err := CreateUpgradeHandler(moduleManager, configurator)( + ctx, + upgradetypes.Plan{Name: UpgradeName}, + versions, + ) + + require.NoError(t, err) + require.Empty(t, updatedVersions) +} diff --git a/app/upgrades/v9/upgrades_test.go b/app/upgrades/v9/upgrades_test.go new file mode 100644 index 00000000..ea81bebd --- /dev/null +++ b/app/upgrades/v9/upgrades_test.go @@ -0,0 +1,244 @@ +package v9 + +import ( + "testing" + + "cosmossdk.io/log" + "cosmossdk.io/store" + "cosmossdk.io/store/metrics" + storetypes "cosmossdk.io/store/types" + upgradetypes "cosmossdk.io/x/upgrade/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + auth "github.com/cosmos/cosmos-sdk/x/auth" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + erc20types "github.com/cosmos/evm/x/erc20/types" + evmtypes "github.com/cosmos/evm/x/vm/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + legacyevmtypes "github.com/xrplevm/node/v10/types/legacy/ethermint/evm" + legacytypes "github.com/xrplevm/node/v10/types/legacy/ethermint/types" + "google.golang.org/grpc" +) + +var ( + _ ERC20Keeper = (*mockERC20Keeper)(nil) + _ EvmKeeper = (*mockEvmKeeper)(nil) +) + +type mockERC20Keeper struct { + dynamicPrecompiles []common.Address + nativePrecompiles []common.Address +} + +func (m *mockERC20Keeper) SetDynamicPrecompile(_ sdk.Context, precompile common.Address) { + m.dynamicPrecompiles = append(m.dynamicPrecompiles, precompile) +} + +func (m *mockERC20Keeper) SetNativePrecompile(_ sdk.Context, precompile common.Address) { + m.nativePrecompiles = append(m.nativePrecompiles, precompile) +} + +type mockEvmKeeper struct { + params evmtypes.Params + setParams bool + codeHashes map[common.Address]common.Hash + setParamsErr error +} + +func (m *mockEvmKeeper) GetParams(sdk.Context) evmtypes.Params { return m.params } +func (m *mockEvmKeeper) SetParams(_ sdk.Context, params evmtypes.Params) error { + m.setParams = true + m.params = params + return m.setParamsErr +} + +func (m *mockEvmKeeper) SetCodeHash(_ sdk.Context, addrBytes, hashBytes []byte) { + if m.codeHashes == nil { + m.codeHashes = make(map[common.Address]common.Hash) + } + m.codeHashes[common.BytesToAddress(addrBytes)] = common.BytesToHash(hashBytes) +} + +func TestCreateUpgradeHandlerMigratesLegacyState(t *testing.T) { + const dynamicPrecompile = "0x0000000000000000000000000000000000000001" + + appCodec := codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) + configurator := module.NewConfigurator(appCodec, grpc.NewServer(), grpc.NewServer()) + moduleManager := module.NewManager() + versions := module.VersionMap{"bank": 1} + + ctx, storeKeys := testContext(t, authtypes.StoreKey, erc20types.StoreKey, evmtypes.StoreKey) + setLegacyEvmParams(ctx, storeKeys, appCodec, legacyevmtypes.Params{ + EvmDenom: "axrp", + }) + ctx.KVStore(storeKeys[erc20types.StoreKey]).Set([]byte("DynamicPrecompiles"), []byte(dynamicPrecompile)) + + accountKeeper := testAccountKeeper(storeKeys[authtypes.StoreKey]) + evmKeeper := &mockEvmKeeper{} + erc20Keeper := &mockERC20Keeper{} + + updatedVersions, err := CreateUpgradeHandler( + moduleManager, + configurator, + storeKeys, + appCodec, + accountKeeper, + evmKeeper, + erc20Keeper, + )( + ctx, + upgradetypes.Plan{Name: UpgradeName}, + versions, + ) + + require.NoError(t, err) + require.Empty(t, updatedVersions) + require.Equal(t, []common.Address{common.HexToAddress(dynamicPrecompile)}, erc20Keeper.dynamicPrecompiles) + require.True(t, evmKeeper.setParams) +} + +func TestMigrateEvmModuleMigratesLegacyParams(t *testing.T) { + const ( + createAllowedAddress = "0x0000000000000000000000000000000000000001" + callRestrictedAddress = "0x0000000000000000000000000000000000000002" + staticPrecompile = "0x0000000000000000000000000000000000000003" + ) + ctx, storeKeys := testContext(t, evmtypes.StoreKey) + appCodec := codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) + legacyParams := legacyevmtypes.Params{ + EvmDenom: "axrp", + ExtraEIPs: []string{"ethereum_3855", "ethereum_3860"}, + EVMChannels: []string{"channel-0"}, + AccessControl: legacyevmtypes.AccessControl{ + Create: legacyevmtypes.AccessControlType{ + AccessType: legacyevmtypes.AccessTypePermissioned, + AccessControlList: []string{createAllowedAddress}, + }, + Call: legacyevmtypes.AccessControlType{ + AccessType: legacyevmtypes.AccessTypeRestricted, + AccessControlList: []string{callRestrictedAddress}, + }, + }, + ActiveStaticPrecompiles: []string{staticPrecompile}, + } + expectedParams := evmtypes.Params{ + EvmDenom: "axrp", + ExtraEIPs: []int64{3855, 3860}, + EVMChannels: []string{"channel-0"}, + AccessControl: evmtypes.AccessControl{ + Create: evmtypes.AccessControlType{ + AccessType: evmtypes.AccessTypePermissioned, + AccessControlList: []string{createAllowedAddress}, + }, + Call: evmtypes.AccessControlType{ + AccessType: evmtypes.AccessTypeRestricted, + AccessControlList: []string{callRestrictedAddress}, + }, + }, + ActiveStaticPrecompiles: []string{staticPrecompile}, + } + setLegacyEvmParams(ctx, storeKeys, appCodec, legacyParams) + keeper := &mockEvmKeeper{} + + err := MigrateEvmModule(ctx, storeKeys, appCodec, keeper) + + require.NoError(t, err) + require.True(t, keeper.setParams) + require.Equal(t, expectedParams, keeper.params) +} + +func TestMigrateErc20ModuleMigratesAndDeletesLegacyPrecompiles(t *testing.T) { + const ( + dynamicPrecompile1 = "0x0000000000000000000000000000000000000001" + dynamicPrecompile2 = "0x0000000000000000000000000000000000000002" + nativePrecompile = "0x0000000000000000000000000000000000000003" + ) + dynamicPrecompiles := []byte(dynamicPrecompile1 + dynamicPrecompile2) + nativePrecompiles := []byte(nativePrecompile) + + ctx, storeKeys := testContext(t, erc20types.StoreKey) + store := ctx.KVStore(storeKeys[erc20types.StoreKey]) + + store.Set([]byte("DynamicPrecompiles"), dynamicPrecompiles) + store.Set([]byte("NativePrecompiles"), nativePrecompiles) + + keeper := &mockERC20Keeper{} + + MigrateErc20Module(ctx, storeKeys, keeper) + + require.Equal(t, []common.Address{ + common.HexToAddress(dynamicPrecompile1), + common.HexToAddress(dynamicPrecompile2), + }, keeper.dynamicPrecompiles) + + require.Equal(t, []common.Address{common.HexToAddress(nativePrecompile)}, keeper.nativePrecompiles) + require.Nil(t, store.Get([]byte("DynamicPrecompiles"))) + require.Nil(t, store.Get([]byte("NativePrecompiles"))) +} + +func TestMigrateEthAccountsToBaseAccountsConvertsContracts(t *testing.T) { + ctx, storeKeys := testContext(t, authtypes.StoreKey) + accountKeeper := testAccountKeeper(storeKeys[authtypes.StoreKey]) + contractAddress := sdk.AccAddress(common.HexToAddress("0x0000000000000000000000000000000000000007").Bytes()) + codeHash := common.HexToHash("0x1234") + ethAccount := &legacytypes.EthAccount{ + BaseAccount: authtypes.NewBaseAccount(contractAddress, nil, 0, 0), + CodeHash: codeHash.Hex(), + } + accountKeeper.SetAccount(ctx, ethAccount) + keeper := &mockEvmKeeper{} + + MigrateEthAccountsToBaseAccounts(ctx, accountKeeper, keeper) + + require.IsType(t, &authtypes.BaseAccount{}, accountKeeper.GetAccount(ctx, contractAddress)) + require.Equal(t, codeHash, keeper.codeHashes[common.BytesToAddress(contractAddress)]) +} + +func testAccountKeeper(key *storetypes.KVStoreKey) authkeeper.AccountKeeper { + encCfg := moduletestutil.MakeTestEncodingConfig(auth.AppModuleBasic{}) + encCfg.InterfaceRegistry.RegisterImplementations((*sdk.AccountI)(nil), &legacytypes.EthAccount{}) + + return authkeeper.NewAccountKeeper( + encCfg.Codec, + runtime.NewKVStoreService(key), + legacytypes.ProtoAccount, + nil, + authcodec.NewBech32Codec("cosmos"), + "cosmos", + authtypes.NewModuleAddress("gov").String(), + ) +} + +func setLegacyEvmParams( + ctx sdk.Context, + storeKeys map[string]*storetypes.KVStoreKey, + appCodec codec.Codec, + params legacyevmtypes.Params, +) { + ctx.KVStore(storeKeys[evmtypes.StoreKey]).Set(evmtypes.KeyPrefixParams, appCodec.MustMarshal(¶ms)) +} + +func testContext(t *testing.T, storeKeys ...string) (sdk.Context, map[string]*storetypes.KVStoreKey) { + t.Helper() + + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) + keys := make(map[string]*storetypes.KVStoreKey, len(storeKeys)) + for _, storeKey := range storeKeys { + key := storetypes.NewKVStoreKey(storeKey) + keys[storeKey] = key + ms.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db) + } + require.NoError(t, ms.LoadLatestVersion()) + + return sdk.NewContext(ms, cmtproto.Header{}, false, log.NewNopLogger()), keys +}