0) 准备

  • 创建分支:git switch -c upgrade/evm-v0.6。
  • 确保升级前构建干净且测试通过。
  • 为当前的 params/genesis 做快照,便于后续对比。

1) 依赖升级(go.mod)

  • 将 github.com/cosmos/evm 升级到 v0.6.0 并运行:
go mod tidy

2) 应用接线变更

IBC Transfer 模块

v0.6.0 移除了自定义 IBC transfer keeper 覆盖,并改为直接使用官方 IBC-Go transfer keeper。这意味着无法通过 Cosmos IBC transfer 交易进行 ERC20 转换。这些转换现在仅在 ICS20 precompile 中处理,任何 ERC20 转账都必须通过那里发起。 app.go 中所需的变更:
  1. 更新导入 - 用官方 IBC-Go 导入替换自定义 transfer 导入:
- import (
-     "github.com/cosmos/evm/x/ibc/transfer"
-     transferkeeper "github.com/cosmos/evm/x/ibc/transfer/keeper"
-     transferv2 "github.com/cosmos/evm/x/ibc/transfer/v2"
-     ibctransfer "github.com/cosmos/ibc-go/v11/modules/apps/transfer"
-     ibctransfertypes "github.com/cosmos/ibc-go/v11/modules/apps/transfer/types"
- )
+ import (
+     transfer "github.com/cosmos/ibc-go/v11/modules/apps/transfer"
+     transferkeeper "github.com/cosmos/ibc-go/v11/modules/apps/transfer/keeper"
+     ibctransfertypes "github.com/cosmos/ibc-go/v11/modules/apps/transfer/types"
+     transferv2 "github.com/cosmos/ibc-go/v11/modules/apps/transfer/v2"
+ )
  1. 更新 TransferKeeper 初始化 - 移除 ERC20 keeper 参数:
  app.TransferKeeper = transferkeeper.NewKeeper(
      appCodec,
      runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]),
+     nil, // ICS4Wrapper param
      app.IBCKeeper.ChannelKeeper,
      app.IBCKeeper.ChannelKeeper,
      app.MsgServiceRouter(),
      app.AccountKeeper,
      app.BankKeeper,
-     app.Erc20Keeper, // Remove: no longer passed to transfer keeper
      authAddr,
  )
  1. 更新模块注册 - 使用官方 transfer 模块:
  app.BasicModuleManager = module.NewBasicManager(
      // ... other modules
-     ibctransfertypes.ModuleName: transfer.AppModuleBasic{AppModuleBasic: &ibctransfer.AppModuleBasic{}},
+     ibctransfertypes.ModuleName: transfer.AppModuleBasic{},
  )
  1. 更新 ICS20 precompile 接线 - 向 ICS20 precompile 传入 ERC20 keeper:
  precompiletypes.DefaultStaticPrecompiles(
      *app.StakingKeeper,
      app.DistrKeeper,
      app.PreciseBankKeeper,
      &app.Erc20Keeper,
      &app.TransferKeeper,
      app.IBCKeeper.ChannelKeeper,
      app.GovKeeper,
      app.SlashingKeeper,
      appCodec,
  )
ICS20 precompile 现在接收 ERC20 keeper 作为参数(而不是由 transfer keeper 接收它)。这样一来,precompile 就可以直接处理 ERC20 转换。

3) 破坏性 API 变更

StateDB 要求

v0.6.0 对事件追踪和状态管理引入了重大变更。所有 EVM 执行函数现在都需要显式传入 stateDB 参数和 callFromPrecompile 标志,以便正确处理事件管理和状态转换。 **注意:**受影响的函数调用只有 CallEVM、CallEVMWithData、ApplyMessage 和 ApplyMessageWithConfig。这些函数通常用于常见的 precompile,以及从 SDK 回调进入 EVM 的逻辑。如果你的项目没有使用这些函数,那么这次升级无需执行额外步骤。

具体变更

以下函数的签名已更新:
CallEVM
变更前(v0.5.x):
func (k Keeper) CallEVM(
    ctx sdk.Context,
    abi abi.ABI,
    from, contract common.Address,
    commit bool,
    gasCap *big.Int,
    method string,
    args ...interface{},
) (*types.MsgEthereumTxResponse, error)
变更后(v0.6.0):
func (k Keeper) CallEVM(
    ctx sdk.Context,
    stateDB *statedb.StateDB,
    abi abi.ABI,
    from, contract common.Address,
    commit bool,
    callFromPrecompile bool,
    gasCap *big.Int,
    method string,
    args ...interface{},
) (*types.MsgEthereumTxResponse, error)
CallEVMWithData
变更前(v0.5.x):
func (k Keeper) CallEVMWithData(
    ctx sdk.Context,
    from common.Address,
    contract *common.Address,
    data []byte,
    commit bool,
    gasCap *big.Int,
) (*types.MsgEthereumTxResponse, error)
变更后(v0.6.0):
func (k Keeper) CallEVMWithData(
    ctx sdk.Context,
    stateDB *statedb.StateDB,
    from common.Address,
    contract *common.Address,
    data []byte,
    commit bool,
    callFromPrecompile bool,
    gasCap *big.Int,
) (*types.MsgEthereumTxResponse, error)
ApplyMessage
变更前(v0.5.x):
func (k *Keeper) ApplyMessage(
    ctx sdk.Context,
    msg core.Message,
    tracer *tracing.Hooks,
    commit bool,
    internal bool,
) (*types.MsgEthereumTxResponse, error)
变更后(v0.6.0):
func (k *Keeper) ApplyMessage(
    ctx sdk.Context,
    stateDB *statedb.StateDB,
    msg core.Message,
    tracer *tracing.Hooks,
    commit bool,
    callFromPrecompile bool,
    internal bool,
) (*types.MsgEthereumTxResponse, error)
ApplyMessageWithConfig
变更前(v0.5.x):
func (k *Keeper) ApplyMessageWithConfig(
    ctx sdk.Context,
    msg core.Message,
    tracer *tracing.Hooks,
    commit bool,
    cfg *statedb.EVMConfig,
    txConfig statedb.TxConfig,
    internal bool,
    overrides *rpctypes.StateOverride,
) (*types.MsgEthereumTxResponse, error)
变更后(v0.6.0):
func (k *Keeper) ApplyMessageWithConfig(
    ctx sdk.Context,
    stateDB *statedb.StateDB,
    msg core.Message,
    tracer *tracing.Hooks,
    commit bool,
    callFromPrecompile bool,
    cfg *statedb.EVMConfig,
    txConfig statedb.TxConfig,
    internal bool,
    overrides *rpctypes.StateOverride,
) (*types.MsgEthereumTxResponse, error)

迁移步骤

对于非 Precompile 上下文

如果你是在 precompile 之外 调用 EVM 函数(例如在模块 keeper、message server 或 query handler 中):
  1. 在调用 EVM 函数之前创建一个新的 stateDB
  2. 为 callFromPrecompile 参数传入 false
示例:
import (
    "github.com/cosmos/evm/x/vm/statedb"
)

// Before (v0.5.x)
res, err := k.evmKeeper.CallEVM(
    ctx,
    abi,
    from,
    contract,
    false, // commit
    nil,   // gasCap
    "balanceOf",
    account,
)

// After (v0.6.0)
stateDB := statedb.New(ctx, k.evmKeeper, statedb.NewEmptyTxConfig())
res, err := k.evmKeeper.CallEVM(
    ctx,
    stateDB,
    abi,
    from,
    contract,
    false, // commit
    false, // callFromPrecompile
    nil,   // gasCap
    "balanceOf",
    account,
)

对于 Precompile 上下文

如果你是在 precompile 内部 调用 EVM 函数:
  1. 复用现有的 stateDB(不要创建新的)
  2. 为 callFromPrecompile 参数传入 true
  3. 现有的 stateDB 通常可以作为 precompile 函数中的参数获得
示例:
// In your precompile's Run() method, you'll have access to stateDB
func (p *MyPrecompile) Run(
    evm *vm.EVM,
    contract *vm.Contract,
    readOnly bool,
) ([]byte, error) {
    stateDB := evm.StateDB.(*statedb.StateDB)

    // Use the existing stateDB and set callFromPrecompile=true
    res, err := p.evmKeeper.CallEVM(
        ctx,
        stateDB,        // reuse existing stateDB
        abi,
        from,
        contract,
        true,           // commit (will flush to cache context)
        true,           // callFromPrecompile
        nil,            // gasCap
        "transfer",
        recipient,
        amount,
    )
}

重要说明

  • 绝不要为 stateDB 传入 nil:这会返回 ErrNilStateDB 错误
  • Precompile 中的 commit 行为:当 commit=true 且 callFromPrecompile=true 时,状态变更会被刷新到 cache context,而不是被完全提交。这样可以避免在嵌套调用场景中压平缓存栈。

EVMKeeper 接口变更

如果你实现或 mock 了 EVMKeeper 接口,请更新你的实现:
type EVMKeeper interface {
    // Updated signatures
    ApplyMessage(
        ctx sdk.Context,
        stateDB *statedb.StateDB,
        msg core.Message,
        tracer *tracing.Hooks,
        commit, callFromPrecompile, internal bool,
    ) (*evmtypes.MsgEthereumTxResponse, error)

    CallEVM(
        ctx sdk.Context,
        stateDB *statedb.StateDB,
        abi abi.ABI,
        from, contract common.Address,
        commit, callFromPrecompile bool,
        gasCap *big.Int,
        method string,
        args ...interface{},
    ) (*evmtypes.MsgEthereumTxResponse, error)

    CallEVMWithData(
        ctx sdk.Context,
        stateDB *statedb.StateDB,
        from common.Address,
        contract *common.Address,
        data []byte,
        commit bool,
        callFromPrecompile bool,
        gasCap *big.Int,
    ) (*evmtypes.MsgEthereumTxResponse, error)

    // ... other methods
}

4) ERC20 Keeper 接口变更

ERC20Keeper 接口新增了以下方法:
type ERC20Keeper interface {
    // ... existing methods

    // New methods in v0.6.0
    IsERC20Enabled(ctx sdk.Context) bool
    GetTokenPairID(ctx sdk.Context, token string) []byte
    ConvertERC20IntoCoinsForNativeToken(
        ctx sdk.Context,
        stateDB *statedb.StateDB,
        contract ethcommon.Address,
        amount math.Int,
        receiver sdk.AccAddress,
        sender ethcommon.Address,
        commit bool,
        callFromPrecompile bool,
    ) (*erc20types.MsgConvertERC20Response, error)
}
如果你实现了这个接口,请在你的实现中添加这些方法。

5) 错误处理

新增了一种错误类型:
var ErrNilStateDB = errorsmod.Register(ModuleName, codeErrNilStateDB, "stateDB cannot be nil")
当把 nil 作为 stateDB 参数传给 EVM 函数时,会返回此错误。

6) 构建与测试

go build ./...
go test ./...

测试检查清单

迁移后,请验证:
  • 所有 EVM 调用都传入了有效的 stateDB
  • 非 precompile 调用使用 callFromPrecompile=false
  • precompile 调用复用了现有的 stateDB,并使用 callFromPrecompile=true
  • 事件发射在成功和 revert 场景下都能正常工作
  • 状态变更能够被正确提交或回滚

常见迁移示例

示例 1:模块 Keeper 查询

// Before (v0.5.x)
func (k Keeper) QueryBalance(ctx sdk.Context, addr common.Address) (*big.Int, error) {
    res, err := k.evmKeeper.CallEVM(
        ctx, erc20ABI, moduleAddr, contract, false, nil, "balanceOf", addr,
    )
    // ...
}

// After (v0.6.0)
func (k Keeper) QueryBalance(ctx sdk.Context, addr common.Address) (*big.Int, error) {
    stateDB := statedb.New(ctx, k.evmKeeper, statedb.NewEmptyTxConfig())
    res, err := k.evmKeeper.CallEVM(
        ctx, stateDB, erc20ABI, moduleAddr, contract, false, false, nil, "balanceOf", addr,
    )
    // ...
}

示例 2:Message Server 交易

// Before (v0.5.x)
func (ms msgServer) ConvertCoin(
        goCtx context.Context,
        msg *types.MsgConvertCoin
    ) (*types.MsgConvertCoinResponse, error) {
    ctx := sdk.UnwrapSDKContext(goCtx)
    // ...
    res, err := ms.evmKeeper.CallEVMWithData(
        ctx, moduleAddr, &contract, data, true, nil,
    )
    // ...
}

// After (v0.6.0)
func (ms msgServer) ConvertCoin(
        goCtx context.Context,
        msg *types.MsgConvertCoin
    ) (*types.MsgConvertCoinResponse, error) {
    ctx := sdk.UnwrapSDKContext(goCtx)
    // ...
    stateDB := statedb.New(ctx, ms.evmKeeper, statedb.NewEmptyTxConfig())
    res, err := ms.evmKeeper.CallEVMWithData(
        ctx, stateDB, moduleAddr, &contract, data, true, false, nil,
    )
    // ...
}

示例 3:预编译内部调用

// Before (v0.5.x)
func (p *StakingPrecompile) delegate(
    ctx sdk.Context,
    evm *vm.EVM,
    // ...
) ([]byte, error) {
    // ... delegate logic ...
    res, err := p.evmKeeper.CallEVM(
        ctx, delegationABI, from, contract, true, nil, "afterDelegate",
    )
    // ...
}

// After (v0.6.0)
func (p *StakingPrecompile) delegate(
    ctx sdk.Context,
    evm *vm.EVM,
    stateDB *statedb.StateDB, // typically passed from Run()
    // ...
) ([]byte, error) {
    // ... delegate logic ...
    res, err := p.evmKeeper.CallEVM(
        ctx, stateDB, delegationABI, from, contract, true, true, nil, "afterDelegate",
    )
    // ...
}

0) Prep

  • Create a branch: git switch -c upgrade/evm-v0.6.
  • 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.6.0 and run:
go mod tidy

2) App Wiring Changes

IBC Transfer Module

v0.6.0 removes the custom IBC transfer keeper override and now uses the official IBC-Go transfer keeper directly. This means that ERC20 conversions via Cosmos IBC transfer transactions are not possible. These are now only handled in the ICS20 precompile, and any ERC20 transfer must be initiated through there. Changes required in app.go:
  1. Update imports - Replace custom transfer imports with official IBC-Go imports:
- import (
-     "github.com/cosmos/evm/x/ibc/transfer"
-     transferkeeper "github.com/cosmos/evm/x/ibc/transfer/keeper"
-     transferv2 "github.com/cosmos/evm/x/ibc/transfer/v2"
-     ibctransfer "github.com/cosmos/ibc-go/v11/modules/apps/transfer"
-     ibctransfertypes "github.com/cosmos/ibc-go/v11/modules/apps/transfer/types"
- )
+ import (
+     transfer "github.com/cosmos/ibc-go/v11/modules/apps/transfer"
+     transferkeeper "github.com/cosmos/ibc-go/v11/modules/apps/transfer/keeper"
+     ibctransfertypes "github.com/cosmos/ibc-go/v11/modules/apps/transfer/types"
+     transferv2 "github.com/cosmos/ibc-go/v11/modules/apps/transfer/v2"
+ )
  1. Update TransferKeeper initialization - Remove ERC20 keeper parameter:
  app.TransferKeeper = transferkeeper.NewKeeper(
      appCodec,
      runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]),
+     nil, // ICS4Wrapper param
      app.IBCKeeper.ChannelKeeper,
      app.IBCKeeper.ChannelKeeper,
      app.MsgServiceRouter(),
      app.AccountKeeper,
      app.BankKeeper,
-     app.Erc20Keeper, // Remove: no longer passed to transfer keeper
      authAddr,
  )
  1. Update module registration - Use official transfer module:
  app.BasicModuleManager = module.NewBasicManager(
      // ... other modules
-     ibctransfertypes.ModuleName: transfer.AppModuleBasic{AppModuleBasic: &ibctransfer.AppModuleBasic{}},
+     ibctransfertypes.ModuleName: transfer.AppModuleBasic{},
  )
  1. Update ICS20 precompile wiring - Pass ERC20 keeper to ICS20 precompile:
  precompiletypes.DefaultStaticPrecompiles(
      *app.StakingKeeper,
      app.DistrKeeper,
      app.PreciseBankKeeper,
      &app.Erc20Keeper,
      &app.TransferKeeper,
      app.IBCKeeper.ChannelKeeper,
      app.GovKeeper,
      app.SlashingKeeper,
      appCodec,
  )
The ICS20 precompile now takes the ERC20 keeper as a parameter (instead of the transfer keeper receiving it). This allows the precompile to handle ERC20 conversions directly.

3) Breaking API Changes

StateDB Requirements

v0.6.0 introduces significant changes to event tracking and state management. All EVM execution functions now require an explicit stateDB parameter and a callFromPrecompile flag to properly handle event management and state transitions. NOTE: The only function calls affected are CallEVM, CallEVMWithData, ApplyMessage, and ApplyMessageWithConfig. These are typically used in common precompiles and logic that calls back into the EVM from the SDK. If your project does not use these functions, then no steps need to be taken for the upgrade.

Advanced Changes

The following functions have updated signatures:
CallEVM
Before (v0.5.x):
func (k Keeper) CallEVM(
    ctx sdk.Context,
    abi abi.ABI,
    from, contract common.Address,
    commit bool,
    gasCap *big.Int,
    method string,
    args ...interface{},
) (*types.MsgEthereumTxResponse, error)
After (v0.6.0):
func (k Keeper) CallEVM(
    ctx sdk.Context,
    stateDB *statedb.StateDB,
    abi abi.ABI,
    from, contract common.Address,
    commit bool,
    callFromPrecompile bool,
    gasCap *big.Int,
    method string,
    args ...interface{},
) (*types.MsgEthereumTxResponse, error)
CallEVMWithData
Before (v0.5.x):
func (k Keeper) CallEVMWithData(
    ctx sdk.Context,
    from common.Address,
    contract *common.Address,
    data []byte,
    commit bool,
    gasCap *big.Int,
) (*types.MsgEthereumTxResponse, error)
After (v0.6.0):
func (k Keeper) CallEVMWithData(
    ctx sdk.Context,
    stateDB *statedb.StateDB,
    from common.Address,
    contract *common.Address,
    data []byte,
    commit bool,
    callFromPrecompile bool,
    gasCap *big.Int,
) (*types.MsgEthereumTxResponse, error)
ApplyMessage
Before (v0.5.x):
func (k *Keeper) ApplyMessage(
    ctx sdk.Context,
    msg core.Message,
    tracer *tracing.Hooks,
    commit bool,
    internal bool,
) (*types.MsgEthereumTxResponse, error)
After (v0.6.0):
func (k *Keeper) ApplyMessage(
    ctx sdk.Context,
    stateDB *statedb.StateDB,
    msg core.Message,
    tracer *tracing.Hooks,
    commit bool,
    callFromPrecompile bool,
    internal bool,
) (*types.MsgEthereumTxResponse, error)
ApplyMessageWithConfig
Before (v0.5.x):
func (k *Keeper) ApplyMessageWithConfig(
    ctx sdk.Context,
    msg core.Message,
    tracer *tracing.Hooks,
    commit bool,
    cfg *statedb.EVMConfig,
    txConfig statedb.TxConfig,
    internal bool,
    overrides *rpctypes.StateOverride,
) (*types.MsgEthereumTxResponse, error)
After (v0.6.0):
func (k *Keeper) ApplyMessageWithConfig(
    ctx sdk.Context,
    stateDB *statedb.StateDB,
    msg core.Message,
    tracer *tracing.Hooks,
    commit bool,
    callFromPrecompile bool,
    cfg *statedb.EVMConfig,
    txConfig statedb.TxConfig,
    internal bool,
    overrides *rpctypes.StateOverride,
) (*types.MsgEthereumTxResponse, error)

Migration Steps

For Non-Precompile Contexts

If you’re calling EVM functions from outside a precompile (e.g., from a module keeper, message server, or query handler):
  1. Create a new stateDB before calling EVM functions
  2. Pass false for the callFromPrecompile parameter
Example:
import (
    "github.com/cosmos/evm/x/vm/statedb"
)

// Before (v0.5.x)
res, err := k.evmKeeper.CallEVM(
    ctx,
    abi,
    from,
    contract,
    false, // commit
    nil,   // gasCap
    "balanceOf",
    account,
)

// After (v0.6.0)
stateDB := statedb.New(ctx, k.evmKeeper, statedb.NewEmptyTxConfig())
res, err := k.evmKeeper.CallEVM(
    ctx,
    stateDB,
    abi,
    from,
    contract,
    false, // commit
    false, // callFromPrecompile
    nil,   // gasCap
    "balanceOf",
    account,
)

For Precompile Contexts

If you’re calling EVM functions from within a precompile:
  1. Reuse the existing stateDB from your precompile context (do not create a new one)
  2. Pass true for the callFromPrecompile parameter
  3. The existing stateDB is typically available as a parameter in your precompile function
Example:
// In your precompile's Run() method, you'll have access to stateDB
func (p *MyPrecompile) Run(
    evm *vm.EVM,
    contract *vm.Contract,
    readOnly bool,
) ([]byte, error) {
    stateDB := evm.StateDB.(*statedb.StateDB)

    // Use the existing stateDB and set callFromPrecompile=true
    res, err := p.evmKeeper.CallEVM(
        ctx,
        stateDB,        // reuse existing stateDB
        abi,
        from,
        contract,
        true,           // commit (will flush to cache context)
        true,           // callFromPrecompile
        nil,            // gasCap
        "transfer",
        recipient,
        amount,
    )
}

Important Notes

  • Never pass nil for stateDB: This will return ErrNilStateDB error
  • Commit behavior in precompiles: When commit=true and callFromPrecompile=true, the state changes are flushed to the cache context rather than fully committed. This prevents collapsing the cache stack in nested call scenarios.

EVMKeeper Interface Changes

If you implement or mock the EVMKeeper interface, update your implementation:
type EVMKeeper interface {
    // Updated signatures
    ApplyMessage(
        ctx sdk.Context,
        stateDB *statedb.StateDB,
        msg core.Message,
        tracer *tracing.Hooks,
        commit, callFromPrecompile, internal bool,
    ) (*evmtypes.MsgEthereumTxResponse, error)

    CallEVM(
        ctx sdk.Context,
        stateDB *statedb.StateDB,
        abi abi.ABI,
        from, contract common.Address,
        commit, callFromPrecompile bool,
        gasCap *big.Int,
        method string,
        args ...interface{},
    ) (*evmtypes.MsgEthereumTxResponse, error)

    CallEVMWithData(
        ctx sdk.Context,
        stateDB *statedb.StateDB,
        from common.Address,
        contract *common.Address,
        data []byte,
        commit bool,
        callFromPrecompile bool,
        gasCap *big.Int,
    ) (*evmtypes.MsgEthereumTxResponse, error)

    // ... other methods
}

4) ERC20 Keeper Interface Changes

The ERC20Keeper interface has new methods:
type ERC20Keeper interface {
    // ... existing methods

    // New methods in v0.6.0
    IsERC20Enabled(ctx sdk.Context) bool
    GetTokenPairID(ctx sdk.Context, token string) []byte
    ConvertERC20IntoCoinsForNativeToken(
        ctx sdk.Context,
        stateDB *statedb.StateDB,
        contract ethcommon.Address,
        amount math.Int,
        receiver sdk.AccAddress,
        sender ethcommon.Address,
        commit bool,
        callFromPrecompile bool,
    ) (*erc20types.MsgConvertERC20Response, error)
}
If you implement this interface, add these methods to your implementation.

5) Error Handling

A new error type has been added:
var ErrNilStateDB = errorsmod.Register(ModuleName, codeErrNilStateDB, "stateDB cannot be nil")
This error is returned when nil is passed as the stateDB parameter to EVM functions.

6) Build & Tests

go build ./...
go test ./...

Testing Checklist

After migration, verify:
  • All EVM calls pass a valid stateDB
  • Non-precompile calls use callFromPrecompile=false
  • Precompile calls reuse existing stateDB and use callFromPrecompile=true
  • Event emission works correctly in both success and revert scenarios
  • State changes are properly committed or reverted

Common Migration Examples

Example 1: Module Keeper Query

// Before (v0.5.x)
func (k Keeper) QueryBalance(ctx sdk.Context, addr common.Address) (*big.Int, error) {
    res, err := k.evmKeeper.CallEVM(
        ctx, erc20ABI, moduleAddr, contract, false, nil, "balanceOf", addr,
    )
    // ...
}

// After (v0.6.0)
func (k Keeper) QueryBalance(ctx sdk.Context, addr common.Address) (*big.Int, error) {
    stateDB := statedb.New(ctx, k.evmKeeper, statedb.NewEmptyTxConfig())
    res, err := k.evmKeeper.CallEVM(
        ctx, stateDB, erc20ABI, moduleAddr, contract, false, false, nil, "balanceOf", addr,
    )
    // ...
}

Example 2: Message Server Transaction

// Before (v0.5.x)
func (ms msgServer) ConvertCoin(
	    goCtx context.Context,
		msg *types.MsgConvertCoin
    ) (*types.MsgConvertCoinResponse, error) {
    ctx := sdk.UnwrapSDKContext(goCtx)
    // ...
    res, err := ms.evmKeeper.CallEVMWithData(
        ctx, moduleAddr, &contract, data, true, nil,
    )
    // ...
}

// After (v0.6.0)
func (ms msgServer) ConvertCoin(
        goCtx context.Context,
        msg *types.MsgConvertCoin
    ) (*types.MsgConvertCoinResponse, error) {
    ctx := sdk.UnwrapSDKContext(goCtx)
    // ...
    stateDB := statedb.New(ctx, ms.evmKeeper, statedb.NewEmptyTxConfig())
    res, err := ms.evmKeeper.CallEVMWithData(
        ctx, stateDB, moduleAddr, &contract, data, true, false, nil,
    )
    // ...
}

Example 3: Precompile Internal Call

// Before (v0.5.x)
func (p *StakingPrecompile) delegate(
    ctx sdk.Context,
    evm *vm.EVM,
    // ...
) ([]byte, error) {
    // ... delegate logic ...
    res, err := p.evmKeeper.CallEVM(
        ctx, delegationABI, from, contract, true, nil, "afterDelegate",
    )
    // ...
}

// After (v0.6.0)
func (p *StakingPrecompile) delegate(
    ctx sdk.Context,
    evm *vm.EVM,
    stateDB *statedb.StateDB, // typically passed from Run()
    // ...
) ([]byte, error) {
    // ... delegate logic ...
    res, err := p.evmKeeper.CallEVM(
        ctx, stateDB, delegationABI, from, contract, true, true, nil, "afterDelegate",
    )
    // ...
}