Deep dive into eth_call

Eth_call is one of Ethereum’s standard JSON-RPC methods. It is a powerful development tool, but not many developers – even the most experienced ones – understand how it works.

In this article, you will deep dive into eth_call. You will learn how EVM works behind the scenes; why you should use eth_call, and what the possible reasons that it fails to execute are.

There are 4 parts in this article:

  • Ethereum is a state machine…wait, what is a state machine?
  • What are transactions and blocks?
  • Eth_call use cases with example
  • Conclusion

Ethereum is a state machine…wait, what is a state machine?

To understand how eth_call works, the first concept to learn is Ethereum’s data structure. Ethereum defines itself as a distributed state machine.

So…what is a state machine?

Every Ethereum clients keep the holistic information of the whole chain. This information includes:

  • The account addresses
  • Other information that is related to the account
    • Balance
    • Nonce
    • storageRoot – for smart contract
    • codeHash – for smart contract

This information is known as the global state. The global state represents the condition that every Ethereum account is in at a specific time. It is consistently updated whenever a new block is generated. Ideally, all Ethereum nodes are synced to have the same state at all times.

Figure 1: Ethereum nodes sync on state data

Ethereum adapts PATRICIA MERKLE TREES for data storage. It has four tries implemented:

  • State trie
  • storage trie
  • receipt trie
  • transaction trie.

The global state is stored in the state trie.

The storage trie contains data for smart contracts. Each Ethereum account owns a separate trie, which is referenced by the storageRoot field from the global state trie.

Figure 2: Ethereum data tries

The transaction and receipt tries contain data for finalized transactions. They are updated whenever a block is created. Since a block is considered finalized after it is validated, these two tries are never modified after creation in normal circumstances.

For simplicity, Ethereum clients keep two types of data:

  • State data
  • Historical transaction data

The availability of state data is one of the most important factors to consider before sending an eth_call.

What are transactions and blocks?

Essentially, any transaction (including calling smart contracts) on Ethereum can be seen as a state update.

Fund transfer

A fund transfer is the most basic type of transaction. In terms of state change, a successful fund transfer causes a deduction in balance for one account and an increment in balance for another. The miner/validator receives the transaction’s gas fee too.

Calling a smart contract

There are two types of smart contract callings, read and write.

Contract read methods retrieve information from a node. There is no real transaction happening, hence no state update for read calls, with no gas fee either.

Write methods, e.g. storing information on chain, are considered an attempt to update the global state. Therefore, the sender must pay a gas fee to send a real transaction. If the transaction is successfully validated, the storage trie will update.

For complex calls involving multiple internal transactions, the smart contracts are called and modified in sequence.

Transaction failing

What if a transaction fails during execution? The transaction will be void. Ethereum reverted to its original state. The sender loses the gas fee but nothing else.

Pending transactions

A pending transaction has nothing to do with the state. It just travels the network waiting to be validated.

What is a block

In practice, Ethereum’s state doesn’t change for a single transaction, the state only changes when a block is validated.

A block consists of multiple transactions packaged together by a validator. After it is validated, it propagates to all nodes in the Ethereum network. When a node receives a new block, it updates its state information accordingly.

Since blocks are usually immutable and Ethereum’s state only changes when a new block is validated, Ethereum’s state is usually referenced by the block number or block hash.

Eth_call example + use cases

So, what is eth_call?

Eth_call immediately executes a new message call without creating a transaction. Its most fundamental use case is using it to execute read methods of a smart contract, but it can do a lot more:

  • For example, many applications use eth_call to “simulate” the result before sending the actual transaction
  • It can be used to check a token balance in the past
  • You can also use it to check the outcome of a pending transaction
  • It is also good for testing unpublished smart contracts

Try it with Chainstack

In this tutorial, the sample code is developed and tested with a Chainstack endpoint — it is highly recommended for the following reasons:

  • Free 3 million requests.
  • Eth_call available on all nodes
  • Supports both Erigon and Geth
  • Archive node available from $49/month, with 20 million requests
  • Seamless node deployment.

Follow these steps to deploy a node:

  1. Sign up with Chainstack.
  2. Deploy an Ethereum node.
  3. Get the deployed node’s endpoint.

There are slight differences between a Geth node and an Erigon node. You can check your node type on the project page.

To deploy an Erigon node, simply turn on the debug and trace APIs option when you deploy a node.

Eth_call

So what is eth_call?

Eth_call is an Ethereum API method that executes a new message call immediately without creating a transaction on the blockchain.

Parameters:

  • object — the transaction call object with:
    • from — (optional) the string of the address, the transaction is sent from
    • to — the string of the address, the transaction is directed to
    • gas — (optional) the integer of the gas provided for the transaction execution
    • gasPrice — (optional) the integer of the gas price used for each paid gas, encoded as hexadecimal
    • value — (optional) the integer of the value sent with this transaction, encoded as hexadecimal
    • data — (optional) the string of the hash of the method signature and encoded parameters, see the Ethereum Contract ABI (opens new window).
    • quantity or tag — the integer block number, or the string with:
      • latest — the latest block that is to be validated. The Beacon Chain may reorg and the latest block can become orphaned
      • safe — the block that is equal to the tip of the chain and is very unlikely to be orphaned
      • finalized — the block that is accepted by two-thirds of the Ethereum validators
      • earliest — the genesis block
      • pending — the pending state and transactions block

Returns:

  • data – the return value of the executed contract
What is eth_call

There are two main types of eth_call: read contract calls and write contract calls.

Read contract call

A read contract call is probably the simplest eth_call. Try it out with the following sample code, remember to change the HTTPS endpoint:

For Curl:

curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"0xc2edad668740f1aa35e4d8f227fb8e17dca888cd","data":"0x1526fe270000000000000000000000000000000000000000000000000000000000000001"},"latest"],"id":1}' https://your-end-point-url.p2pify.com/ABCDabcd

It returns:

{"jsonrpc":"2.0","id":1,"result":"0x000000000000000000000000397ff1542f962076d0bfe58ea045ffa2d347aca00000000000000000000000000000000000000000000000000000000000001d4c0000000000000000000000000000000000000000000000000000000000f5277800000000000000000000000000000000000000000000000095ad6b3115e07f95"}

This example calls the poolInfo method from SushiSwap’s smart contract. It returns the liquidity pool state of SushiSwap. For example, if the input is 1, it returns the current state for the USDC-WETH pool.

The call data is encoded, you can learn how the data objected is constructed from this article.

No gas fee is needed for reading contract calls. You can use any address as the “from” address, or simply without specifying it.

Write contract call

This sample code is based on a successful withdrawal transaction from SushiSwap.

Sample curl command:

curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0xe7e8569267c4a3278be75a2d86fd1a9e0a6818d8","to":"0xc2edad668740f1aa35e4d8f227fb8e17dca888cd","gas":"0x1E9EF","gasPrice":"0x2C2F7FD5E","data":"0x441a3e70000000000000000000000000000000000000000000000000000000000000010300000000000000000000000000000000000000000000000000c6c3eca729cb9e"},"0xF52579"],"id":1}' https://your-end-point-url.p2pify.com/ABCDabcd

Output:

{"jsonrpc":"2.0","id":1,"result":"0x"}

Different from a read contract call, write contract calls require the account to have enough balance to pay for the gas fee, even though no actual gas will be consumed.

This example is called on block “0xF52579”, which is the block this transaction was executed; running it on a non-archive node will likely result in an error. By now you may have a fair guess why this error occurs; If you don’t, reading on will help you understand how eth_call works better.

Common questions

Q. When you send an eth_call against an old block, what is the value returned?

A. The value is based on the old state. For example, if you send a read contract eth_call to Uniswap checking the balance of a specific token, but the block you specify is two years old; You will get the token balance from two years ago.

The same applies to a write contract eth_call: If you are calling against an old block, it only tells you if the call would pass/fail based on the historical state. Not the current state.

Q. I send an eth_call with a transaction that was successfully executed before, but it fails. Why?

A. This is most likely related to the previous question. The state data changes constantly. It is totally normal for a successful transaction to fail in a new state. For example:

  • The sender may not have enough balance to pay for the gas fee
  • The receiver may be blacklisted
  • The smart contract may be modified

So please remember to choose the block number carefully.

Q. Can eth_call be used to simulate a fund transfer?

A. Yes. For valid fund transfer, it returns:

{"jsonrpc":"2.0","id":1,"result":"0x"}

else it will return

{"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"insufficient funds for gas * price + value: address 0x5FFFFFFF…………… have 1234 want 1234567890"}}

For a fund transfer, you need to specify the “from” address, “to” address, gas, gasPrice, and the value to transfer.

A curl sample with Chainstack endpoint:

curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x5b10c7ea7c186495c84c2266a7f1f775afce342a","to":"0x5b10c7ea7c186495c84c2266a7f1f775afce342a","gas":"0x493E0","gasPrice":"0x37E11D600","value":"0xF"},"latest"],"id":1}' https:// nd-123-456-789.p2pify.com/ABCDabcd

Q. What does it mean by “missing trie node” error

A. It simply means that the state is not available. The block number you enter is either invalid or does not exist anymore on the node. An Ethereum full node by default only keeps the most recent 128 blocks of data (AKA tries). You can learn more in this article.

Q. To send eth_call, user can specify the block as a number/hash, “latest”, ”earliest” or “pending”. What are they?

A. If you specify a number/hexadecimal string, the client will run it against an old block state with the specific block number.

“Latest” block for the current state.

The “Earliest” block is the earliest block available. For a full node, it is usually 128 blocks from the current block.

The “Pending” block is an imaginary block that will likely be validated next. However, it is not guaranteed. Normally, the real block validated differs from the “pending” block.

Q. Does the block number represent the state before block execution or after?

A. After. To rerun a successful transaction in block 100, you need to specify the block as 99.

Q. What does it mean by “cannot unmarshal number into Go value of type string” error

A. This means one of the value inputs is supposed to be a string, but the input is a number. Please take note that for an Erigon client, the block number can be either a hexadecimal string or a number; For a Geth client, the block number must be hexadecimal.

Q. If I run eth_call, then another eth_call, would the result of the second call be based on the first call?

A. Nope, multiple eth_call requests are executed independently. However, if you need to run eth_call in sequence, Erigon’s trace_callMany may be the method you are looking for.

State override

A non-standard feature both Geth and Erigon support is state override. State override allows the user to run eth_call with customized states.

State override set

The state override set is an optional address-to-state mapping, where each entry specifies some state to be ephemerally overridden prior to executing the call. Each address maps to an object containing:

FieldTypeBytesOptionalDescription
balanceQuantity<32YesFake balance to set for the account before executing the call.
nonceQuantity<8YesFake nonce to set for the account before executing the call.
codeBinaryanyYesFake EVM bytecode to inject into the account before executing the call.
stateObjectanyYesFake key-value mapping to override all slots in the account storage before executing the call.
stateDiffObjectanyYesFake key-value mapping to override individual slots in the account storage before executing the call.

The goal of the state override set is manyfold:

  • It can be used by DApps to reduce the amount of contract code needed to be deployed on chain. Code that simply returns internal state or does pre-defined validations can be kept off chain and fed to the node on-demand.
  • It can be used for smart contract analysis by extending the code deployed on chain with custom methods and invoking them. This avoids having to download and reconstruct the entire state in a sandbox to run custom code against.
  • It can be used to debug smart contracts in an already deployed large suite of contracts by selectively overriding some code or state and seeing how execution changes. Specialized tooling will probably be necessary.
state override parameters for eth_call

For example, the below transaction will fail because of insufficient funds:

curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x1111111111111111111111111111111111111111","to":"0x2222222222222222222222222222222222222222","gas":"0x493E0","gasPrice":"0x37E11D600","value":"0xFFFFFFFFFFFFFFFFFFF"},"latest"],"id":1}' https:// nd-123-456-789.p2pify.com/ABCDabcd

With state override, we can set the account balance to any number for testing purposes:

curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x1111111111111111111111111111111111111111","to":"0x2222222222222222222222222222222222222222","gas":"0x493E0","gasPrice":"0x37E11D600","value":"0xFFFFFFFFFFFFFFFFFFF"},"latest",{"0x1111111111111111111111111111111111111111":{"balance":"0xFFFFFFFFFFFFFFFFFFFF"}}],"id":1}' https:// nd-123-456-789.p2pify.com/ABCDabcd

Conclusion

This is the end of this article. Hope you will find it useful. Thanks for reading.

If you have any questions, feel free to ping me on Twitter/Telegram/Discord.

Happy coding.

Cheers!

Chainstack uses cookies to provide you with a secure and
personalized experience on its website. Learn more.