Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ linters:
- third_party$
- builtin$
- examples$
- x/evm/types/preinstall.go
issues:
max-same-issues: 50
formatters:
Expand All @@ -72,4 +73,4 @@ formatters:
paths:
- third_party$
- builtin$
- examples$
- examples$
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (evm) [#725](https://github.com/crypto-org-chain/ethermint/pull/725) feat(RPC): add authorizationList from eth_getTransactionByHash response for EIP-7702 transactions
* (evm) [#740](https://github.com/crypto-org-chain/ethermint/pull/740) fix: missing tx context during vm initialisation
* (evm) [#742](https://github.com/crypto-org-chain/ethermint/pull/742) fix: prevent nil pointer dereference in tracer hooks
* (evm) [#728](https://github.com/crypto-org-chain/ethermint/pull/728) feat: support preinstalls

## [v0.22.0] - 2025-08-12

Expand Down
8 changes: 5 additions & 3 deletions evmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,6 @@ var (
_ evmserver.AppWithPendingTxListener = (*EthermintApp)(nil)
)

type GenesisState map[string]json.RawMessage

// var _ server.Application (*EthermintApp)(nil)

// EthermintApp implements an extended ABCI application. It is an application
Expand Down Expand Up @@ -926,7 +924,11 @@ func (app *EthermintApp) InterfaceRegistry() types.InterfaceRegistry {

// DefaultGenesis returns a default genesis from the registered AppModuleBasic's.
func (app *EthermintApp) DefaultGenesis() map[string]json.RawMessage {
return app.BasicModuleManager.DefaultGenesis(app.appCodec)
genesis := app.BasicModuleManager.DefaultGenesis(app.appCodec)
evmGenState := NewEVMGenesisState()
genesis[evmtypes.ModuleName] = app.appCodec.MustMarshalJSON(evmGenState)

return genesis
}

func (app *EthermintApp) TxConfig() client.TxConfig {
Expand Down
27 changes: 27 additions & 0 deletions evmd/genesis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package evmd

import (
"encoding/json"

evmtypes "github.com/evmos/ethermint/x/evm/types"
)

// GenesisState of the blockchain is represented here as a map of raw json
// messages key'd by an identifier string.
// The identifier is used to determine which module genesis information belongs
// to so it may be appropriately routed during init chain.
// Within this application default genesis information is retrieved from
// the ModuleBasicManager which populates json from each BasicModule
// object provided to it during init.
type GenesisState map[string]json.RawMessage

// NewEVMGenesisState returns the default genesis state for the EVM module.
//
// NOTE: for the example chain implementation we need to set the default EVM denomination,
// enable ALL precompiles, and include default preinstalls.
func NewEVMGenesisState() *evmtypes.GenesisState {
evmGenState := evmtypes.DefaultGenesisState()
evmGenState.Preinstalls = evmtypes.DefaultPreinstalls

return evmGenState
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ require (
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ulikunitz/xz v0.5.14 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/zeebo/errs v1.4.0 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v0.14.3 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1701,8 +1701,8 @@ github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3C
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.14 h1:uv/0Bq533iFdnMHZdRBTOlaNMdb1+ZxXIlHDZHIHcvg=
github.com/ulikunitz/xz v0.5.14/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
Expand Down
5 changes: 5 additions & 0 deletions proto/ethermint/evm/v1/genesis.proto
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
syntax = "proto3";
package ethermint.evm.v1;

import "amino/amino.proto";
import "ethermint/evm/v1/params.proto";
import "ethermint/evm/v1/preinstall.proto";
import "ethermint/evm/v1/state.proto";
import "gogoproto/gogo.proto";

Expand All @@ -13,6 +15,9 @@ message GenesisState {
repeated GenesisAccount accounts = 1 [(gogoproto.nullable) = false];
// params defines all the parameters of the module.
Params params = 2 [(gogoproto.nullable) = false];
// preinstalls defines a set of predefined contracts
repeated Preinstall preinstalls = 3
[ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ];
}

// GenesisAccount defines an account to be initialized in the genesis state.
Expand Down
15 changes: 15 additions & 0 deletions proto/ethermint/evm/v1/preinstall.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
syntax = "proto3";
package ethermint.evm.v1;

option go_package = "github.com/evmos/ethermint/x/evm/types";

// Preinstall defines a contract that is preinstalled on-chain with a specific
// contract address and bytecode
message Preinstall {
// name of the preinstall contract
string name = 1;
// address in hex format of the preinstall contract
string address = 2;
// code in hex format for the preinstall contract
string code = 3;
}
22 changes: 22 additions & 0 deletions proto/ethermint/evm/v1/tx.proto
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
syntax = "proto3";
package ethermint.evm.v1;

import "amino/amino.proto";
import "cosmos/msg/v1/msg.proto";
import "cosmos_proto/cosmos.proto";
import "gogoproto/gogo.proto";
Expand All @@ -9,6 +10,7 @@ import "google/protobuf/any.proto";
import "ethermint/evm/v1/access_tuple.proto";
import "ethermint/evm/v1/log.proto";
import "ethermint/evm/v1/params.proto";
import "ethermint/evm/v1/preinstall.proto";
import "ethermint/evm/v1/set_code_authorization.proto";

option go_package = "github.com/evmos/ethermint/x/evm/types";
Expand All @@ -23,6 +25,10 @@ service Msg {
// UpdateParams defined a governance operation for updating the x/evm module parameters.
// The authority is hard-coded to the Cosmos SDK x/gov module account
rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse);

// UpdateParams defined a governance operation for updating the x/evm module parameters.
// The authority is hard-coded to the Cosmos SDK x/gov module account
rpc RegisterPreinstalls(MsgRegisterPreinstalls) returns (MsgRegisterPreinstallsResponse);
}

// MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.
Expand Down Expand Up @@ -225,3 +231,19 @@ message MsgUpdateParams {
// MsgUpdateParamsResponse defines the response structure for executing a
// MsgUpdateParams message.
message MsgUpdateParamsResponse {}

// MsgRegisterPreinstalls defines a Msg for creating preinstalls in evm state.
message MsgRegisterPreinstalls {
option (cosmos.msg.v1.signer) = "authority";

// authority is the address of the governance account.
string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];

// preinstalls defines the preinstalls to create.
repeated Preinstall preinstalls = 2
[ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ];
}

// MsgRegisterPreinstallsResponse defines the response structure for executing a
// MsgRegisterPreinstalls message.
message MsgRegisterPreinstallsResponse {}
4 changes: 4 additions & 0 deletions x/evm/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ func InitGenesis(
}
}

if err := k.AddPreinstalls(ctx, data.Preinstalls); err != nil {
panic(fmt.Errorf("error adding preinstalls: %s", err))
}

return []abci.ValidatorUpdate{}
}

Expand Down
1 change: 0 additions & 1 deletion x/evm/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,6 @@ func (k Keeper) Code(c context.Context, req *types.QueryCodeRequest) (*types.Que

address := common.HexToAddress(req.Address)
acct := k.GetAccount(ctx, address)

var code []byte
if acct != nil && acct.IsContract() {
code = k.GetCode(ctx, common.BytesToHash(acct.CodeHash))
Expand Down
67 changes: 67 additions & 0 deletions x/evm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package keeper
import (
"math/big"

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

errorsmod "cosmossdk.io/errors"
"cosmossdk.io/log"
storetypes "cosmossdk.io/store/types"
Expand Down Expand Up @@ -339,3 +341,68 @@ func (k Keeper) DeleteHeaderHash(ctx sdk.Context, height uint64) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.GetHeaderHashKey(height))
}

func (k *Keeper) AddPreinstalls(ctx sdk.Context, preinstalls []types.Preinstall) error {
for _, preinstall := range preinstalls {
address := common.HexToAddress(preinstall.Address)
accAddress := sdk.AccAddress(address.Bytes())

if len(preinstall.Code) == 0 {
return errorsmod.Wrapf(types.ErrInvalidPreinstall,
"preinstall %s, address %s has no code", preinstall.Name, preinstall.Address)
}

// check that the address does not conflict with the precompiles
cfg, err := k.EVMBlockConfig(ctx, k.ChainID())
if err != nil {
return err
}
for _, fn := range k.customContractFns {
c := fn(ctx, cfg.Rules)
if address == c.Address() {
return errorsmod.Wrapf(types.ErrInvalidPreinstall,
"preinstall %s, address %s already exists as a precompile", preinstall.Name, preinstall.Address)
}
}

codeHash := crypto.Keccak256Hash(common.FromHex(preinstall.Code))
codeHashBytes := codeHash.Bytes()
if types.IsEmptyCodeHash(codeHashBytes) {
k.Logger(ctx).Error("preinstall has empty code hash",
"preinstall address", preinstall.Address)
return errorsmod.Wrapf(types.ErrInvalidPreinstall,
"preinstall %s, address %s has empty code hash", preinstall.Name, preinstall.Address)
}

acct := k.accountKeeper.GetAccount(ctx, accAddress)
// check that the account is not already set
if acct != nil {
return errorsmod.Wrapf(types.ErrInvalidPreinstall,
"preinstall %s, address %s already has an account in account keeper", preinstall.Name, preinstall.Address)
}
// create account with the account keeper and set code hash
acct = k.accountKeeper.NewAccountWithAddress(ctx, accAddress)
if ethAcct, ok := acct.(ethermint.EthAccountI); ok {
if err := ethAcct.SetCodeHash(codeHash); err != nil {
return err
}
}
k.accountKeeper.SetAccount(ctx, acct)
k.SetCode(ctx, codeHashBytes, common.FromHex(preinstall.Code))

// We are not setting any storage for preinstalls, so we skip that step.
}
return nil
}

// GetCodeHash loads the code hash from the database for the given contract address.
func (k *Keeper) GetCodeHash(acct sdk.AccountI) common.Hash {
if ethAcct, ok := acct.(ethermint.EthAccountI); ok {
hash := ethAcct.GetCodeHash()
if len(hash.Bytes()) == 0 {
return common.BytesToHash(types.EmptyCodeHash)
}
return hash
}
return common.BytesToHash(types.EmptyCodeHash)
}
29 changes: 20 additions & 9 deletions x/evm/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,28 +104,29 @@ func (suite *KeeperTestSuite) TestBaseFee() {
func (suite *KeeperTestSuite) TestGetAccountStorage() {
testCases := []struct {
name string
malleate func()
expRes []int
malleate func() common.Address
}{
{
"Only one account that's not a contract (no storage)",
func() {},
[]int{0},
nil,
},
{
"Two accounts - one contract (with storage), one wallet",
func() {
func() common.Address {
supply := big.NewInt(100)
suite.DeployTestContract(suite.T(), suite.Address, supply, suite.enableFeemarket)
contractAddr := suite.DeployTestContract(suite.T(), suite.Address, supply, suite.enableFeemarket)
return contractAddr
},
[]int{2, 0},
},
}

for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest()
tc.malleate()
var contractAddr common.Address
if tc.malleate != nil {
contractAddr = tc.malleate()
}
i := 0
suite.App.AccountKeeper.IterateAccounts(suite.Ctx, func(account sdk.AccountI) bool {
ethAccount, ok := account.(ethermint.EthAccountI)
Expand All @@ -137,7 +138,17 @@ func (suite *KeeperTestSuite) TestGetAccountStorage() {
addr := ethAccount.EthAddress()
storage := suite.App.EvmKeeper.GetAccountStorage(suite.Ctx, addr)

suite.Require().Equal(tc.expRes[i], len(storage))
if addr == contractAddr {
s.Require().NotEqual(0, len(storage),
"expected account %d to have non-zero amount of storage slots, got %d",
i, len(storage),
)
} else {
s.Require().Len(storage, 0,
"expected account %d to have %d storage slots, got %d",
i, 0, len(storage),
)
}
i++
return false
})
Expand Down
19 changes: 19 additions & 0 deletions x/evm/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,22 @@ func (k *Keeper) UpdateParams(goCtx context.Context, req *types.MsgUpdateParams)

return &types.MsgUpdateParamsResponse{}, nil
}

// RegisterPreinstalls implements the gRPC MsgServer interface. When a RegisterPreinstalls
// proposal passes, it creates the preinstalls. The registration can only be
// performed if the requested authority is the Cosmos SDK governance module
// account.
func (k *Keeper) RegisterPreinstalls(goCtx context.Context, req *types.MsgRegisterPreinstalls) (*types.
MsgRegisterPreinstallsResponse, error,
) {
if k.authority.String() != req.Authority {
return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority, expected %s, got %s", k.authority.String(), req.Authority)
}

ctx := sdk.UnwrapSDKContext(goCtx)
if err := k.AddPreinstalls(ctx, req.Preinstalls); err != nil {
return nil, err
}

return &types.MsgRegisterPreinstallsResponse{}, nil
}
3 changes: 2 additions & 1 deletion x/evm/migrations/v4/types/params_v4.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion x/evm/simulation/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func RandomizedGenState(simState *module.SimulationState) {
)

params := types.NewParams(types.DefaultEVMDenom, false, true, true, types.DefaultChainConfig(), extraEIPs)
evmGenesis := types.NewGenesisState(params, []types.GenesisAccount{})
evmGenesis := types.NewGenesisState(params, []types.GenesisAccount{}, []types.Preinstall{})

bz, err := json.MarshalIndent(evmGenesis, "", " ")
if err != nil {
Expand Down
Loading
Loading