Skip to content

feat: upgrade cosmos/evm from v0.3.2 to v0.4.0#239

Open
AryaLanjewar3005 wants to merge 5 commits into
mainfrom
evm-upgrade-0.4.0
Open

feat: upgrade cosmos/evm from v0.3.2 to v0.4.0#239
AryaLanjewar3005 wants to merge 5 commits into
mainfrom
evm-upgrade-0.4.0

Conversation

@AryaLanjewar3005
Copy link
Copy Markdown
Collaborator

@AryaLanjewar3005 AryaLanjewar3005 commented May 18, 2026

Summary

All changes are in push-chain/ and were required by breaking API changes in evm/
introduced during the upgrade from cosmos/evm v0.3.2v0.4.0.


1. go.mod — Dependency updates

File: go.mod

Change

- github.com/cosmos/evm => github.com/pushchain/evm v1.0.0-rc1.0.20260506103806-1e0c52f48243
- github.com/ethereum/go-ethereum => github.com/cosmos/go-ethereum v1.15.11-cosmos-0
+ github.com/cosmos/evm => ../evm
+ github.com/ethereum/go-ethereum => github.com/cosmos/go-ethereum v0.0.0-20250806193535-2fc7571efa91

Why

go-ethereum version bump: The evm fork was upgraded to include go-ethereum 1.16 (upstream PR #315).
The replace directive was bumped from the old v1.15.11-cosmos-0 tag to the new timestamped commit
v0.0.0-20250806193535-2fc7571efa91. Using the old tag causes missing go.sum entries and type
incompatibilities with code that was written against the 1.16 API (e.g. vm.PrecompiledContractsPrague
which does not exist in 1.15).


2. app/precompiles.go — Full rewrite

File: app/precompiles.go

The entire file was rewritten. The old file had a flat function with hardcoded precompile constructors
using the v0.3.2 API. The new file adds an Optionals struct with address codecs and updates every
constructor call to match the v0.4.0 signatures.


2a. Evidence precompile removed

- evidencekeeper "cosmossdk.io/x/evidence/keeper"
- evidenceprecompile "github.com/cosmos/evm/precompiles/evidence"
  ...
- func NewAvailableStaticPrecompiles(
-     ...
-     evidenceKeeper evidencekeeper.Keeper,
-     ...
- ) map[common.Address]vm.PrecompiledContract {
  ...
-     evidencePrecompile, err := evidenceprecompile.NewPrecompile(evidenceKeeper, bankKeeper)
-     ...
-     precompiles[evidencePrecompile.Address()] = evidencePrecompile

Why (evm change): The evidence precompile (precompiles/evidence/) was entirely deleted from
cosmos/evm in upstream PR #305. The package no longer exists in v0.4.0, so any import of it
causes a compilation error. The parameter evidenceKeeper evidencekeeper.Keeper was dropped from
NewAvailableStaticPrecompiles as a result.


2b. Staking precompile — bankKeeper replaced with AddressCodec

- stakingPrecompile, err := stakingprecompile.NewPrecompile(stakingKeeper, bankKeeper)
+ stakingPrecompile, err := stakingprecompile.NewPrecompile(stakingKeeper, options.AddressCodec)

Why (evm change): The staking precompile constructor was refactored in v0.4.0 as part of the
address codec injection pattern. bankKeeper was removed from the constructor signature; the
precompile now takes an address.Codec for address encoding/decoding instead.


2c. Distribution precompile — bankKeeper removed, AddressCodec added, argument order changed

- distributionPrecompile, err := distprecompile.NewPrecompile(
-     distributionKeeper,
-     bankKeeper,
-     stakingKeeper,
-     evmKeeper,
- )
+ distributionPrecompile, err := distprecompile.NewPrecompile(
+     distributionKeeper,
+     stakingKeeper,
+     evmKeeper,
+     options.AddressCodec,
+ )

Why (evm change): The distribution precompile constructor was refactored. bankKeeper was removed
(it was not actually used in the distribution logic) and address.Codec was added as the final
argument for address encoding. The order of stakingKeeper and evmKeeper was also shifted.


2d. ICS-20 precompile — bankKeeper moved to first parameter

- ibcTransferPrecompile, err := ics20precompile.NewPrecompile(
-     stakingKeeper,
-     bankKeeper,
-     transferKeeper,
-     channelKeeper,
-     evmKeeper,
- )
+ ibcTransferPrecompile, err := ics20precompile.NewPrecompile(
+     bankKeeper,
+     stakingKeeper,
+     transferKeeper,
+     channelKeeper,
+     evmKeeper,
+ )

Why (evm change): The ICS-20 precompile constructor argument order changed in v0.4.0. bankKeeper
was moved from the second position to the first to align with the pattern used across other precompiles.
Calling with the old order compiles (both are keeper types) but produces a runtime panic when the
precompile tries to use the wrong keeper.


2e. Gov precompile — bankKeeper replaced with AddressCodec

- govPrecompile, err := govprecompile.NewPrecompile(govKeeper, bankKeeper, appCodec)
+ govPrecompile, err := govprecompile.NewPrecompile(govKeeper, appCodec, options.AddressCodec)

Why (evm change): The gov precompile constructor was refactored to remove bankKeeper and inject
an address.Codec instead. The argument order of appCodec and the new codec also changed.


2f. Slashing precompile — bankKeeper replaced with two addr codecs

- slashingPrecompile, err := slashingprecompile.NewPrecompile(slashingKeeper, bankKeeper)
+ slashingPrecompile, err := slashingprecompile.NewPrecompile(
+     slashingKeeper,
+     options.ValidatorAddrCodec,
+     options.ConsensusAddrCodec,
+ )

Why (evm change): The slashing precompile now requires separate validator and consensus address
codecs for correctly encoding/decoding validator and consensus addresses. bankKeeper was removed
entirely.


2g. Optionals pattern added

type Optionals struct {
    AddressCodec       address.Codec
    ValidatorAddrCodec address.Codec
    ConsensusAddrCodec address.Codec
}

func defaultOptionals() Optionals {
    return Optionals{
        AddressCodec:       addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()),
        ValidatorAddrCodec: addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()),
        ConsensusAddrCodec: addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()),
    }
}

Why (evm change): v0.4.0 introduced a functional options pattern for precompile constructors.
Address codecs are now passed in instead of being hardcoded inside each precompile, making the
precompiles chain-agnostic. The NewAvailableStaticPrecompiles function now accepts opts ...Option
variadic arguments and merges them over the defaults.


2h. vm.PrecompiledContractsBerlinvm.PrecompiledContractsPrague

- precompiles := maps.Clone(vm.PrecompiledContractsBerlin)
+ precompiles := maps.Clone(vm.PrecompiledContractsPrague)

Why (evm change): The go-ethereum 1.16 upgrade (upstream PR #315) added Prague EVM precompiles.
vm.PrecompiledContractsPrague is the new correct baseline and includes all precompiles up through
the Prague hard fork. Using the old PrecompiledContractsBerlin constant still compiles but results
in a stale precompile set that is missing newer EIP precompiles.


3. app/app.go — Evidence keeper removed from NewAvailableStaticPrecompiles call

File: app/app.go

Change

  corePrecompiles := NewAvailableStaticPrecompiles(
      *app.StakingKeeper,
      app.DistrKeeper,
      app.BankKeeper,
      app.Erc20Keeper,
      app.TransferKeeper,
      app.IBCKeeper.ChannelKeeper,
      app.EVMKeeper,
      app.GovKeeper,
      app.SlashingKeeper,
-     app.EvidenceKeeper,
      appCodec,
  )

Why (evm change): Follows directly from the evidence precompile removal (upstream PR #305).
The evidenceKeeper parameter was dropped from NewAvailableStaticPrecompiles, so the call site
in app.go must pass one fewer argument.


4. app/app.goSetClientCtx and RegisterPendingTxListener added

File: app/app.go

Change

New fields added to the ChainApp struct:

clientCtx          client.Context
pendingTxListeners []func(common.Hash)

New import:

"github.com/ethereum/go-ethereum/common"

New methods:

// SetClientCtx sets the client context on the app (required by evmserver.Application).
func (app *ChainApp) SetClientCtx(clientCtx client.Context) {
    app.clientCtx = clientCtx
}

// RegisterPendingTxListener registers a listener for pending EVM transactions (required by evmserver.Application).
func (app *ChainApp) RegisterPendingTxListener(listener func(common.Hash)) {
    app.pendingTxListeners = append(app.pendingTxListeners, listener)
}

Why (evm change): v0.4.0 introduced the evmserver.Application interface in evm/server/start.go:

type Application interface {
    servertypes.Application
    SetClientCtx(clientCtx client.Context)
    RegisterPendingTxListener(listener func(common.Hash))
}

The EVM server calls evmApp.SetClientCtx(clientCtx) during node startup to inject the Cosmos client
context (needed for JSON-RPC and pending tx streaming). RegisterPendingTxListener enables the EVM
mempool to notify the JSON-RPC WebSocket layer when a pending transaction arrives. ChainApp must
implement both methods to satisfy this interface, which is checked at compile time by
cosmosevmserver.NewDefaultStartOptions.


5. cmd/pchaind/commands.gonewApp return type and SDK wrapper

File: cmd/pchaind/commands.go

Change

newApp return type updated:

  func newApp(
      logger log.Logger,
      db dbm.DB,
      traceStore io.Writer,
      appOpts servertypes.AppOptions,
- ) servertypes.Application {
+ ) cosmosevmserver.Application {

New SDK wrapper added for pruning.Cmd and snapshot.Cmd:

// pruning.Cmd and snapshot.Cmd still expect servertypes.Application, so wrap newApp.
sdkAppCreator := func(l log.Logger, d dbm.DB, w io.Writer, ao servertypes.AppOptions) servertypes.Application {
    return newApp(l, d, w, ao)
}

rootCmd.AddCommand(
    ...
-   pruning.Cmd(newApp, app.DefaultNodeHome),
-   snapshot.Cmd(newApp),
+   pruning.Cmd(sdkAppCreator, app.DefaultNodeHome),
+   snapshot.Cmd(sdkAppCreator),
)

Why (evm change): cosmosevmserver.NewDefaultStartOptions was changed in v0.4.0 to accept
evmserver.AppCreator (a function returning evmserver.Application) instead of
servertypes.AppCreator. Since ChainApp now implements evmserver.Application (via the new
methods above), newApp can return that richer type. However, pruning.Cmd and snapshot.Cmd
in the Cosmos SDK still expect servertypes.Application, so a thin wrapper (sdkAppCreator) is
required to satisfy both call sites without changing the Cosmos SDK types.


6. x/uexecutor/types/expected_keepers.goCallEVM interface updated

File: x/uexecutor/types/expected_keepers.go

Change

  type EVMKeeper interface {
      CallEVM(
          ctx sdk.Context,
          abi abi.ABI,
          from, contract common.Address,
          commit bool,
+         gasCap *big.Int,
          method string,
          args ...interface{},
      ) (*types.MsgEthereumTxResponse, error)

Why (evm change): The CallEVM function in x/vm/keeper/call_evm.go gained a new
gasCap *big.Int parameter in v0.4.0:

// Before v0.4.0
func (k Keeper) CallEVM(ctx, abi, from, contract, commit, method, args...) (*MsgEthereumTxResponse, error)

// After v0.4.0
func (k Keeper) CallEVM(ctx, abi, from, contract, commit, gasCap *big.Int, method, args...) (*MsgEthereumTxResponse, error)

This allows callers to impose a custom gas cap on EVM calls. Pass nil to use the default
(config.DefaultGasCap). The interface in expected_keepers.go must mirror the real signature
exactly, otherwise the EVMKeeper mock used in tests and the keeper wiring in keeper.go produce
a compile error.


7. x/uexecutor/keeper/evm.go — All CallEVM call sites updated

File: x/uexecutor/keeper/evm.go

Change

nil added as the gasCap argument (between commit bool and method string) at every
CallEVM call site. Example:

- receipt, err := k.evmKeeper.CallEVM(ctx, abi, from, factoryAddr, false, "getUEAForOrigin", ...)
+ receipt, err := k.evmKeeper.CallEVM(ctx, abi, from, factoryAddr, false, nil, "getUEAForOrigin", ...)

Functions updated:

  • CallFactoryToGetUEAAddressForOrigingetUEAForOrigin
  • CallFactoryGetOriginForUEAgetOriginForUEA
  • CallUEADomainSeparatordomainSeparator
  • GetGasPriceByChaingasPriceByChainNamespace
  • GetL1GasFeeByChainl1GasFeeByChainNamespace
  • GetTssFundMigrationGasLimitByChaintssFundMigrationGasLimitByChainNamespace
  • GetUniversalCoreQuoterAddressuniswapV3Quoter
  • GetUniversalCoreWPCAddressWPC
  • GetDefaultFeeTierForTokendefaultFeeTier
  • GetSwapQuotequoteExactInputSingle

Why (evm change): Directly follows from the CallEVM signature change described in item 6.
All existing callers pass nil to preserve the previous default gas cap behaviour.


8. x/uexecutor/keeper/gas_fee.goCallEVM call site updated

File: x/uexecutor/keeper/gas_fee.go

Change

- receipt, err := k.evmKeeper.CallEVM(ctx, ucABI, ueModuleAccAddress, handlerAddr, false,
-     "getOutboundTxGasAndFees", prc20, gasLimitWithBaseLimit)
+ receipt, err := k.evmKeeper.CallEVM(ctx, ucABI, ueModuleAccAddress, handlerAddr, false, nil,
+     "getOutboundTxGasAndFees", prc20, gasLimitWithBaseLimit)

Why (evm change): Same CallEVM signature change as item 6. This call site is in a separate
file (gas_fee.go) from the rest of the evm.go call sites; updated independently.


9. test/utils/contracts_setup.go — All CallEVM call sites updated

File: test/utils/contracts_setup.go

Change

nil added as the gasCap argument at all test-utility call sites. Example:

- ownerResult, err := app.EVMKeeper.CallEVM(ctx, factoryABI, owner, factoryAddr, true, "owner")
+ ownerResult, err := app.EVMKeeper.CallEVM(ctx, factoryABI, owner, factoryAddr, true, nil, "owner")

Call sites updated:

Function Method called
setupHandlerContract initialize
setupFactoryContract owner (×2), initialize, setUEAProxyImplementation
setupPrc20Contract updateHandlerContract
registerEVMChainAndUEA registerNewChain, registerUEA, getUEA

Why (evm change): Same CallEVM signature change as item 6. Test utilities call app.EVMKeeper
directly (not through the EVMKeeper interface), so they must match the concrete keeper signature.


Bonus: evm-side change made to accommodate push-chain

evm/mempool/check_tx.go moved to evm/mempool/checktx/check_tx.go

What changed in evm: NewCheckTxHandler was added in v0.4.0, relying on types.CheckTxHandler
and types.RunTx from cosmos-sdk v0.53.4.

Problem: push-chain pins cosmos-sdk to v0.50.10 (via a replace directive). When push-chain
compiled the evm/mempool package (transitively via evm/ante), the build failed because
types.CheckTxHandler does not exist in cosmos-sdk v0.50.10.

Fix: check_tx.go was moved from the mempool package into a new mempool/checktx
sub-package. This means importing github.com/cosmos/evm/mempool (used by
ante/evm/09_increment_sequence.go for ErrNonceGap) no longer transitively compiles
check_tx.go, which requires the newer SDK types.

evmd/app.go (the only caller of NewCheckTxHandler) was updated to import from the new
sub-package path:

- evmmempool "github.com/cosmos/evm/mempool"
+ evmmempool "github.com/cosmos/evm/mempool"
+ evmmempoolchecktx "github.com/cosmos/evm/mempool/checktx"
  ...
- checkTxHandler := evmmempool.NewCheckTxHandler(evmMempool)
+ checkTxHandler := evmmempoolchecktx.NewCheckTxHandler(evmMempool)

This change lives in evm/, not in push-chain/, but it was made specifically to unblock the
push-chain build.


Test File changes

1. x/uexecutor/mocks/mock_evmkeeper.go — Updated CallEVM mock signature

Failing test: Build failure in x/uexecutor/keeper — mock no longer matched the EVMKeeper interface.

Root cause: v0.4.0 added a gasCap *big.Int parameter to CallEVM between commit bool and method string. The hand-maintained mock was still using the old 7-parameter signature.

Fix: Added gasCap *big.Int to both the mock method and the mock recorder:

// Before
func (m *MockEVMKeeper) CallEVM(ctx types.Context, abi abi.ABI, from, contract common.Address, commit bool, method string, args ...interface{})
varargs := []interface{}{ctx, abi, from, contract, commit, method}
func (mr *MockEVMKeeperMockRecorder) CallEVM(ctx, abi, from, contract, commit, method interface{}, args ...interface{})
varargs := append([]interface{}{ctx, abi, from, contract, commit, method}, args...)

// After
func (m *MockEVMKeeper) CallEVM(ctx types.Context, abi abi.ABI, from, contract common.Address, commit bool, gasCap *big.Int, method string, args ...interface{})
varargs := []interface{}{ctx, abi, from, contract, commit, gasCap, method}
func (mr *MockEVMKeeperMockRecorder) CallEVM(ctx, abi, from, contract, commit, gasCap, method interface{}, args ...interface{})
varargs := append([]interface{}{ctx, abi, from, contract, commit, gasCap, method}, args...)

2. x/uexecutor/keeper/msg_server_test.go — Updated CallEVM mock expectations

Failing test: TestMsgServer_ExecutePayload/Fail:_CallFactoryToComputeUEAAddress

Root cause: Two EXPECT().CallEVM(...) calls used 7 gomock.Any() matchers matching the old signature. After the mock was updated with the new 8-parameter signature, the expectations had a mismatch ("Got: 8, want: 7").

Fix: Added one gomock.Any() to both expectations (the gasCap position):

// Before (2 occurrences)
f.mockEVMKeeper.EXPECT().CallEVM(
    gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
    gomock.Any(), gomock.Any(), gomock.Any(),
).Return(...)

// After
f.mockEVMKeeper.EXPECT().CallEVM(
    gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
    gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).Return(...)

3. Integration test files — nil gasCap in CallEVM calls

Failing tests: Build failures across all test/integration/ packages.

Root cause: Same v0.4.0 CallEVM signature change. All call sites passed arguments positionally without the new gasCap *big.Int parameter, causing compile errors.

Files fixed and pattern applied:

File Occurrences
test/integration/uexecutor/inbound_solana_test.go 3
test/integration/uexecutor/vote_chain_meta_test.go 3
test/integration/uexecutor/inbound_synthetic_bridge_test.go 4
test/integration/uexecutor/inbound_cea_gas_and_payload_test.go multiple
test/integration/uexecutor/inbound_cea_payload_test.go multiple
test/integration/uexecutor/inbound_cea_smart_contract_test.go multiple
test/integration/utss/fund_migration_test.go 3

Pattern (both single-line and multi-line forms):

// Before
evmKeeper.CallEVM(ctx, prc20ABI, addr, contract, false, "balanceOf", recipient)

// After
evmKeeper.CallEVM(ctx, prc20ABI, addr, contract, false, nil, "balanceOf", recipient)

// Multi-line before
evmKeeper.CallEVM(
    ctx, prc20ABI, addr, contract,
    false,       // commit
    "balanceOf", recipient,
)

// Multi-line after
evmKeeper.CallEVM(
    ctx, prc20ABI, addr, contract,
    false,       // commit
    nil,         // gasCap (nil = use default)
    "balanceOf", recipient,
)

Pre-existing Failure (Not Fixed)

TestSimulateBSC_FetchVaultFromGateway in universalClient/chains/evm/tx_builder_test.go

This test makes a live RPC call to BSC mainnet (vault() method on a gateway contract). It fails due to a network/contract revert, not related to the upgrade. The test predates this upgrade work and was failing before it began.


References

Changes

Testing

  • go test ./...

Checklist

  • Ready for review
  • Docs updated (if applicable)
  • Env vars updated (if applicable)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant