World state: a read-only backend plus eevm's own write overlay.
It inspects as its backend and a count of pending writes, such as
#EEVM.Database<EEVM.Database.InMemory, pending writes to 2 accounts and 1 storage slot>;
read state with this module's functions, list the writes with
changes/1, or pass structs: false to inspect/2 to see every field.
A backend implements this behaviour's read callbacks, account/2,
code/2, storage/3, and block_hash/2. eevm never writes to it
during execution: every balance, nonce, code, and storage write lands in
the overlay this struct carries. Because a %EEVM.Database{} is an
immutable value, reverting a call frame is just dropping its copy.
When a transaction or block finishes, changes/1 returns the overlay as an
EEVM.StateChanges. EEVM.transact/4 and EEVM.Block.execute/4 hand it
to the backend's commit/2 and return the committed database. When
commit/2 returns {:error, :unsupported}, the result keeps the overlay
and the caller applies result.changes itself. EEVM.run/2 never commits.
EEVM.StateRoot also needs to enumerate the backend, through
accounts/1 and storage_slots/2. A backend that can't, such as one
over JSON-RPC, returns {:error, :unsupported} from them and still
executes transactions; the EIP-7610 check that a CREATE target has no
storage then sees only the overlay.
Every callback is required, so what a backend can do is stated in its
code: eevm never asks the code server which functions a module exports.
use EEVM.Database declares the behaviour and defines commit/2,
accounts/1, and storage_slots/2 to return {:error, :unsupported}
until the module overrides them, so a read-only backend implements only
the four reads.
EEVM.Database.InMemory implements every callback. This read-only
backend serves a fixed map of accounts, so a call's writes stay in the
overlay and commit/1 reports that the backend can't commit:
iex> defmodule MyApp.FixedDatabase do
...> use EEVM.Database
...> @impl true
...> def account(accounts, address), do: {:ok, Map.get(accounts, address)}
...> @impl true
...> def code(_accounts, _code_hash), do: {:ok, <<>>}
...> @impl true
...> def storage(_accounts, _address, _slot), do: {:ok, 0}
...> @impl true
...> def block_hash(_accounts, _number), do: {:ok, nil}
...> end
iex> db = EEVM.Database.new(MyApp.FixedDatabase, %{0xA11CE => %{balance: 100, nonce: 0, code: <<>>}})
iex> {:ok, result} = EEVM.call(db, 0xB0B, <<>>, caller: 0xA11CE, value: 40)
iex> {EEVM.Database.get_balance(result.db, 0xA11CE), EEVM.Database.get_balance(result.db, 0xB0B)}
{60, 40}
iex> EEVM.Database.commit(result.db)
{:error, :commit_not_supported}Read errors
A callback that returns {:error, reason} aborts execution: the entry
point (EEVM.run/2, EEVM.transact/4, EEVM.Block.execute/4, or
EEVM.Block.build/4) returns {:error, {:database, reason}}. Called
directly, the functions in this module raise EEVM.Database.ReadError.
Deprecated backends
These still work for one minor version:
- A backend that leaves out
commit/2,accounts/1, orstorage_slots/2(optional before #204) is treated as returning{:error, :unsupported}, and a bare enumerable fromaccounts/1orstorage_slots/2is accepted. - A
commit/2,accounts/1, orstorage_slots/2that returns{:error, :not_supported}, the atom earlier docs used, is treated as returning{:error, :unsupported}. - A module that implements only the pre-1.0 write and query callbacks
(
get_account/2,set_balance/3,storage_store/4, and so on) is wrapped inEEVM.Database.Legacybynew/2.
Summary
Types
An account record: balance, nonce, and code.
An address: a 160-bit integer.
An account as a backend returns it. code may be included to save a
code/2 call; when it is, code_hash may be left out.
Callbacks
Returns the account at address, or nil when it doesn't exist.
Enumerates every address with an account or storage, or returns
{:error, :unsupported}. Only state roots need it.
Returns the hash of block number, or nil when unknown. BLOCKHASH asks
only for blocks the EEVM.BlockEnv doesn't already carry.
Returns the code whose keccak256 hash is code_hash.
Applies a finished set of changes and returns the new backend state.
A read-only backend returns {:error, :unsupported}.
Returns the storage word at slot of address (0 when unset).
Enumerates {slot, value} for every set slot of address, or returns
{:error, :unsupported}. Only state roots and the EIP-7610 CREATE check need it.
Functions
Declares the EEVM.Database behaviour and defines commit/2,
accounts/1, and storage_slots/2 to return {:error, :unsupported}.
Returns every address with an account or storage, overlay included.
Needs the backend's accounts/1.
Returns whether address is empty: zero balance and nonce, and no code (EIP-161).
Returns whether an account exists at address.
Returns the hash of block number from the backend, or nil when unknown.
Returns the overlay's writes as an EEVM.StateChanges.
Commits the overlay to the backend with its commit/2 and returns the
database with an empty overlay. Returns {:error, :commit_not_supported}
when the backend's commit/2 returns {:error, :unsupported}.
Deletes the account at address and all its storage.
Returns %{balance, nonce, code} for address, or nil if it has no account.
Returns the balance of address in wei (0 if it has no account).
Returns the code at address, or <<>>.
Returns the keccak256 hash of the code at address; nil if it has no account.
Returns the nonce of address (0 if it has no account).
Increments the nonce of address.
Wraps a backend module and its state, with an empty overlay.
Replaces the account record at address; missing fields default to zero and no code.
Sets the code at address.
Sets the balance of address.
Sets the nonce of address.
Returns the addresses with storage. Deprecated: use account_addresses/1.
Returns the storage word at key of address (0 when unset).
Returns {slot, value} for every set storage slot of address, overlay
included. Needs the backend's storage_slots/2.
Stores a 256-bit word at key of address.
Moves amount wei from from to to.
Types
@type account() :: %{ optional(:balance) => non_neg_integer(), optional(:nonce) => non_neg_integer(), optional(:code) => binary() }
An account record: balance, nonce, and code.
@type address() :: non_neg_integer()
An address: a 160-bit integer.
@type backend_account() :: %{ :balance => non_neg_integer(), :nonce => non_neg_integer(), optional(:code_hash) => <<_::256>>, optional(:code) => binary() }
An account as a backend returns it. code may be included to save a
code/2 call; when it is, code_hash may be left out.
@type t() :: %EEVM.Database{ accounts: %{required(address()) => overlay_account() | nil}, destroyed: MapSet.t(address()), impl: module(), state: term(), storage: %{ required(address()) => %{required(non_neg_integer()) => non_neg_integer()} }, tries: EEVM.StateRoot.tries() | nil }
Callbacks
@callback account(state :: term(), address()) :: {:ok, backend_account() | nil} | {:error, term()}
Returns the account at address, or nil when it doesn't exist.
@callback accounts(state :: term()) :: {:ok, Enumerable.t()} | {:error, :unsupported | term()}
Enumerates every address with an account or storage, or returns
{:error, :unsupported}. Only state roots need it.
@callback block_hash(state :: term(), number :: non_neg_integer()) :: {:ok, <<_::256>> | nil} | {:error, term()}
Returns the hash of block number, or nil when unknown. BLOCKHASH asks
only for blocks the EEVM.BlockEnv doesn't already carry.
Returns the code whose keccak256 hash is code_hash.
@callback commit(state :: term(), EEVM.StateChanges.t()) :: {:ok, term()} | {:error, :unsupported | term()}
Applies a finished set of changes and returns the new backend state.
A read-only backend returns {:error, :unsupported}.
@callback storage(state :: term(), address(), slot :: non_neg_integer()) :: {:ok, non_neg_integer()} | {:error, term()}
Returns the storage word at slot of address (0 when unset).
@callback storage_slots(state :: term(), address()) :: {:ok, Enumerable.t()} | {:error, :unsupported | term()}
Enumerates {slot, value} for every set slot of address, or returns
{:error, :unsupported}. Only state roots and the EIP-7610 CREATE check need it.
Functions
Declares the EEVM.Database behaviour and defines commit/2,
accounts/1, and storage_slots/2 to return {:error, :unsupported}.
A module that uses it implements only the read callbacks, account/2,
code/2, storage/3, and block_hash/2, and overrides a default
by defining the callback itself:
defmodule MyApp.RpcDatabase do
use EEVM.Database
@impl true
def account(state, address), do: ...
# code/2, storage/3, and block_hash/2 likewise; no commit/2,
# accounts/1, or storage_slots/2.
endThe defaults are ordinary compiled functions, so the module still states
what it supports in code. @behaviour EEVM.Database with all seven
callbacks keeps working unchanged. The EEVM.Database module docs run a
complete read-only adapter.
Returns every address with an account or storage, overlay included.
Needs the backend's accounts/1.
Returns whether address is empty: zero balance and nonce, and no code (EIP-161).
Returns whether an account exists at address.
@spec block_hash(t(), non_neg_integer()) :: <<_::256>> | nil
Returns the hash of block number from the backend, or nil when unknown.
@spec changes(t()) :: EEVM.StateChanges.t()
Returns the overlay's writes as an EEVM.StateChanges.
Example
iex> db = EEVM.Database.InMemory.new() |> EEVM.Database.set_balance(1, 5)
iex> %{1 => change} = EEVM.Database.changes(db).accounts
iex> {change.balance, change.nonce, change.destroyed?}
{5, 0, false}
Commits the overlay to the backend with its commit/2 and returns the
database with an empty overlay. Returns {:error, :commit_not_supported}
when the backend's commit/2 returns {:error, :unsupported}.
Deletes the account at address and all its storage.
Returns %{balance, nonce, code} for address, or nil if it has no account.
@spec get_balance(t(), address()) :: non_neg_integer()
Returns the balance of address in wei (0 if it has no account).
Returns the code at address, or <<>>.
Returns the keccak256 hash of the code at address; nil if it has no account.
@spec get_nonce(t(), address()) :: non_neg_integer()
Returns the nonce of address (0 if it has no account).
Increments the nonce of address.
Wraps a backend module and its state, with an empty overlay.
A module that implements only the deprecated pre-1.0 callbacks is wrapped
in EEVM.Database.Legacy.
Example
iex> db = EEVM.Database.new(EEVM.Database.InMemory, EEVM.Database.InMemory.init())
iex> EEVM.Database.get_balance(db, 0x1234)
0
Replaces the account record at address; missing fields default to zero and no code.
Sets the code at address.
@spec set_balance(t(), address(), non_neg_integer()) :: t()
Sets the balance of address.
@spec set_nonce(t(), address(), non_neg_integer()) :: t()
Sets the nonce of address.
Returns the addresses with storage. Deprecated: use account_addresses/1.
@spec storage_load(t(), address(), non_neg_integer()) :: non_neg_integer()
Returns the storage word at key of address (0 when unset).
@spec storage_slots(t(), address()) :: [{non_neg_integer(), non_neg_integer()}]
Returns {slot, value} for every set storage slot of address, overlay
included. Needs the backend's storage_slots/2.
@spec storage_store(t(), address(), non_neg_integer(), non_neg_integer()) :: t()
Stores a 256-bit word at key of address.
@spec transfer(t(), address(), address(), non_neg_integer()) :: {:ok, t()} | {:error, :insufficient_balance}
Moves amount wei from from to to.