Skip to content

Evm

Abstract

This document defines the specification of the Ethereum Virtual Machine (EVM) as a Cosmos SDK module.

Since the introduction of Ethereum in 2015, the ability to control digital assets through [smart contracts] has attracted a large number of developers to build decentralized applications on the Ethereum Virtual Machine (EVM). This community continues to create a wide range of tools and introduce standards, which further increases the adoption rate of EVM-compatible technologies.

However, the growth of EVM-based chains such as Ethereum has revealed some scalability challenges, often referred to as the decentralization, security, and scalability trilemma. Developers are frustrated by high gas fees, slow transaction speeds and throughput, and chain-specific governance that undergoes only slow changes due to their widely deployed applications. A solution is needed to allay these concerns for developers building applications in the familiar EVM environment.

The x/evm module provides this EVM familiarity on scalable, high-throughput proof-of-stake blockchains. It is built as a Cosmos SDK module, allowing deployment of smart contracts, interaction with the EVM state machine (state transitions), and use of EVM tools . It is available for Cosmos application-specific blockchains through Tendermint Core high transaction throughput, fast transaction finality and IBC to alleviate the above problems.

content

  1. Concepts
  2. state
  3. state transitions
  4. Transactions
  5. ABCI
  6. hooks
  7. Events
  8. Parameters
  9. client

Module architecture

Note:: If you are not familiar with the overall module structure of SDK modules, please read this document first as a prerequisite .

evm/
β”œβ”€β”€ client
β”‚ └── cli
β”‚ β”œβ”€β”€ query.go # Module CLI query command
β”‚ └── tx.go # CLI transaction command of the module
β”œβ”€β”€keeper
β”‚ β”œβ”€β”€ abci.go # ABCI start and end block logic
β”‚ β”œβ”€β”€ keeper.go # Process the business logic of the module.
β”‚ β”œβ”€β”€ params.go # parameter getter and setter
β”‚ β”œβ”€β”€ queryer.go # status query function
β”‚ └── statedb.go # Functions from types/statedb passed into sdk.Context
β”œβ”€β”€ types
β”‚ β”œβ”€β”€ chain_config.go
β”‚ β”œβ”€β”€ codec.go # Encoding type registration
β”‚ β”œβ”€β”€ errors.go # Module-specific errors
β”‚ β”œβ”€β”€ events.go # Events exposed to Tendermint PubSub/Websocket
β”‚ β”œβ”€β”€ genesis.go # Genesis state of the module
β”‚ β”œβ”€β”€ journal.go # Ethereum state transition transactions
β”‚ β”œβ”€β”€ keys.go # Store keys and utility functions
β”‚ β”œβ”€β”€ logs.go # Persistent Ethereum tx login status type after chain upgrade
β”‚ β”œβ”€β”€ msg.go #EVM module transaction message
β”‚ β”œβ”€β”€ params.go # Governance parameters can be used to change suggested custom module parameters
β”‚ β”œβ”€β”€ state_object.go # EVM state object
β”‚ β”œβ”€β”€ statedb.go # Implementation of StateDb interface
β”‚ β”œβ”€β”€ storage.go # Use arrays to implement Ethereum state storage mapping to prevent non-determinism
β”‚ └── tx_data.go # Ethereum transaction data type
β”œβ”€β”€ genesis.go # ABCI InitGenesis and ExportGenesis functions
β”œβ”€β”€ handler.go # message routing
└── module.go # Module settings for the module manager

concept

EVM

The Ethereum Virtual Machine (EVM) is a computing engine that can be thought of as a single entity maintained by thousands of connected computers (nodes) running Ethereum clients. As a virtual machine (VM), the EVM is responsible for computing state changes deterministically, regardless of its environment (hardware and operating system). This means that given the same starting state and transaction (tx), each node must achieve the exact same result.

The EVM is considered part of the Ethereum protocol that handles deployment and execution of smart contracts.

To clearly distinguish between:

  • The Ethereum protocol describes a blockchain in which all Ethereum accounts and smart contracts exist. It has only one canonical state (a data structure that holds all accounts) in any given block in the chain.
  • However, the EVM is a state machine, which defines the rules for computing a new valid state from one block to another. It is an isolated runtime, which means that code running inside the EVM cannot access the network, file system, or other processes (not external APIs).

The x/evm module implements the EVM as a Cosmos SDK module. It allows users to interact with the EVM by submitting Ethereum transactions and executing the contained messages on a given state to induce state transitions.

state

The Ethereum state is a data structure, implemented as a Merkle Patricia Tree, that keeps all accounts on-chain. The EVM makes changes to this data structure, resulting in a new state with a different state root. Therefore, Ethereum can be viewed as a state chain that transitions from one state to another by executing transactions in blocks using the EVM. A new txs block can be described by its block header (parent hash, block number, timestamp, nonce, receipt...).

account

There are two types of accounts that can be stored in the state for a given address:

  • Externally Owned Account (EOA): Has nonce (tx counter) and balance
  • Smart Contract: with nonce, balance, (immutable) code hash, storage root (another Merkle Patricia Trie)

A smart contract is like a regular account on the blockchain, it also stores executable code in an Ethereum-specific binary format called EVM bytecode. They are usually written in an Ethereum high-level language, such as Solidity, which is compiled to EVM bytecode and deployed on the blockchain by submitting transactions using the Ethereum client.

structure

The EVM operates as a stack-based machine. Its main architectural components include:

  • Virtual ROM: Contract code is pulled into this read-only memory when processing txs
  • Machine state (volatile): changes as the EVM runs and is cleared after processing each tx
    • Program counter (PC)
    • Gas​​: track how much gas is used
      • stack and memory: computing state changes
  • Access account storage (persistent)

State transition of smart contract

Typically, smart contracts expose a public ABI, which is a list of supported ways users can interact with the contract. To interact with a contract and invoke a state transition, a user submits a transaction carrying an arbitrary amount of gas and a data payload formatted according to the ABI, specifying the interaction type and any other parameters. When a tx is received, the EVM executes the EVM bytecode of the smart contract using the tx payload.

Execute EVM bytecode

A contract's EVM bytecode consists of basic operations (addition, multiplication, store, etc.), called opcodes. Each Opcode execution requires gas paid for in tx. Therefore, the EVM is considered quasi-Turing-complete because it allows arbitrary computations, but the amount of computation during contract execution is limited to the amount of gas provided in tx.

The gas cost of each opcode reflects the cost of running these operations on actual computer hardware (e.g. ADD = 3gas and SSTORE = 100gas `). To calculate the gas consumption of a transaction, multiply the gas cost by the gas price, which may vary based on the demand of the network at the time. If the network is heavily loaded, you may need to pay a higher gas price to execute your transaction. If the gas limit is reached (gas exception), no changes will be made to the Ethereum state unless the sender's nonce increases and their balance decreases to pay for wasting EVM time.

Smart contracts can also call other smart contracts. Every call to a new contract creates a new EVM instance (including new stack and memory). Each invocation passes the sandbox state to the next EVM. If the gas runs out, all state changes will be discarded. Otherwise, they will be preserved.

For further reading, see:

Daodst implemented as Geth

Daodst includes an implementation of the Ethereum Protocol in Golang (Geth) as a Cosmos SDK module. Geth includes an implementation of the EVM to compute state transitions. Check out the go-ethereum source code to see how the EVM opcodes are implemented. Just as Geth can run as an Ethereum node, Daodst can also run as a node to compute state transitions of the EVM. Daodst supports Geth's standard for compatibility with Web3 and EVM.

JSON-RPC

JSON-RPC is a stateless, lightweight remote procedure call (RPC) protocol. The specification mainly defines several data structures and their processing rules. It's transport-agnostic, as the concepts can be used in the same process, over sockets, over HTTP, or in many different messaging environments. It uses JSON (RFC 4627) as the data format.

** JSON-RPC example: eth_call **

The JSON-RPC method allows you to execute messages against contracts. Typically, you need to send a transaction to a Geth node to include it in the mempool, then the nodes propagate to each other, and eventually the transaction is included in a block and executed. However, eth_call allows you to send data to a contract and see what happens without committing the transaction.

In the Geth implementation, the call endpoint roughly goes through the following steps:

  1. The eth_call request is converted to call the func (s *PublicBlockchainAPI) Call() function using the eth namespace
  2. Call() is given the transaction parameters, the block to call and an optional Parameters to modify the state to call. Then it calls DoCall().
  3. DoCall() Convert arguments to ethtypes.message, instantiate the EVM and apply the message using core.ApplyMessage
  4. ApplyMessage() Call state transition TransitionDb()
  5. TransitionDb() Create() is a new contract or Call() is a contract
  6. evm.Call() Run the interpreter evm.interpreter.Run() to execute the message. If the execution fails, the state will revert to the snapshot taken before execution and consume gas.
  7. Run() Execute a loop to execute opcodes.

Status DB

The StateDB interface from go-ethereum represents the EVM database for full state queries. EVM state transitions are enabled by this interface, implemented by Keeper in the x/evm module. The implementation of this interface makes Daodst EVM compatible.

Consensus Engine

Applications using the x/evm module interact with the Tendermint core consensus engine through the Application Blockchain Interface (ABCI). Together, the application and Tendermint Core form a program that runs a full blockchain and combines business logic with decentralized data storage.

Ethereum transactions submitted to the x/evm module participate in this consensus process before executing and changing application state. We encourage understanding the basics of the Tendermint consensus engine in order to understand state transitions in detail.

Transaction Record

In each x/evm transaction, the result contains the Ethereum log from the state machine execution, which is used by the JSON-RPC Web3 server for filter queries and processing EVM hooks.

The tx log is stored in temporary storage during tx execution and then emitted via cosmos events after the transaction. They can be queried via gRPC and JSON-RPC.

Block Bloom

bloom is the bloom filter value (in bytes) per block available for filter queries. Block bloom values are stored in transient storage and then emitted via cosmos events during EndBlock processing. They can be queried via gRPC and JSON-RPC.

:::hint

πŸ‘‰ NOTE: Since they are not stored in state, transaction logs and block blooms are not persisted after upgrades. Users must use an archive node after upgrading to get legacy chain events. :::

state

This section provides an overview of objects stored in the state of the x/evm module, functionality derived from the go-ethereum StateDB interface, and its implementation via Keeper and state implementation at genesis.

State object

The x/evm module maintains objects in the following states:

state

Description Key Value Store
Code Smart contract bytecode []byte{1} + []byte(address) []byte{code} KV
Storage Smart contract storage []byte{2} + [32]byte{key} [32]byte(value) KV
Block Bloom Block bloom filter, used to accumulate the bloom filter of the current block and send to the end blocker event. []byte{1} + []byte(tx.Hash) protobuf([]Log) Transient
Tx Index The index of the current transaction in the current block. []byte{2} BigEndian(uint64) Transient
Log Size The number of logs emitted so far in the current block. Log index used to determine subsequent logs. []byte{3} BigEndian(uint64) Transient
Gas Used The amount of gas used by the ethereum message of the current cosmos-sdk tx, which is required when the cosmos-sdk tx contains multiple ethereum messages. []byte{4} BigEndian(uint64) Transient

Status DB

The StateDB interface is implemented by StateDB in the x/evm/statedb module, which represents an EVM database for complete state queries of contracts and accounts. In the Ethereum protocol, StateDB is used to store anything in the IAVL tree and is responsible for caching and storing nested states.

// github.com/ethereum/go-ethereum/core/vm/interface.go
type StateDB interface {
 CreateAccount(common.Address)

 SubBalance(common.Address, *big.Int)
 AddBalance(common.Address, *big.Int)
 GetBalance(common.Address) *big.Int

 GetNonce(common.Address) uint64
 SetNonce(common.Address, uint64)

 GetCodeHash(common.Address) common.Hash
 GetCode(common.Address) []byte
 SetCode(common.Address, []byte)
 GetCodeSize(common.Address) int

 AddRefund(uint64)
 SubRefund(uint64)
 GetRefund() uint64

 GetCommittedState(common.Address, common.Hash) common.Hash
 GetState(common.Address, common.Hash) common.Hash
 SetState(common.Address, common.Hash, common.Hash)

 Suicide(common.Address) bool
 HasSuicided(common.Address) bool

 // Exist reports whether the given account exists in state.
 // Notably this should also return true for suicided accounts.
 Exist(common.Address) bool
 // Empty returns whether the given account is empty. Empty
 // is defined according to EIP161 (balance = nonce = code = 0).
 Empty(common.Address) bool

 PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
 AddressInAccessList(addr common.Address) bool
 SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
 // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
 // even if the feature/fork is not active yet
 AddAddressToAccessList(addr common.Address)
 // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
 // even if the feature/fork is not active yet
 AddSlotToAccessList(addr common.Address, slot common.Hash)

 RevertToSnapshot(int)
 Snapshot() int

 AddLog(*types.Log)
 AddPreimage(common.Hash, []byte)

 ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error
}

The StateDB in the x/evm provides the following functionalities:

CRUD for Ethereum accounts

You can create an EthAccount instance from a provided address and use createAccount() to set the value to be stored on the AccountKeeper. If an account with the given address already exists, this function also resets any pre-existing code and storage associated with that address.

An account's currency balance can be managed through BankKeeper, which can be read using GetBalance() and updated using AddBalance() and SubBalance().

  • GetBalance() returns the EVM currency balance of the provided address. The currency name is taken from the module parameters.
  • AddBalance() adds the given amount to the address currency balance by minting new currency and transferring it to the address. The currency name is taken from the module parameters.
  • SubBalance() subtracts the given amount from the address balance by transferring currency to escrow accounts and then destroying them. The currency name is taken from the module parameters. This function will perform a no-op if the amount is negative or the user does not have enough funds to transfer.

The nonce (or transaction sequence) can be obtained from the account Sequence by the authentication module AccountKeeper.

  • GetNonce() retrieves the account with the given address and returns the tx sequence (i.e. the nonce). If the account cannot be found, the function will perform a no-op.
  • SetNonce() sets the given nonce as sequence of address accounts. If the account does not exist, a new account will be created from this address.

Smart contract bytecode containing arbitrary contract logic is stored on EVMKeeper and can be queried using GetCodeHash(), GetCode() and GetCodeSize(), and updated using SetCode().

  • GetCodeHash() gets an account from storage and returns its code hash. Returns an empty code hash if the account does not exist or is not of type EthAccount.
  • GetCode() returns the code byte array associated with the given address. This function returns nil if the code hash from the account is empty.
  • SetCode() stores the code byte array to the application KVStore and sets the code hash to the given account. If the code is empty, it will be removed from storage.
  • GetCodeSize() returns the size of the contract code associated with this object, or zero if none.

The refunded gas needs to be tracked and stored in a separate variable so that it can be subtracted/added to the gas usage value after the EVM execution is complete. The refund value is cleared at the end of each transaction and each block.

  • AddRefund() adds the given amount of gas to the refund value in memory.
  • SubRefund() subtracts the given amount of gas from the refund value in memory. This function will panic if the gas amount is greater than the current refund.
  • GetRefund() returns the amount of gas available for return after tx execution completes. The value resets to 0 on every transaction.

State is stored on EVMKeeper. Can be queried with GetCommittedState(), GetState() and updated with SetState().

  • GetCommittedState() returns the value set in storage for the given key hash. If the key is not registered, this function returns an empty hash.
  • GetState() returns the in-memory dirty state for the given key hash, If not present, the submitted value is loaded from the KVStore.
  • SetState() sets the given hash (key, value) as state. This function removes the key from the state if the value hash is empty, The new value is first kept dirty and finally committed to KVStore.

Accounts can also be set to commit suicide. When the contract commits suicide, the account is marked as suicide, and when the code is committed, the storage and account are deleted (starting from the next block).

  • Suicide() marks the given account as having committed suicide and clears the account balance in EVM tokens.
  • HasSuicided() queries the flags in memory to check if the account has been marked as suicidal in the current transaction. Suicided accounts will return non-zero during queries and be "cleared" after the block is committed.

To check if an account exists, use Exist() and Empty().

  • Exist() returns true if the given account exists in storage or has been marked as suicidal.
  • Empty() returns true if the address satisfies the following conditions:
    • nonce is 0
    • Balance of evm currency is 0
    • account code hash is empty

EIP2930 functionality

Supports transaction types that contain an access list, addresses and storage key lists that the transaction plans to access. Access list state is kept in memory and discarded after transaction commit.

  • PrepareAccessList() handles the preparation steps for performing state transitions on EIP-2929 and EIP-2930. This method should only be called if Yolov3/Berlin/2929+2930 applies to the current number.
    • Add sender to access list (EIP-2929)
    • Add destination to access list (EIP-2929)
    • Add precompile to access list (EIP-2929)
    • Add optional tx access list content (EIP-2930)
  • AddressInAccessList() returns true if the address is registered.
  • SlotInAccessList() checks if address and slot are registered.
  • AddAddressToAccessList() adds the given address to the access list. If the address is already in the access list, this function performs a no-op.
  • AddSlotToAccessList() adds the given (address, slot) to the access list. This function performs a no-op if the address and slot are already in the access list.

Snapshot state and restore functions

The EVM uses state restoration exceptions to handle errors. Such an exception will undo all changes made to the state in the current call (and all its sub-calls), and the caller can handle the error and not propagate. You can use Snapshot() to identify the current state by revision, and RevertToSnapshot() to revert the state to a given revision to support this feature. - Snapshot() creates a new snapshot and returns the identifier. - RevertToSnapshot(rev) undoes all modifications up to the snapshot identified by rev.

Daodst adapted the to support this, it uses a journal list in order to record For all the state modification operations done so far, the snapshot consists of a unique id and index in the log list, to revert to a snapshot it just undoes the log logs after the snapshot index in reverse order.

Ethereum transaction log

With AddLog(), you can append a given Ethereum Log to the list of logs associated with the transaction hash saved in the current state. The function also fills in the tx hash, block hash, tx index and log index fields before setting the log to be stored.

Keeper

The EVM module Keeper grants access to the state of the EVM module and implements the statedb.Keeper interface to support StateDB implementations. The Keeper contains a storage key that allows the DB to write to a concrete subtree of the multistore that can only be accessed by the EVM module. Instead of using trie and database for query and persistence (StateDB implementation), Daodst uses Cosmos' KVStore (key-value store) and Cosmos SDK's Keeper to facilitate state transitions.

To support interface functionality, it imports 4 modules Keepers:

  • auth: CRUD account
  • bank: CRUD for accounting (supply) and balances
  • staking: query historical headers
  • fee market: handle EIP1559 base fee for DynamicFeeTx After activating the London hard fork on the ChainConfig parameter
type Keeper struct {
 // Protobuf codec
 cdc codec.BinaryCodec
 // Store key required for the EVM Prefix KVStore. It is required by:
 // - storing account's Storage State
 // - storing account's Code
 // - storing Bloom filters by block height. Needed for the Web3 API.
 // For the full list, check the module specification
 storeKey sdk.StoreKey

 // key to access the transient store, which is reset on every block during Commit
 transientKey sdk.StoreKey

 // module specific parameter space that can be configured through governance
 paramSpace paramtypes.Subspace
 // access to account state
 accountKeeper types.AccountKeeper
 // update balance and accounting operations with coins
 bankKeeper types.BankKeeper
 // access historical headers for EVM state transition execution
 stakingKeeper types.StakingKeeper
 // fetch EIP1559 base fee and parameters
 feeMarketKeeper types.FeeMarketKeeper

 // chain ID number obtained from the context's chain id
 eip155ChainID *big.Int

 // Tracer used to collect execution traces from the EVM transaction execution
 tracer string
 // trace EVM state transition execution. This value is obtained from the `--trace` flag.
 // For more info check https://geth.ethereum.org/docs/dapp/tracing
 debug bool

 // EVM Hooks for tx post-processing
 hooks types.EvmHooks
}

Genesis state

The x/evm module GenesisState defines the state required to initialize a chain from a previously exported height. It contains GenesisAccounts and module parameters

type GenesisState struct {
   // accounts is an array containing the ethereum genesis accounts.
   Accounts []GenesisAccount `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts"`
   // params defines all the parameters of the module.
   Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"`
}

Genesis account

The GenesisAccount type corresponds to an adaptation of the Ethereum GenesisAccount type. It defines an account that is initialized in the genesis state.

Its main difference is that the one on Daodst uses a custom Storage type that uses slices instead of evm State's map (due to non-determinism), and it does not contain a private key field.

It is also important to note that since the auth module on the Cosmos SDK manages account state, the Address field must correspond to an existing EthAccount (i.e. AccountKeeper) stored in the auth module Keeper.

The address uses EIP55 hexadecimal[format] on genesis.json.

type GenesisAccount struct {
   // address defines an ethereum hex formatted address of an account
   Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
   // code defines the hex bytes of the account code.
   Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"`
   // storage defines the set of state key values for the account.
   Storage Storage `protobuf:"bytes,3,rep,name=storage,proto3,castrepeated=Storage" json:"storage"`
}

state transition

The x/evm module allows users to submit Ethereum transactions (Tx) and execute the messages they contain to induce a state transition on a given state.

Users submit transactions on the client side to broadcast them to the network. When a transaction is included in a block during the consensus process, it is executed server-side. We strongly recommend that you learn the basics of the Tendermint consensus engine to learn more about state transitions.

client

:::hint πŸ‘‰ This is based on eth_sendTransaction JSON-RPC :::

  1. A user submits a transaction via one of the available JSON-RPC endpoints using an Ethereum-compatible client or wallet (e.g. Metamask, WalletConnect, Ledger, etc.):

    • eth (public) namespace: eth_sendTransaction and eth_sendRawTransaction
    • Personal (private) namespace: personal_sendTransaction
  2. Create a MsgEthereumTx instance after filling the RPC transaction with SetTxDefaults to fill the missing tx parameter with default values

  3. Use ValidateBasic() to validate the Tx field (stateless)
  4. Tx is signed using the key associated with the sender address and the latest Ethereum hard fork (London, Berlin, etc.) from ChainConfig
  5. Tx is constructed from the msg field using the Cosmos Config builder
  6. Tx in synchronous mode broadcasting Make sure to wait for the CheckTx execution response. The application verifies the transaction using CheckTx() before adding to the consensus engine's mempool.
  7. The JSON-RPC user receives a response with the RLP hash of the transaction field. This hash is different from the default hash used by SDK transactions, which computes a sha256 hash of the transaction bytes.

Server

Once a block (containing "Tx") is committed during consensus, it is applied to the application in a series of ABCI messages on the server side.

Each Tx is handled by the application by calling RunTx. After stateless verification of each sdk.Msg in Tx, AnteHandler confirms whether Tx is an Ethereum transaction or an SDK transaction. As an Ethereum transaction, it contains messages, which are then processed by the x/evm module to update the state of the application.

Preprocessor

anteHandler runs for every transaction. It checks whether Tx is an Ethereum transaction and routes it to the internal ante handler. Here, Tx is handled using EthereumTx extension options to handle them differently than normal Cosmos SDK transactions. antehandler runs through a list of options and its AnteHandle function for each Tx:

  • EthSetUpContextDecorator() adapted from cosmos-sdk's SetUpContextDecorator which ignores gas consumption by setting the gas meter to infinite
  • EthValidateBasicDecorator(evmKeeper) validates fields of Ethereum type Cosmos Tx messages
  • EthSigVerificationDecorator(evmKeeper) verifies that the registered chain ID is the same as on the message, and that the signer address matches that defined on the message. RecheckTx won't skip it because it sets the From address, which is essential for other ante handlers to work. A failure of RecheckTx will prevent the tx from being included into the block, especially when CheckTx is successful, in which case the user will not see the error message.
    • EthAccountVerificationDecorator(ak, bankKeeper, evmKeeper) Will verify that the sender balance is greater than the total transaction cost. If the account does not exist, i.e. cannot be found in the store, the account will be set into the store. This AnteHandler decorator will fail if:
      • Any message is not MsgEthereumTx
      • sender address is empty
      • The account balance is lower than the transaction cost
  • EthNonceVerificationDecorator(ak) verifies that the transaction nonce is valid and equal to the sender account's current nonce.
  • EthGasConsumeDecorator(evmKeeper) verifies that the Ethereum tx message is sufficient to pay for inherent gas (during CheckTx only) and that the sender has enough balance to cover the gas cost. The intrinsic gas of a transaction is the amount of gas used by the transaction before execution. Gas is a constant value plus any cost incurred by the extra data bytes provided by the transaction. This AnteHandler decorator will fail if:
    • Transaction contains multiple messages
    • Message is not MsgEthereumTx
    • No sender account found
    • the gas limit of the transaction is lower than the intrinsic gas
    • User does not have enough balance to deduct transaction fee (gas_limit * gas_price)
    • Transaction or block gas meter runs out of gas
  • CanTransferDecorator(evmKeeper, feeMarketKeeper) creates an EVM from the message and calls the BlockContext CanTransfer function to see if the address can perform a transaction.
  • EthIncrementSenderSequenceDecorator(ak) handles the sequence of incrementing signers (i.e. senders). If the transaction is a contract creation, the nonce will be incremented during transaction execution, not within this AnteHandler decorator.

The options authante.NewMempoolFeeDecorator(), authante.NewTxTimeoutHeightDecorator() and authante.NewValidateMemoDecorator(ak) are the same as Cosmos Tx. Click here to learn more about anteHandler.

EVM module

Each sdk.Msg in Tx (in this case MsgEthereumTx) after being authenticated by antehandler Passed to the Msg Handler in the x/evm module and run through the following steps:

  1. Convert Msg to Ethereum's Tx type
  2. Apply Tx with EVMConfig and attempt to perform a state transition, only persisting (committing) to the underlying KVStore if the transaction has not failed:
    1. Confirm that EVMConfig is created
    2. Create an Ethereum signer using the chain configuration values from "EVMConfig"
    3. Set the Ethereum transaction hash to (temporary) temporary storage so that it can also be used for StateDB functions
      1. Generate a new EVM instance
    4. Verify that the EVM parameters for contract creation (EnableCreate) and contract execution (EnableCall) are enabled
      1. App message. If the To address is nil, create a new contract with the code as the deployed code. Else invokes the contract at the given address with the given input as arguments
      2. Calculate the gas used by the evm operation
  3. If the Tx application is successful
    1. Execute the EVM Tx post-processing hook. If the hook returns an error, restore the entire "Tx"
      1. Return gas according to the Ethereum gas bookkeeping rules
    2. Update block bloom filter value with log generated from tx
    3. Emit SDK events for transaction fields and tx logs

trade

This section defines the sdk.Msg concrete types that cause the state transitions defined in the previous section.

MsgEthereumTx

EVM state transitions can be achieved by using "MsgEthereumTx". This message encapsulates the Ethereum transaction data (TxData) as sdk.Msg. It contains the necessary transaction data fields. Note that MsgEthereumTx implements sdk.Msg and sdk .Tx interface. Typically, SDK messages only implement the former, while the latter is a set of messages bundled together.

This section defines the sdk.Msg concrete types that cause the state transitions defined in the previous section.

MsgEthereumTx

EVM state transitions can be achieved using MsgEthereumTx. This message encapsulates the Ethereum transaction data (TxData) as sdk.Msg. It contains the necessary transaction data fields. Note that MsgEthereumTx implements sdk.Msg and sdk .Tx interface.

Typically, SDK messages only implement the former, while the latter is a set of messages bundled together.

type MsgEthereumTx struct {
 // inner transaction data
 Data *types.Any `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
 // DEPRECATED: encoded storage size of the transaction
 Size_ float64 `protobuf:"fixed64,2,opt,name=size,proto3" json:"-"`
 // transaction hash in hex format
 Hash string `protobuf:"bytes,3,opt,name=hash,proto3" json:"hash,omitempty" rlp:"-"`
 // ethereum signer address in hex format. This address value is checked
 // against the address derived from the signature (V, R, S) using the
 // secp256k1 elliptic curve
 From string `protobuf:"bytes,4,opt,name=from,proto3" json:"from,omitempty"`
}

This message field validation is expected to fail if:

  • The From field is defined and the address is invalid
  • TxData stateless validation failed

Transaction execution is expected to fail if:

  • Any custom AnteHandler ethereum decorator check fails:
    • Minimum gas requirement for transactions
    • Tx sender account does not exist or does not have sufficient fee balance
    • account sequence does not match transaction Data.AccountNonce
    • message signature verification failed
  • EVM contract creation (i.e. evm.Create) failed, or evm.Call failed

Conversion

MsgEthreumTx can be converted to go-ethereum Transaction and Message types In order to create and call evm contracts.

// AsTransaction creates an Ethereum Transaction type from the msg fields
func (msg MsgEthereumTx) AsTransaction() *ethtypes.Transaction {
  txData, err := UnpackTxData(msg.Data)
  if err != nil {
   return nil
  }

  return ethtypes. NewTx(txData. AsEthereumData())
}

// AsMessage returns the transaction as a core.Message.
func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
  msg := Message{
   nonce: tx.Nonce(),
   gasLimit: tx. Gas(),
   gasPrice: new(big.Int).Set(tx.GasPrice()),
   gasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
   gasTipCap: new(big.Int).Set(tx.GasTipCap()),
   to: tx.To(),
   amount: tx. Value(),
   data: tx.Data(),
   accessList: tx.AccessList(),
   isFake: false,
  }
  // If baseFee provided, set gasPrice to effectiveGasPrice.
  if baseFee != nil {
   msg.gasPrice = math.BigMin(msg.gasPrice.Add(msg.gasTipCap, baseFee), msg.gasFeeCap)
  }
  var err error
  msg.from, err = Sender(s, tx)
  return msg, err
}

sign

For signature verification to work, TxData must contain the v|r|s value from Signer. Sign computes the secp256k1 ECDSA signature and signs the transaction. According to the EIP155 standard, it requires a keyring signer and chainID to sign Ethereum transactions. This method mutates the transaction when filling the V, R, S fields of the transaction signature. The function will fail if no sender address is defined for the message or if the sender is not registered on the keyring.

// Sign calculates a secp256k1 ECDSA signature and signs the transaction. It
// takes a keyring signer and the chainID to sign an Ethereum transaction according to
// EIP155 standard.
// This method mutates the transaction as it populates the V, R, S
// fields of the Transaction's Signature.
// The function will fail if the sender address is not defined for the msg or if
// the sender is not registered on the keyring
func (msg *MsgEthereumTx) Sign(ethSigner ethtypes.Signer, keyringSigner keyring.Signer) error {
 from := msg.GetFrom()
 if from.Empty() {
  return fmt.Errorf("sender address not defined for message")
 }

 tx := msg.AsTransaction()
 txHash := ethSigner.Hash(tx)

 sig, _, err := keyringSigner.SignByAddress(from, txHash.Bytes())
 if err != nil {
  return err
 }

 tx, err = tx.WithSignature(ethSigner, sig)
 if err != nil {
  return err
 }

 msg.FromEthereumTx(tx)
 return nil
}

TxData

MsgEthereumTx supports 3 valid Ethereum transaction data types from go-ethereum: LegacyTx, AccessListTx, and DynamicFeeTx. These types are defined as protobuf messages and packed into the proto.Any interface type in the MsgEthereumTx field.

  • LegacyTx: EIP-155 transaction type
  • DynamicFeeTx: EIP-1559 transaction type.
  • AccessListTx: EIP-2930 transaction type.

LegacyTx

Transaction data for regular Ethereum transactions.

type LegacyTx struct {
 // nonce corresponds to the account nonce (transaction sequence).
 Nonce uint64 `protobuf:"varint,1,opt,name=nonce,proto3" json:"nonce,omitempty"`
 // gas price defines the value for each gas unit
 GasPrice *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=gas_price,json=gasPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_price,omitempty"`
 // gas defines the gas limit defined for the transaction.
 GasLimit uint64 `protobuf:"varint,3,opt,name=gas,proto3" json:"gas,omitempty"`
 // hex formatted address of the recipient
 To string `protobuf:"bytes,4,opt,name=to,proto3" json:"to,omitempty"`
 // value defines the unsigned integer value of the transaction amount.
 Amount *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,5,opt,name=value,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"value,omitempty"`
 // input defines the data payload bytes of the transaction.
 Data []byte `protobuf:"bytes,6,opt,name=data,proto3" json:"data,omitempty"`
 // v defines the signature value
 V []byte `protobuf:"bytes,7,opt,name=v,proto3" json:"v,omitempty"`
 // r defines the signature value
 R []byte `protobuf:"bytes,8,opt,name=r,proto3" json:"r,omitempty"`
 // s define the signature value
 S []byte `protobuf:"bytes,9,opt,name=s,proto3" json:"s,omitempty"`
}

This message field validation is expected to fail if:

  • GasPrice is invalid (nil, negative or out of range of int256)
  • Fee (gasprice * gaslimit) is invalid
  • Amount is invalid (negative or out of range of int256)
  • Invalid To address (invalid Ethereum hex address)

DynamicFeeTx

Transaction data for EIP-1559 dynamic fee transactions.

type DynamicFeeTx struct {
 // destination EVM chain ID
 ChainID *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"chainID"`
 // nonce corresponds to the account nonce (transaction sequence).
 Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
 // gas tip cap defines the max value for the gas tip
 GasTipCap *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=gas_tip_cap,json=gasTipCap,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_tip_cap,omitempty"`
 // gas fee cap defines the max value for the gas fee
 GasFeeCap *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=gas_fee_cap,json=gasFeeCap,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_fee_cap,omitempty"`
 // gas defines the gas limit defined for the transaction.
 GasLimit uint64 `protobuf:"varint,5,opt,name=gas,proto3" json:"gas,omitempty"`
 // hex formatted address of the recipient
 To string `protobuf:"bytes,6,opt,name=to,proto3" json:"to,omitempty"`
 // value defines the the transaction amount.
 Amount *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,7,opt,name=value,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"value,omitempty"`
 // input defines the data payload bytes of the transaction.
 Data     []byte     `protobuf:"bytes,8,opt,name=data,proto3" json:"data,omitempty"`
 Accesses AccessList `protobuf:"bytes,9,rep,name=accesses,proto3,castrepeated=AccessList" json:"accessList"`
 // v defines the signature value
 V []byte `protobuf:"bytes,10,opt,name=v,proto3" json:"v,omitempty"`
 // r defines the signature value
 R []byte `protobuf:"bytes,11,opt,name=r,proto3" json:"r,omitempty"`
 // s define the signature value
 S []byte `protobuf:"bytes,12,opt,name=s,proto3" json:"s,omitempty"`
}

This message field validation is expected to fail if:

  • GasTipCap is invalid (nil, negative value or overflow int256)
  • GasFeeCap is invalid (nil, negative value or overflow int256)
  • GasFeeCap is less than GasTipCap
  • Fee (gas price * gas limit) is invalid (overflow int256)
  • Invalid Amount (negative or overflow int256)
  • Invalid To address (invalid Ethereum hex address)
  • ChainID is nil

AccessListTx

EIP-2930 Access transaction data for list transactions.

type AccessListTx struct {
 // destination EVM chain ID
 ChainID *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"chainID"`
 // nonce corresponds to the account nonce (transaction sequence).
 Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
 // gas price defines the value for each gas unit
 GasPrice *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=gas_price,json=gasPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_price,omitempty"`
 // gas defines the gas limit defined for the transaction.
 GasLimit uint64 `protobuf:"varint,4,opt,name=gas,proto3" json:"gas,omitempty"`
 // hex formatted address of the recipient
 To string `protobuf:"bytes,5,opt,name=to,proto3" json:"to,omitempty"`
 // value defines the unsigned integer value of the transaction amount.
 Amount *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,6,opt,name=value,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"value,omitempty"`
 // input defines the data payload bytes of the transaction.
 Data     []byte     `protobuf:"bytes,7,opt,name=data,proto3" json:"data,omitempty"`
 Accesses AccessList `protobuf:"bytes,8,rep,name=accesses,proto3,castrepeated=AccessList" json:"accessList"`
 // v defines the signature value
 V []byte `protobuf:"bytes,9,opt,name=v,proto3" json:"v,omitempty"`
 // r defines the signature value
 R []byte `protobuf:"bytes,10,opt,name=r,proto3" json:"r,omitempty"`
 // s define the signature value
 S []byte `protobuf:"bytes,11,opt,name=s,proto3" json:"s,omitempty"`
}

This message field validation is expected to fail if:

  • GasPrice is invalid (nil, negative, or overflow int256)
  • Fee (gas price * gas limit) is invalid (overflow int256)
  • Invalid Amount (negative or overflow int256)
  • Invalid To address (invalid Ethereum hex address)
  • ChainID is nil

ABCI

The Application Blockchain Interface (ABCI) allows applications to interact with the Tendermint consensus engine. The application maintains multiple ABCI connections to Tendermint. Most relevant to x/evm is Consensus Connection at Commit. This connection is responsible for block execution and calls functions InitChain (contains InitGenesis), BeginBlock, DeliverTx, EndBlock, Commit. InitChain is only called the first time a new blockchain is started, and DeliverTx is called for every transaction in a block.

Initialize Genesis

InitGenesis initializes the EVM module genesis state by setting the GenesisState field to storage. In particular, it sets parameters and genesis accounts (state and code).

Export Genesis

The ExportGenesis ABCI function exports the genesis state of an EVM module. In particular, it retrieves all accounts and their bytecodes, balances and storage, transaction logs, As well as EVM parameters and chain configuration.

Begin Block

The EVM module BeginBlock logic is executed before the state transitions that process transactions. The main goals of this function are:

  • Set the context of the current block for block headers, storage, gas meters, etc. Once one of the StateDB functions is called during an EVM state transition, these functions are available to the Keeper.
  • Set the EIP155 ChainID number (obtained from the full chain ID) in case it was not previously set during InitChain

End block

The EndBlock logic of the EVM module occurs after all state transitions of the transaction have been executed. The main goals of this function are:

  • emit block bloom event
    • This is due to web3 compatibility, since the ethereum header includes this type as a field. The JSON-RPC service uses this event query to construct Ethereum headers from Tendermint headers.
    • The block bloom filter value is fetched from transient storage and then emitted

hooks

The x/evm module implements an EvmHooks interface, which can extend and customize Tx processing logic externally.

This supports EVM contracts calling native cosmos modules

  1. Define log signatures and emit specific logs from smart contracts,
  2. Identify these logs in the native tx handling code, and
  3. Convert them to native module calls.

For this, the interface contains a PostTxProcessing hook to register custom Tx hooks in EvmKeeper. These Tx hooks are processed after the EVM state transition is complete and will not fail. Note that there are no default hooks implemented in the EVM module.

type EvmHooks interface {
  // Must be called after tx is processed successfully, if return an error, the whole transaction is reversed.
  PostTxProcessing(ctx sdk.Context, msg core.Message, receipt *ethtypes.Receipt) error
}

PostTxProcessing

PostTxProcessing is only called after the EVM transaction has completed successfully, and delegates the call to the underlying hook. If no hooks are registered, this function returns a nil error. .

func (k *Keeper) PostTxProcessing(ctx sdk.Context, msg core.Message, receipt *ethtypes.Receipt) error {
  if k.hooks == nil {
   return nil
  }
  return k.hooks.PostTxProcessing(k.Ctx(), msg, receipt)
}

It executes in the same cache context as the EVM transaction, if it returns an error, the whole EVM transaction will be reverted, if hook implementors don't want to revert tx, they can always return nil.

Errors returned by hooks are translated to VM errors Unable to process native log, with detailed error messages stored in the return value. Messages are sent asynchronously to native modules, and callers cannot catch and recover from errors.

** Use case: calling native ERC20 modules on Daodst **

The following is an example taken from the Daodst [erc20 module] showing how EVMHooks support contracts that call native modules to convert ERC-20 tokens to Cosmos native tokens. Follow the steps above.

You can define and emit a "Transfer" log signature in your smart contract like this:

event Transfer(address indexed from, address indexed to, uint256 value);

function _transfer(address sender, address recipient, uint256 amount) internal virtual {
  require(sender != address(0), "ERC20: transfer from the zero address");
  require(recipient != address(0), "ERC20: transfer to the zero address");

  _beforeTokenTransfer(sender, recipient, amount);

  _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
  _balances[recipient] = _balances[recipient].add(amount);
  emit Transfer(sender, recipient, amount);
}

The application will register a BankSendHook with EvmKeeper. It recognizes the Ethereum tx Log and translates it into a call to the SendCoinsFromAccountToAccount method of the bank module:


const ERC20EventTransfer = "Transfer"

// PostTxProcessing implements EvmHooks.PostTxProcessing
func (k Keeper) PostTxProcessing(
 ctx sdk.Context,
 msg core.Message,
 receipt *ethtypes.Receipt,
) error {
 params := h.k.GetParams(ctx)
 if !params.EnableErc20 || !params.EnableEVMHook {
  // no error is returned to allow for other post-processing txs
  // to pass
  return nil
 }

 erc20 := contracts.ERC20BurnableContract.ABI

 for i, log := range receipt.Logs {
  if len(log.Topics) < 3 {
   continue
  }

  eventID := log.Topics[0] // event ID

  event, err := erc20.EventByID(eventID)
  if err != nil {
   // invalid event for ERC20
   continue
  }

  if event.Name != types.ERC20EventTransfer {
   h.k.Logger(ctx).Info("emitted event", "name", event.Name, "signature", event.Sig)
   continue
  }

  transferEvent, err := erc20.Unpack(event.Name, log.Data)
  if err != nil {
   h.k.Logger(ctx).Error("failed to unpack transfer event", "error", err.Error())
   continue
  }

  if len(transferEvent) == 0 {
   continue
  }

  tokens, ok := transferEvent[0].(*big.Int)
  // safety check and ignore if amount not positive
  if !ok || tokens == nil || tokens.Sign() != 1 {
   continue
  }

  // check that the contract is a registered token pair
  contractAddr := log.Address

  id := h.k.GetERC20Map(ctx, contractAddr)

  if len(id) == 0 {
   // no token is registered for the caller contract
   continue
  }

  pair, found := h.k.GetTokenPair(ctx, id)
  if !found {
   continue
  }

  // check that conversion for the pair is enabled
  if !pair.Enabled {
   // continue to allow transfers for the ERC20 in case the token pair is disabled
   h.k.Logger(ctx).Debug(
    "ERC20 token -> Cosmos coin conversion is disabled for pair",
    "coin", pair.Denom, "contract", pair.Erc20Address,
   )
   continue
  }

  // ignore as the burning always transfers to the zero address
  to := common.BytesToAddress(log.Topics[2].Bytes())
  if !bytes.Equal(to.Bytes(), types.ModuleAddress.Bytes()) {
   continue
  }

  // check that the event is Burn from the ERC20Burnable interface
  // NOTE: assume that if they are burning the token that has been registered as a pair, they want to mint a Cosmos coin

  // create the corresponding sdk.Coin that is paired with ERC20
  coins := sdk.Coins{{Denom: pair.Denom, Amount: sdk.NewIntFromBigInt(tokens)}}

  // Mint the coin only if ERC20 is external
  switch pair.ContractOwner {
  case types.OWNER_MODULE:
   _, err = h.k.CallEVM(ctx, erc20, types.ModuleAddress, contractAddr, true, "burn", tokens)
  case types.OWNER_EXTERNAL:
   err = h.k.bankKeeper.MintCoins(ctx, types.ModuleName, coins)
  default:
   err = types.ErrUndefinedOwner
  }

  if err != nil {
   h.k.Logger(ctx).Debug(
    "failed to process EVM hook for ER20 -> coin conversion",
    "coin", pair.Denom, "contract", pair.Erc20Address, "error", err.Error(),
   )
   continue
  }

  // Only need last 20 bytes from log.topics
  from := common.BytesToAddress(log.Topics[1].Bytes())
  recipient := sdk.AccAddress(from.Bytes())

  // transfer the tokens from ModuleAccount to sender address
  if err := h.k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, recipient, coins); err != nil {
   h.k.Logger(ctx).Debug(
    "failed to process EVM hook for ER20 -> coin conversion",
    "tx-hash", receipt.TxHash.Hex(), "log-idx", i,
    "coin", pair.Denom, "contract", pair.Erc20Address, "error", err.Error(),
   )
   continue
  }
 }

 return nil

Finally, register the hook in app.go:

app.EvmKeeper = app.EvmKeeper.SetHooks(app.Erc20Keeper)

event

The x/evm module emits Cosmos SDK events after state execution. The EVM module emits events related to transaction fields, as well as transaction logs (Ethereum events)。

MsgEthereumTx

Type Property Key Property Value
ethereum_tx "amount" {amount}
ethereum_tx "recipient" {hex_address}
ethereum_tx "contract" {hex_address}
ethereum_tx "txHash" {tendermint_hex_hash}
ethereum_tx "ethereumTxHash" {hex_hash}
ethereum_tx "txIndex" {tx_index}
ethereum_tx "txGasUsed" {gas_used}
tx_log "txLog" {tx_log}
message "sender" {eth_address}
message "action" "ethereum"
message "module" "evm"

Additionally, the EVM module emits an event for the filter query block bloom during EndBlock.

ABCI

Type Property Key Property Value
block_bloom "bloom" string(bloomBytes)

parameters

The evm module contains the following parameters:

Module parameters

Key Data Type Default
EVMDenom string "dst"
EnableCreate bool true
EnableCall bool true
ExtraEIPs []int TBD
ChainConfig ChainConfig See ChainConfig

:::hint Note: SDK applications want to import the EVM module as a dependency Will need to set own evm_denom (i.e. not "dst"). :::

EnableCreate

The EnableCreate parameter toggles state transitions using the vm.Create function. When this parameter is disabled, it blocks all contract creation functions.

EnableCall

The EnableCall parameter toggles state transitions using the vm.Call function. When this parameter is disabled, it prevents transfers between accounts and execution of smart contract calls.

Extra EIPs

The additional EIPs parameter defines a set of Ethereum Improvement Proposals that can be activated on the Ethereum VM Config applying a custom jump table (EIPs ).

:::hint Note: Some of these EIPs are already enabled via chain configuration, depending on the number of hard forks. :::

The supported activatable EIPS are:

Chain Config

ChainConfig is a protobuf wrapper type that contains the same fields as the go-ethereum ChainConfig parameter, but uses *sdk.Int type instead of *big.Int.

By default, all block configuration fields except ConstantinopleBlock are enabled at Genesis (height 0).

Default ChainConfig

name default
HomesteadBlock 0
DAOForkBlock 0
DAOForkSupport true
EIP150Block 0
EIP150Hash 0x0000000000000000000000000000000000000000000000000000000000000000
EIP155Block 0
EIP158Block 0
ByzantiumBlock 0
ConstantinopleBlock 0
PetersburgBlock 0
IstanbulBlock 0
MuirGlacierBlock 0
BerlinBlock 0
LondonBlock 0
ArrowGlacierBlock 0
GrayGlacierBlock 0
MergeNetsplitBlock 0
ShanghaiBlock 0
CancunBlock. 0

client

Users can query and interact with the evm module using CLI, JSON-RPC, gRPC, or REST.

CLI

Below find a list of stcd commands added using the x/evm module. You can get a complete list with stcd -h command.

Inquire

The query command allows the user to query the state of evm.

code

Allows users to query the smart contract code for a given address.

stcd query evm code ADDRESS [flags]
# Example
$ stcd query evm code 0x7bf7b17da59880d9bcca24915679668db75f9397

# Output
code: "0xef616c92f3cfc9e92dc270d6acff9cea213cecc7020a76ee4395af09bdceb4837a1ebdb5735e11e7d3adb6104e0c3ac55180b4ddf5e54d022cc5e8837f6a4f971b"

storage

Allows users to query the store for accounts with a given key and height。

stcd query evm storage ADDRESS KEY [flags]
# Example
$ stcd query evm storage 0x0f54f47bf9b8e317b214ccd6a7c3e38b893cd7f0 0 --height 0

# Output
value: "0x0000000000000000000000000000000000000000000000000000000000000000"

trade

The tx command allows the user to interact with the evm module.

raw

Allows users to build cosmos transactions from raw Ethereum transactions.

stcd tx evm raw TX_HEX [flags]
# Example
$ stcd tx evm raw 0xf9ff74c86aefeb5f6019d77280bbb44fb695b4d45cfe97e6eed7acd62905f4a85034d5c68ed25a2e7a8eeb9baf1b84

# Output
value: "0x0000000000000000000000000000000000000000000000000000000000000000"

gRPC

Inquire

Verb Method Description
gRPC ethermint.evm.v1.Query/Account Get Ethereum account
gRPC ethermint.evm.v1.Query/CosmosAccount Get the Cosmos address of an Ethereum account
gRPC ethermint.evm.v1.Query/ValidatorAccount Get Ethereum account from validator consensus address
gRPC ethermint.evm.v1.Query/Balance Get the EVM denomination balance of a single EthAccount.
gRPC ethermint.evm.v1.Query/Storage Get the balance of all coins in a single account
gRPC ethermint.evm.v1.Query/Code Get the balance of all coins in a single account
gRPC ethermint.evm.v1.Query/Params Get the parameters of the x/evm module
gRPC ethermint.evm.v1.Query/EthCall implements the eth_call rpc api
gRPC ethermint.evm.v1.Query/EstimateGas implements the eth_estimateGas rpc api
gRPC ethermint.evm.v1.Query/TraceTx implement debug_traceTransaction rpc api
gRPC ethermint.evm.v1.Query/TraceBlock implements debug_traceBlockByNumber and debug_traceBlockByHash rpc api
GET /ethermint/evm/v1/account/{address} Get Ethereum account
GET /ethermint/evm/v1/cosmos_account/{address} Get the Cosmos address of the Ethereum account
GET /ethermint/evm/v1/validator_account/{cons_address} Get Ethereum account from validator consensus address
GET /ethermint/evm/v1/balances/{address} Get the EVM denomination balance of a single EthAccount.
GET /ethermint/evm/v1/storage/{address}/{key} Get the balance of all coins in a single account
GET /ethermint/evm/v1/codes/{address} Get the balance of all coins in a single account
GET /ethermint/evm/v1/params Get the parameters of the x/evm module
GET /ethermint/evm/v1/eth_call implements eth_call rpc api
GET /ethermint/evm/v1/estimate_gas implements the eth_estimateGas rpc api
GET /ethermint/evm/v1/trace_tx implements debug_traceTransaction rpc api
GET /ethermint/evm/v1/trace_block implements debug_traceBlockByNumber and debug_traceBlockByHash rpc api

trade

Verb Method Description
gRPC ethermint.evm.v1.Msg/EthereumTx Submit an Ethereum transaction
POST /ethermint/evm/v1/ethereum_tx Submit an Ethereum transaction