Skip to main content

0) Prep

  • Create a branch: git switch -c upgrade/evm-v0.5.
  • Ensure a clean build + tests green pre-upgrade.
  • Snapshot your current params/genesis for comparison later.

1) Dependency bumps (go.mod)

  • Bump github.com/cosmos/evm to v0.5.0 and run:
go mod tidy

2) Fix "github.com/cosmos/evm/types" imports

v0.5.0 removes github.com/cosmos/evm/types and moves files to their folders, respective to function. For a complete list of changes, refer to this PR. The following list includes references within evmd that have been moved.

Summary of import changes in evmd:

Removed import:
- cosmosevmtypes "github.com/cosmos/evm/types"
Added imports:
+ antetypes "github.com/cosmos/evm/ante/types"
+ evmaddress "github.com/cosmos/evm/encoding/address"
+ "github.com/cosmos/evm/utils"

Detailed mapping of moved items:

  • AttoPowerReduction → moved to "github.com/cosmos/evm/utils"
    - sdk.DefaultPowerReduction = cosmosevmtypes.AttoPowerReduction
    + sdk.DefaultPowerReduction = utils.AttoPowerReduction
    
  • HasDynamicFeeExtensionOption → moved to "github.com/cosmos/evm/ante/types"
    - ExtensionOptionChecker: cosmosevmtypes.HasDynamicFeeExtensionOption,
    + ExtensionOptionChecker: antetypes.HasDynamicFeeExtensionOption,
    
  • Address Codec functions → new package "github.com/cosmos/evm/encoding/address" Use evmaddress.NewEvmCodec() for address codec initialization:
    app.AccountKeeper = authkeeper.NewAccountKeeper(
        appCodec,
        runtime.NewKVStoreService(keys[authtypes.StoreKey]),
        authtypes.ProtoBaseAccount,
        evmconfig.GetMaccPerms(),
        evmaddress.NewEvmCodec(sdk.GetConfig().GetBech32AccountAddrPrefix()),
        sdk.GetConfig().GetBech32AccountAddrPrefix(),
        authAddr,
    )
    
  • Bip44CoinType, BIP44HDPath → moved to "github.com/cosmos/evm/crypto/hd"
  • GenesisState → removed as a duplicate object can be found in the evmd folder and a testing version is in "github.com/cosmos/evm/testutil"

3) App wiring in app.go

Mempool

Minimal setups: nothing to change

If you use the default mempool wiring (no custom pools), your existing code continues to work. If BlockGasLimit is 0, it defaults to 100_000_000. If BroadCastTxFn is not set, it’s also set to a default value.
mempoolConfig := &evmmempool.EVMMempoolConfig{
    AnteHandler:   app.GetAnteHandler(),
    BlockGasLimit: 100_000_000, // or 0 to use default
}
evmMempool := evmmempool.NewExperimentalEVMMempool(
    app.CreateQueryContext, logger, app.EVMKeeper, app.FeeMarketKeeper, app.txConfig, app.clientCtx, mempoolConfig
)

Advanced setups: migrate your customizations

PR #496 replaced pre-built pools with configs in EVMMempoolConfig:
  • Replace pools with configs
    • Removed: TxPool *txpool.TxPool, CosmosPool sdkmempool.ExtMempool
    • Added: LegacyPoolConfig *legacypool.Config, CosmosPoolConfig *sdkmempool.PriorityNonceMempoolConfig[math.Int]
If you built custom pools yourself:
 mempoolConfig := &evmmempool.EVMMempoolConfig{
-   TxPool:     customTxPool,
-   CosmosPool: customCosmosPool,
+   LegacyPoolConfig: &legacyCfg,  // or nil for defaults
+   CosmosPoolConfig: &cosmosCfg,  // or nil for defaults
   AnteHandler:      app.GetAnteHandler(),
   BroadCastTxFn:    myBroadcast, // optional
 }
Example custom configs:
// EVM legacy txpool tuning
legacyCfg := legacypool.DefaultConfig
legacyCfg.PriceLimit = 2
mempoolConfig.LegacyPoolConfig = &legacyCfg

// Cosmos priority mempool tuning
cosmosCfg := sdkmempool.PriorityNonceMempoolConfig[math.Int]{}
cosmosCfg.TxPriority = sdkmempool.TxPriority[math.Int]{
    GetTxPriority: func(goCtx context.Context, tx sdk.Tx) math.Int {
        // Custom priority function
    },
    Compare:  func(a, b math.Int) int { return a.BigInt().Cmp(b.BigInt()) },
    MinValue: math.ZeroInt(),
}
mempoolConfig.CosmosPoolConfig = &cosmosCfg

// Custom EVM broadcast (optional)
mempoolConfig.BroadCastTxFn = func(txs []*ethtypes.Transaction) error { return nil }

New Configuration Options

PR #698 adds new configuration options for the EVM mempool that can be set via app.toml or CLI flags. These options allow fine-tuning of the EVM legacy pool behavior.
Configuration via app.toml
The following mempool configuration options are now available in app.toml under the [evm.mempool] section:
[evm.mempool]
# PriceLimit is the minimum gas price to enforce for acceptance into the pool (in wei)
price-limit = 1

# PriceBump is the minimum price bump percentage to replace an already existing transaction (nonce)
price-bump = 10

# AccountSlots is the number of executable transaction slots guaranteed per account
account-slots = 16

# GlobalSlots is the maximum number of executable transaction slots for all accounts
global-slots = 5120

# AccountQueue is the maximum number of non-executable transaction slots permitted per account
account-queue = 64

# GlobalQueue is the maximum number of non-executable transaction slots for all accounts
global-queue = 1024

# Lifetime is the maximum amount of time non-executable transaction are queued
lifetime = "3h0m0s"
Configuration via CLI Flags
These options can also be set via CLI flags:
  • --evm.mempool.price-limit (default: 1)
  • --evm.mempool.price-bump (default: 10)
  • --evm.mempool.account-slots (default: 16)
  • --evm.mempool.global-slots (default: 5120)
  • --evm.mempool.account-queue (default: 64)
  • --evm.mempool.global-queue (default: 1024)
  • --evm.mempool.lifetime (default: 3h0m0s)
Cosmos Mempool Max Transactions
A new flag --mempool.max-txs allows limiting the maximum number of transactions in the Cosmos mempool. Set to 0 or -1 for unbounded (default: 0).
Simplified Mempool Setup
The mempool configuration can now be handled by a helper function. If you prefer to use the configuration from app.toml and CLI flags, you can refactor your mempool setup:
// Before: Manual configuration in app.go
mempoolConfig := &evmmempool.EVMMempoolConfig{
    AnteHandler:   app.GetAnteHandler(),
    BlockGasLimit: blockGasLimit,
    MinTip:        minTip,
}
evmMempool := evmmempool.NewExperimentalEVMMempool(
    app.CreateQueryContext, logger, app.EVMKeeper, app.FeeMarketKeeper,
    app.txConfig, app.clientCtx, mempoolConfig,
)

// After: Using helper function (optional)
// See evmd/mempool.go for reference implementation
if err := app.configureEVMMempool(appOpts, logger); err != nil {
    panic(fmt.Sprintf("failed to configure EVM mempool: %s", err.Error()))
}
The helper function reads configuration from appOpts and applies defaults where needed. Note that NewExperimentalEVMMempool now takes an additional cosmosPoolMaxTx parameter.

Default Precompiles

Default precompiles have been moved to /evm/precompiles/types/defaults.go and the function name was changed to DefaultStaticPrecompiles. The function signature has also changed, and now takes pointers as inputs for the Erc20Keeper and TransferKeeper. Finally, the WithStaticPrecompiles builder function can now happen alongside the keeper instantiation, and not after. The new wiring is shown below:
	app.EVMKeeper = evmkeeper.NewKeeper(
		appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], keys,
		authtypes.NewModuleAddress(govtypes.ModuleName),
		app.AccountKeeper,
		app.PreciseBankKeeper,
		app.StakingKeeper,
		app.FeeMarketKeeper,
		&app.ConsensusParamsKeeper,
		&app.Erc20Keeper,
		tracer,
	).WithStaticPrecompiles(
		precompiletypes.DefaultStaticPrecompiles(
			*app.StakingKeeper,
			app.DistrKeeper,
			app.PreciseBankKeeper,
			&app.Erc20Keeper, // UPDATED
			&app.TransferKeeper, // UPDATED
			app.IBCKeeper.ChannelKeeper,
			app.GovKeeper,
			app.SlashingKeeper,
			appCodec,
		),
	)

Denom Configs

#661 removes the instantiation of chain configs via app.go and moves them to state or genesis. It is critical to remove any use of EvmAppOptions as calling the configurator will panic the chain at runtime during startup.

EVM Chain ID

The EVM chain ID is now retrieved directly from appOpts instead of being passed as a parameter. In app.go, the chain ID is obtained using:
evmChainID := cast.ToUint64(appOpts.Get(srvflags.EVMChainID))
See evmd/app.go:216 for the reference implementation.

Function Signature Changes

In app.go, remove evmChainID and evmAppOptions from the NewApp signature.
// NewExampleApp returns a reference to an initialized EVMD.
func NewExampleApp(
	logger log.Logger,
	db dbm.DB,
	traceStore io.Writer,
	loadLatest bool,
	appOpts servertypes.AppOptions,
-	evmChainID uint64,
-	evmAppOptions evmconfig.EVMOptionsFn,
	baseAppOptions ...func(*baseapp.BaseApp),
) *EVMD {
Afterwards, fix any reference to the function by removing the inputs. Then, remove any reference of evmAppOptions being called: app.go
-	if err := evmAppOptions(evmChainID); err != nil {
-		panic(err)
-	}
root.go
- noOpEvmAppOptions := func(_ uint64) error {
-		return nil
-	}
- if initClientCtx.ChainID != "" {
-		if err := config.EvmAppOptions(config.EVMChainID); err != nil {
-			panic(err)
-		}
-	}
evmd_config.go, chain_id.go, config.go, constants.go have been moved to github.com/cosmos/evm/config and may be removed to your repo.

UpgradeHandler

As the configs have been moved to state and genesis, you must include an UpgradeHandler if your chain does not satisfy the following conditions.
  1. Your EVM Denom set in the x/vm params must have DenomMetadata registered for it in x/bank.
  2. Your EVM Denom must have a display denom associated with it in DenomMetadata.
    1. The display denom for the EVM Denom must have an accurate decimal value (i.e. for uatom, atom must have a decimal value of 6.
  3. Your chain is an 18-decimal chain.
In your UpgradeHandler:
  • If your chain does not have DenomMetadata set for the EVM Denom, you must include it.
  • If your chain’s EVM denom is not 18 decimals, you must add ExtendedDenomOptions to your x/vm params.
Please refer to the upgrade example for more information.

4) Build & quick tests

go build ./...
Smoke test on a single node:
  • Send a few EVM txs; confirm promotion/broadcast (or your BroadCastTxFn).
  • Send Cosmos txs; confirm ordering reflects your CosmosPoolConfig (if customized).
I