This migration contains breaking changes that require code updatesv0.5.0 introduces significant changes to precompile interfaces, VM parameters, mempool architecture, and ante handlers. Review all breaking changes before upgrading.

Breaking Changes Summary

New Features


0) Preparation

Create an upgrade branch and prepare your environment:
git switch -c upgrade/evm-v0.5
go test ./...
evmd export > pre-upgrade-genesis.json

1) Dependency Updates

Update go.mod

- github.com/cosmos/evm v0.4.1
+ github.com/cosmos/evm v0.5.0
go mod tidy

2) VM Parameter Changes

BREAKING: allow_unprotected_txs Parameter Removed

The allow_unprotected_txs parameter has been removed from VM module parameters. Non-EIP-155 transaction acceptance is now managed per-node. Migration Required:
  1. Update Genesis Files:
{
  "app_state": {
    "vm": {
      "params": {
        "evm_denom": "atest",
        "extra_eips": [],
-       "allow_unprotected_txs": false,
        "evm_channels": [],
        "access_control": {...},
        "active_static_precompiles": [...],
+       "history_serve_window": 8192
      }
    }
  }
}
  1. Update Parameter Validation:
If you have custom parameter validation logic, remove references to allow_unprotected_txs:
/ Remove any code referencing AllowUnprotectedTxs
- if !params.AllowUnprotectedTxs {
-   / validation logic
- }

NEW: history_serve_window Parameter

Added for EIP-2935 block hash storage support.
  • Default value: 8192 blocks
  • Purpose: Controls how many historical block hashes to serve
  • Range: Must be > 0, recommended ≤ 8192 for optimal performance

3) Precompile Interface Changes

BREAKING: Constructor Interface Updates

All precompile constructors now accept keeper interfaces instead of concrete implementations for better decoupling. New Interface Definitions:
New Interface Definitions
/ precompiles/common/interfaces.go
type BankKeeper interface {
    IterateAccountBalances(ctx context.Context, account sdk.AccAddress, cb func(coin sdk.Coin) bool)
    GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin
    SendCoins(ctx context.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
    / ... other methods
}

type StakingKeeper interface {
    BondDenom(ctx context.Context) (string, error)
    GetDelegatorValidators(ctx context.Context, delegatorAddr sdk.AccAddress, maxRetrieve uint32) (stakingtypes.Validators, error)
    / ... other methods
}
Migration Steps:
  1. Update Precompile Assembly:
Precompile Assembly Update
/ evmd/precompiles.go or app/precompiles.go
func NewAvailableStaticPrecompiles(
    / ... other params
) map[common.Address]vm.PrecompiledContract {
    precompiles := make(map[common.Address]vm.PrecompiledContract)

    / Bank precompile - interface now required
-   bankPrecompile, err := bankprecompile.NewPrecompile(bankKeeper, ...)
+   bankPrecompile, err := bankprecompile.NewPrecompile(
+       common.BankKeeper(bankKeeper), / Cast to interface
+       / ... other params
+   )

    / Distribution precompile - simplified parameters
-   distributionPrecompile, err := distributionprecompile.NewPrecompile(
-       distributionKeeper, stakingKeeper, authzKeeper, cdc, options.AddressCodec
-   )
+   distributionPrecompile, err := distributionprecompile.NewPrecompile(
+       common.DistributionKeeper(distributionKeeper),
+       cdc, options.AddressCodec
+   )

    / Staking precompile - keeper interface
-   stakingPrecompile, err := stakingprecompile.NewPrecompile(stakingKeeper, ...)
+   stakingPrecompile, err := stakingprecompile.NewPrecompile(
+       common.StakingKeeper(stakingKeeper), 
+       / ... other params
+   )
    
    return precompiles
}
  1. Update Custom Precompiles:
If you have custom precompiles, update their constructors to accept interfaces:
/ Custom precompile constructor
- func NewMyPrecompile(bankKeeper bankkeeper.Keeper, stakingKeeper stakingkeeper.Keeper) (*MyPrecompile, error) {
+ func NewMyPrecompile(bankKeeper common.BankKeeper, stakingKeeper common.StakingKeeper) (*MyPrecompile, error) {
    return &MyPrecompile{
        bankKeeper:    bankKeeper,
        stakingKeeper: stakingKeeper,
    }, nil
}

4) Mempool Changes

Configuration-Based Architecture

The mempool now uses configuration objects instead of pre-built pools for better flexibility. Migration for Standard Setups: If you use default mempool configuration, minimal changes are needed:
Standard Mempool Setup
/ Existing code continues to work
mempoolConfig := &evmmempool.EVMMempoolConfig{
    AnteHandler:   app.GetAnteHandler(),
    BlockGasLimit: 100_000_000, / or 0 for default
}
evmMempool := evmmempool.NewExperimentalEVMMempool(
    app.CreateQueryContext, 
    logger, 
    app.EVMKeeper, 
    app.FeeMarketKeeper, 
    app.txConfig, 
    app.clientCtx, 
    mempoolConfig
)
Migration for Advanced Setups: If you previously built custom pools:
Advanced Mempool Configuration
 mempoolConfig := &evmmempool.EVMMempoolConfig{
-   TxPool:        customTxPool,
-   CosmosPool:    customCosmosPool,
+   LegacyPoolConfig: &legacypool.Config{
+       PriceLimit: 2,
+       / ... other legacy pool settings
+   },
+   CosmosPoolConfig: &sdkmempool.PriorityNonceMempoolConfig[math.Int]{
+       TxPriority: sdkmempool.TxPriority[math.Int]{
+           GetTxPriority: customPriorityFunc,
+           Compare: math.IntComparator,
+           MinValue: math.ZeroInt(),
+       },
+   },
   AnteHandler:      app.GetAnteHandler(),
   BroadcastTxFn:    customBroadcastFunc, / optional
   BlockGasLimit:    100_000_000,
 }

5) Ante Handler Changes

Performance Optimizations

The ante handler system has been optimized to remove unnecessary EVM instance creation. What Changed:
  • CanTransfer ante decorator no longer creates StateDB instances
  • EVM instance removal improves performance for balance checks
  • Signature verification optimizations
Migration Impact:
  • Standard setups: No changes required
  • Custom ante handlers: Verify compatibility with new CanTransfer behavior
Custom Ante Handler Updates: If you have custom ante handlers that depend on EVM instance creation during balance checks:
/ Custom ante handler example
func (d MyCustomDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
    / Balance checking now optimized - no EVM instance creation
-   evm := d.evmKeeper.NewEVM(ctx, ...)
-   stateDB := evm.StateDB
-   balance := stateDB.GetBalance(address)
    
    / Use keeper method instead
+   balance := d.evmKeeper.GetBalance(ctx, address)
    
    return next(ctx, tx, simulate)
}

Field Mapping (v0.4.x → v0.5.0)

  • Removed fields:
    • TxPool (pre-built EVM pool)
    • CosmosPool (pre-built Cosmos pool)
  • New/Replacement fields:
    • LegacyPoolConfig (configure legacy EVM txpool behavior)
    • CosmosPoolConfig (configure Cosmos PriorityNonceMempool behavior)
    • BlockGasLimit (required; 0 uses fallback 100_000_000)
    • BroadcastTxFn (optional callback; defaults to broadcasting via clientCtx)
    • MinTip (optional minimum tip for EVM selection)

6) Global Mempool Removal

BREAKING: Singleton Pattern Eliminated

The global mempool registry has been removed in favor of direct injection. What Was Removed:
  • mempool.SetGlobalEVMMempool()
  • mempool.GetGlobalEVMMempool()
  • Global mempool singleton pattern
Migration Steps:
  1. Update JSON-RPC Server Initialization:
JSON-RPC Server Update
/ server/start.go or equivalent
- mempool.SetGlobalEVMMempool(evmMempool)
  
  jsonRPCServer, err := jsonrpc.StartJSONRPC(
      ctx,
      clientCtx,
      logger.With("module", "jsonrpc"),
+     evmMempool, / Pass mempool directly
      config,
      indexer,
  )
  1. Update RPC Backend:
If you have custom RPC backends:
Custom RPC Backend Update
/ Custom RPC backend
func NewCustomBackend(
    / ... other params
+   mempool *evmmempool.ExperimentalEVMMempool,
) *CustomBackend {
-   mempool := mempool.GetGlobalEVMMempool()
    
    return &CustomBackend{
        mempool: mempool,
        / ... other fields
    }
}

7) EIP-7702 EOA Code Delegation

NEW FEATURE: Account Abstraction for EOAs

EIP-7702 enables externally owned accounts to temporarily execute smart contract code through authorization lists. What’s New:
  • SetCodeTx Transaction Type: New transaction type supporting code delegation
  • Authorization Signatures: Signed permissions for code delegation
  • Temporary Execution: EOAs can execute contract logic for single transactions
  • Account Abstraction: Multi-sig, time-locks, automated strategies
Implementation: x/vm/keeper/state_transition.go:426+ Usage Example:
/ Enable EOA to execute as multicall contract
const authorization = await signAuthorization({
    chainId: 9000,
    address: multicallContractAddress,
    nonce: await wallet.getNonce(),
}, wallet);

const tx = {
    type: 4, / SetCodeTxType  
    authorizationList: [authorization],
    to: wallet.address,
    data: multicall.interface.encodeFunctionData("batchCall", [calls]),
    gasLimit: 500000,
};

await wallet.sendTransaction(tx);
Developer Impact:
  • Enhanced Wallets: EOAs can have programmable features
  • Better UX: Batched operations, custom validation logic
  • Account Abstraction: Multi-sig and advanced security features
  • No Migration: Existing EOAs enhanced without changes

8) EIP-2935 Block Hash Storage

NEW FEATURE: Historical Block Hash Access

EIP-2935 provides standardized access to historical block hashes via contract storage. What’s New:
  • BLOCKHASH opcode now queries contract storage for historical hashes
  • New history_serve_window parameter controls storage depth
  • Compatible with Ethereum’s EIP-2935 specification
Configuration:
/ Genesis parameter
"history_serve_window": 8192  / Default: 8192 blocks
Usage for Developers:
/ Smart contract can now reliably access historical block hashes
contract HistoryExample {
    function getRecentBlockHash(uint256 blockNumber) public view returns (bytes32) {
        / Works for blocks within history_serve_window range
        return blockhash(blockNumber);
    }
}
Performance Considerations:
  • Larger history_serve_window values increase storage requirements
  • Default of 8192 provides good balance of utility and performance
  • Values > 8192 may impact node performance

8) New RPC Methods

eth_createAccessList

New JSON-RPC method for creating access lists to optimize transaction costs. Usage:
curl -X POST \
  -H "Content-Type: application/json" \
  --data '{
    "jsonrpc": "2.0",
    "method": "eth_createAccessList",
    "params": [{
      "to": "0x...",
      "data": "0x...",
      "gas": "0x...",
      "gasPrice": "0x..."
    }, "latest"],
    "id": 1
  }' \
  http://localhost:8545
Response:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "accessList": [
      {
        "address": "0x...",
        "storageKeys": ["0x..."]
      }
    ],
    "gasUsed": "0x..."
  }
}

9) Performance Improvements

Gas Estimation Optimization

  • Short-circuit plain transfers: Simple ETH transfers now bypass complex gas estimation
  • Optimistic bounds: Uses MaxUsedGas for better initial estimates
  • Result: Significantly faster eth_estimateGas performance

State Management

  • Reduced EVM instances: Fewer unnecessary EVM instance creations
  • Storage optimizations: Empty storage checks implemented per EIP standards
  • Block notifications: Improved timing prevents funding errors

Mempool Enhancements

  • Nonce gap handling: Better error handling for transaction sequencing
  • Configuration flexibility: Tunable parameters for different network conditions

10) Testing Your Migration

Pre-Upgrade Checklist

Pre-Upgrade Checklist
# 1. Backup current state
evmd export > pre-upgrade-state.json

# 2. Document existing parameters
evmd query vm params > pre-upgrade-params.json

# 3. Note active precompiles
evmd query erc20 token-pairs > pre-upgrade-token-pairs.json

Post-Upgrade Verification

Post-Upgrade Verification
# 1. Verify node starts successfully
evmd start

# 2. Test EVM functionality
cast send --rpc-url http://localhost:8545 --private-key $PRIVATE_KEY \
  0x... "transfer(address,uint256)" 0x... 1000

# 3. Verify new RPC methods
curl -X POST \
  -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_createAccessList","params":[...],"id":1}' \
  http://localhost:8545

# 4. Test precompile functionality
cast call 0x... "balanceOf(address)" 0x... --rpc-url http://localhost:8545

# 5. Verify EIP-2935 support
cast call --rpc-url http://localhost:8545 \
  $CONTRACT_ADDRESS "getBlockHash(uint256)" $BLOCK_NUMBER

Integration Tests

Integration Test Example
/ Example integration test
func TestV050Migration(t *testing.T) {
    / Test mempool configuration
    config := &evmmempool.EVMMempoolConfig{
        AnteHandler:   anteHandler,
        BlockGasLimit: 100_000_000,
    }
    mempool := evmmempool.NewExperimentalEVMMempool(...)
    require.NotNil(t, mempool)
    
    / Test precompile interfaces
    bankPrecompile, err := bankprecompile.NewPrecompile(
        common.BankKeeper(bankKeeper),
    )
    require.NoError(t, err)
    
    / Test parameter validation
    params := vmtypes.NewParams(...)
    require.Equal(t, uint64(8192), params.HistoryServeWindow)
    require.False(t, hasAllowUnprotectedTxs(params)) / Should be removed
}

11) Rollback Plan

If issues arise during migration:
# 1. Stop the upgraded node
systemctl stop evmd

# 2. Restore pre-upgrade binary
cp evmd-v0.4.1 /usr/local/bin/evmd

# 3. Restore pre-upgrade genesis (if needed)
cp pre-upgrade-genesis.json ~/.evmd/config/genesis.json

# 4. Restart with previous version
systemctl start evmd

12) Common Migration Issues

Issue: Precompile Constructor Errors

error: cannot use bankKeeper (type bankkeeper.Keeper) as type common.BankKeeper
Solution: Cast concrete keepers to interfaces:
bankPrecompile, err := bankprecompile.NewPrecompile(
    common.BankKeeper(bankKeeper), / Add interface cast
)

Issue: Genesis Validation Failure

error: unknown field 'allow_unprotected_txs' in vm params
Solution: Remove the parameter from genesis:
# Update genesis.json to remove allow_unprotected_txs
# Add history_serve_window with default value 8192

Issue: Mempool Initialization Panic

panic: config must not be nil
Solution: Always provide mempool configuration:
config := &evmmempool.EVMMempoolConfig{
    AnteHandler:   app.GetAnteHandler(),
    BlockGasLimit: 100_000_000,
}

Issue: Global Mempool Access

error: undefined: mempool.GetGlobalEVMMempool
Solution: Pass mempool directly instead of using global access:
/ Pass mempool as parameter
func NewRPCService(mempool *evmmempool.ExperimentalEVMMempool) {
    / Use injected mempool
}

13) Summary

v0.5.0 introduces significant improvements in performance, EVM compatibility, and code architecture:
  • EIP-2935 enables reliable historical block hash access
  • Precompile interfaces improve modularity and testing
  • Mempool optimizations provide better configurability
  • Performance improvements reduce gas estimation latency
  • New RPC methods enhance developer experience
Review all breaking changes carefully and test thoroughly before deploying to production. For additional support, refer to: