EEVM — Ethereum Virtual Machine implementation in Elixir.
Execution engine for every fork from Frontier to Osaka: full opcode set, all
precompiles (BLS12-381 and KZG in pure Elixir), transaction validation and
processing, whole blocks, and MPT-backed state roots. CI runs the official
execution-spec-tests and ethereum/tests suites; the README's
"Conformance" section lists the results and the known exceptions.
Quick Start
iex> {:ok, result} = EEVM.run(<<0x60, 2, 0x60, 3, 0x01, 0x60, 0, 0x52, 0x60, 32, 0x60, 0, 0xF3>>)
iex> result.status
:success
iex> :binary.decode_unsigned(result.return_data)
5Module Map
The public API:
EEVM— run bytecode (run/2), call or deploy a contract without a signature (call/4,create/3), execute a signed transaction (transact/4), decode revert reasons, and the hashing and hex helpers that calls need (keccak256/1,selector/1,event_topic/1,address/1,hex/1)EEVM.ABI— Solidity ABI encoding: calldata, return data, and event logsEEVM.Config— the hardfork, chain id, and custom precompilesEEVM.BlockEnvandEEVM.TxEnv— the block and transaction environment code executes inEEVM.CallResultandEEVM.TransactionResult— whatrun/2andtransact/4returnEEVM.Error— every error reasonEEVM.Transactionand itsEnvelopeandSigner— transaction structs, wire encoding, and signaturesEEVM.Block— block RLP codec, validation, building, and executionEEVM.DatabaseandEEVM.Database.InMemory— account and storage state
Extension points: EEVM.Precompile (custom precompiles), EEVM.Tracer
(EIP-3155 traces), EEVM.Block.Processor (custom transaction executors),
and EEVM.SystemContracts (genesis setup). Modules grouped under
"Internals" in the docs are implementation details and may change in any
release.
Errors
A mistake in the arguments your own code passes, such as a hex string
where run/2, call/4, or create/3 take bytes, raises ArgumentError,
while anything that can arrive from outside, such as the raw signed bytes
transact/4 takes, a block, or a database read, returns
{:error, reason} with a reason listed in EEVM.Error.
Summary
Functions
Returns an address as the integer eevm uses everywhere: from a hex string
(with or without 0x, any case), a 20-byte binary, or an integer.
Decodes a hex string, with or without 0x and in any case, into bytes:
the inverse of hex/1 for binaries. Use it on bytecode, calldata, or raw
transactions copied from a compiler, explorer, or wallet; run/2,
call/4, create/3, and transact/4 take bytes and reject
0x-prefixed hex strings.
Calls the contract at address to with calldata, without a signed
transaction: like eth_call, from any :caller.
Deploys a contract by running initcode, without a signed transaction:
CREATE from any :caller.
Disassembles bytecode into a human-readable list of instructions.
Returns the first log topic of an event signature, its Keccak-256 hash, as
an integer, which is how result.logs carries topics. Anonymous events
have no such topic. EEVM.ABI.decode_log/2 matches and decodes a whole log.
Executes EVM bytecode and returns the final machine state.
Renders bytes or a non-negative integer as 0x-prefixed lowercase hex.
Returns the 32-byte Keccak-256 hash of data, Ethereum's hash function
(the original Keccak padding, not NIST SHA3-256).
Returns the logs emitted, in order. Deprecated: use run/2 and read result.logs.
Computes the logs bloom of the emitted logs. Deprecated: use EEVM.Block.Bloom.from_logs/1.
Decodes why a call reverted, from its REVERT data or from an
EEVM.CallResult or EEVM.TransactionResult.
Runs code as a message call, with no transaction rules: no signature,
nonce, intrinsic gas, or fee checks.
Returns the 4-byte ABI selector of a function signature: the first four
bytes of its Keccak-256 hash. Write the signature in canonical form, with
no spaces or parameter names and full type names (uint256, not uint).
Returns the stack contents (top first). Deprecated: use run/2 with debug: true.
Executes bytecode with an opcode-level tracer attached and returns
{final_state, tracer}.
Validates and executes one signed transaction against db in the block
environment block: decode, recover the sender, validate, charge gas,
execute, refund, and pay the coinbase.
Functions
@spec address(String.t() | <<_::160>> | non_neg_integer()) :: non_neg_integer()
Returns an address as the integer eevm uses everywhere: from a hex string
(with or without 0x, any case), a 20-byte binary, or an integer.
Raises ArgumentError for anything else.
Examples
iex> EEVM.address("0x00000000000000000000000000000000000000Ff")
255
iex> EEVM.address(<<0xB0B::160>>)
2827
Decodes a hex string, with or without 0x and in any case, into bytes:
the inverse of hex/1 for binaries. Use it on bytecode, calldata, or raw
transactions copied from a compiler, explorer, or wallet; run/2,
call/4, create/3, and transact/4 take bytes and reject
0x-prefixed hex strings.
Raises ArgumentError for anything that isn't an even number of hex
digits.
Examples
iex> EEVM.bytes("0x6002600301")
<<0x60, 0x02, 0x60, 0x03, 0x01>>
iex> EEVM.bytes("ABcd")
<<0xAB, 0xCD>>
iex> EEVM.bytes("0x")
""
@spec call(EEVM.Database.t(), non_neg_integer(), binary(), keyword()) :: {:ok, EEVM.CallResult.t()} | {:error, EEVM.Error.reason()}
Calls the contract at address to with calldata, without a signed
transaction: like eth_call, from any :caller.
The code is loaded from db, following an EIP-7702 delegation, and runs
as to, so it sees to's balance and storage; a precompile address runs
the precompile. :value moves from the caller to to first, and the
addresses EIP-2929 starts warm (caller, to, precompiles, and from
Shanghai the coinbase) are warm.
No transaction rules apply: no signature, nonce, intrinsic gas, or fees.
result.gas_used is execution gas only, without the 21,000 a transaction
would add.
Options
:caller— CALLER and ORIGIN (default0):value— wei sent with the call (default0):gas— the gas limit (default 30,000,000):config(or the:hardforkshorthand),:block,:tracer, and:debug, as forrun/2
Any other option raises ArgumentError.
Returns {:ok, %EEVM.CallResult{}} however execution ends; check
result.status, and see revert_reason/1 for a revert. Nothing is
committed: on success result.db holds the writes in its overlay, so pass
it to the next call, or commit it with EEVM.Database.commit/1. Returns
{:error, :insufficient_balance} when :value exceeds the caller's
balance, or {:error, {:database, reason}} when the backend fails a read.
Example
iex> # Runtime code: return storage slot 0 as a word.
iex> code = <<0x60, 0, 0x54, 0x60, 0, 0x52, 0x60, 32, 0x60, 0, 0xF3>>
iex> db = EEVM.Database.InMemory.new(accounts: %{0xC0DE => %{code: code}}, storage: %{0xC0DE => %{0 => 42}})
iex> {:ok, result} = EEVM.call(db, 0xC0DE, <<>>, caller: 0xA11CE)
iex> {result.status, :binary.decode_unsigned(result.return_data)}
{:success, 42}
@spec create(EEVM.Database.t(), binary(), keyword()) :: {:ok, EEVM.CallResult.t()} | {:error, EEVM.Error.reason()}
Deploys a contract by running initcode, without a signed transaction:
CREATE from any :caller.
The caller's nonce is incremented and the new address derived from its
value before the increment, as for a contract-creation transaction, so
repeated deploys get new addresses. :value moves to the new account,
the initcode runs there, and the code it returns is installed under the
EIP-170 size limit, EIP-3541, and the code-deposit charge.
result.contract_address is the new address, or nil when the
deployment failed.
Takes the same options as call/4, and like it applies no transaction
rules and commits nothing: on success result.db holds the new account in
its overlay; on failure it is db unchanged, nonce included. From
Shanghai, initcode over 49,152 bytes returns {:error, :initcode_too_large}
(EIP-3860).
Example
iex> # Initcode: copy the 11-byte runtime code from the call in `call/4`
iex> # and return it.
iex> runtime = <<0x60, 0, 0x54, 0x60, 0, 0x52, 0x60, 32, 0x60, 0, 0xF3>>
iex> initcode = <<0x60, 11, 0x60, 12, 0x60, 0, 0x39, 0x60, 11, 0x60, 0, 0xF3>> <> runtime
iex> {:ok, result} = EEVM.create(EEVM.Database.InMemory.new(), initcode, caller: 0xA11CE)
iex> EEVM.Database.get_code(result.db, result.contract_address) == runtime
true
@spec disassemble(binary()) :: [{non_neg_integer(), String.t(), String.t() | nil}]
Disassembles bytecode into a human-readable list of instructions.
Example
iex> EEVM.disassemble(<<0x60, 0x01, 0x60, 0x02, 0x01, 0x00>>)
[{0, "PUSH1", "0x01"}, {2, "PUSH1", "0x02"}, {4, "ADD", nil}, {5, "STOP", nil}]
@spec event_topic(String.t()) :: non_neg_integer()
Returns the first log topic of an event signature, its Keccak-256 hash, as
an integer, which is how result.logs carries topics. Anonymous events
have no such topic. EEVM.ABI.decode_log/2 matches and decodes a whole log.
Example
iex> EEVM.event_topic("Transfer(address,address,uint256)") |> EEVM.hex()
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
@spec execute( binary(), keyword() ) :: EEVM.Interpreter.MachineState.t()
Executes EVM bytecode and returns the final machine state.
Deprecated: use run/2, which returns an EEVM.CallResult. This takes the
options :gas, :hardfork, :config, :db, :block, :tx, and
:contract.
@spec hex(binary() | non_neg_integer()) :: String.t()
Renders bytes or a non-negative integer as 0x-prefixed lowercase hex.
A binary keeps every byte, so pass a hash or <<address::160>> to keep its
width; an integer renders as a JSON-RPC quantity, without leading zeros.
Examples
iex> EEVM.hex(<<0, 1, 0xAB>>)
"0x0001ab"
iex> EEVM.hex(255)
"0xff"
iex> EEVM.hex(0)
"0x0"
@spec keccak256(binary()) :: <<_::256>>
Returns the 32-byte Keccak-256 hash of data, Ethereum's hash function
(the original Keccak padding, not NIST SHA3-256).
Example
iex> EEVM.keccak256("") |> EEVM.hex()
"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
@spec logs(EEVM.Interpreter.MachineState.t()) :: [map()]
Returns the logs emitted, in order. Deprecated: use run/2 and read result.logs.
@spec logs_bloom(EEVM.Interpreter.MachineState.t()) :: EEVM.Block.Bloom.t()
Computes the logs bloom of the emitted logs. Deprecated: use EEVM.Block.Bloom.from_logs/1.
@spec revert_reason(binary() | EEVM.CallResult.t() | EEVM.TransactionResult.t()) :: {:error_string, String.t()} | {:panic, non_neg_integer()} | {:custom, <<_::32>>, binary()} | {:raw, binary()} | nil
Decodes why a call reverted, from its REVERT data or from an
EEVM.CallResult or EEVM.TransactionResult.
Returns:
{:error_string, message}for Solidity'sError(string)(selector0x08c379a0), whatrequire(cond, "message")andrevert("message")produce{:panic, code}forPanic(uint256)(selector0x4e487b71), such as0x01for a failedassert,0x11for arithmetic overflow, and0x12for division by zero{:custom, selector, args}for any other payload of four bytes or more, such as a Solidity custom error (decode itsargswithEEVM.ABI.decode/2) or a malformedError(string){:raw, data}for one to three bytesnilfor no data, or for a result whosestatusisn't:revert
Examples
iex> data = EEVM.ABI.encode_call("Error(string)", ["nope!"])
iex> EEVM.revert_reason(data)
{:error_string, "nope!"}
iex> EEVM.revert_reason(<<0x4E487B71::32, 0x12::256>>)
{:panic, 0x12}
iex> {:ok, result} = EEVM.run(<<0x60, 0, 0x60, 0, 0xFD>>)
iex> {result.status, EEVM.revert_reason(result)}
{:revert, nil}
@spec run( binary(), keyword() ) :: {:ok, EEVM.CallResult.t()} | {:error, EEVM.Error.reason()}
Runs code as a message call, with no transaction rules: no signature,
nonce, intrinsic gas, or fee checks.
Use it to test bytecode or to probe a contract eth_call-style. To run a
signed transaction, use transact/4.
Options
:config— theEEVM.Configto run under, or:hardforkfor the shorthandEEVM.Config.new(hardfork)(default:cancun):gas— the gas limit (default 1,000,000):db— theEEVM.Database(default: an emptyEEVM.Database.InMemory):blockand:tx— theEEVM.BlockEnvandEEVM.TxEnvthe code sees:address,:caller,:value,:calldata— the executing account, CALLER, CALLVALUE, and the call's input (defaults0,0,0,<<>>).:addressis required with:db: it is the account whose balance and storage the code sees. To call a contract already in:db, usecall/4, which loads its code for you.:tracer—trueor anEEVM.Tracerto record an EIP-3155 trace, returned asresult.trace:debug—trueto return the final stack asresult.stack
Any other option, or :db without :address, raises ArgumentError.
Returns {:ok, %EEVM.CallResult{}} however execution ends; check
result.status. Writes stay in result.db's overlay and are listed in
result.changes; nothing is committed to the backend. A backend read
failure returns {:error, {:database, reason}}.
Examples
iex> {:ok, result} = EEVM.run(<<0x60, 0x0A, 0x60, 0x14, 0x01, 0x00>>, debug: true)
iex> {result.status, result.stack, result.gas_used}
{:success, [30], 9}
iex> {:ok, result} = EEVM.run(<<0xFE>>, gas: 100)
iex> {result.status, result.gas_left}
{{:halt, :invalid_opcode}, 0}
@spec selector(String.t()) :: <<_::32>>
Returns the 4-byte ABI selector of a function signature: the first four
bytes of its Keccak-256 hash. Write the signature in canonical form, with
no spaces or parameter names and full type names (uint256, not uint).
Prepend it to the ABI-encoded arguments to build calldata, or let
EEVM.ABI.encode_call/2 do both.
Example
iex> EEVM.selector("transfer(address,uint256)")
<<0xA9, 0x05, 0x9C, 0xBB>>
@spec stack_values(EEVM.Interpreter.MachineState.t()) :: [non_neg_integer()]
Returns the stack contents (top first). Deprecated: use run/2 with debug: true.
@spec trace( binary(), keyword() ) :: {EEVM.Interpreter.MachineState.t(), EEVM.Tracer.t()}
Executes bytecode with an opcode-level tracer attached and returns
{final_state, tracer}.
Deprecated: use run(code, tracer: true), which returns the trace as
result.trace.
@spec transact( EEVM.Handler.wire_tx(), EEVM.BlockEnv.t(), EEVM.Database.t(), EEVM.Handler.opts() ) :: {:ok, EEVM.TransactionResult.t()} | {:error, EEVM.Error.reason() | EEVM.TransactionError.t()}
Validates and executes one signed transaction against db in the block
environment block: decode, recover the sender, validate, charge gas,
execute, refund, and pay the coinbase.
tx is a transaction struct (see EEVM.Transaction) or its raw signed
bytes. Options are :config (or the :hardfork shorthand) and :tracer,
as for run/2, and details: true, which returns a rejection as
{:error, %EEVM.TransactionError{}} with the values involved, such as
the transaction's and the account's nonce.
For simulation, tx may instead be an unsigned EEVM.TxEnv, which
impersonates its origin: no signature is needed or recovered, and every
other rule applies, so a wrong nonce or a sender that can't pay is still
rejected. The config's simulation switches (see EEVM.Config) relax
those rules for tooling; never use either for consensus. Signed
transactions are unaffected.
Returns {:ok, %EEVM.TransactionResult{}} for any transaction that passes
validation, including reverted and halted ones, or {:error, reason} with
a reason from EEVM.Error, leaving db unchanged.
Example
iex> tx = %EEVM.Transaction.Legacy{nonce: 5, gas_price: 10, gas_limit: 21_000, to: 0xB0B}
iex> {:ok, tx} = EEVM.Transaction.Signer.sign(tx, <<0x46::256>>, EEVM.Config.new(:cancun))
iex> db = EEVM.Database.InMemory.new()
iex> EEVM.transact(tx, EEVM.BlockEnv.new(), db)
{:error, :nonce_too_high}
iex> {:error, error} = EEVM.transact(tx, EEVM.BlockEnv.new(), db, details: true)
iex> {error.reason, error.details.tx_nonce, error.details.state_nonce}
{:nonce_too_high, 5, 0}Impersonating 0xA11CE with an unsigned EEVM.TxEnv:
iex> db = EEVM.Database.InMemory.new(accounts: %{0xA11CE => %{balance: 10 ** 18}})
iex> tx = %EEVM.TxEnv{origin: 0xA11CE, to: 0xB0B, value: 1_000, gas_limit: 21_000, gas_price: 10}
iex> {:ok, result} = EEVM.transact(tx, EEVM.BlockEnv.new(), db)
iex> {result.status, result.sender, EEVM.Database.get_balance(result.db, 0xB0B)}
{:success, 0xA11CE, 1000}
iex> EEVM.transact(%{tx | nonce: 1}, EEVM.BlockEnv.new(), db)
{:error, :nonce_too_high}