Shared utilities for opcode implementations.
Provides:
- Stack-pop / push helpers for the common pop-pop-compute-push shape used by arithmetic, comparison, and bitwise opcodes.
- Two's-complement conversion (
to_signed/1,to_unsigned/1) bridging the uint256 stack with signed opcodes (SLT, SGT, SDIV, SMOD, SAR). mod_pow/3, square-and-multiply modular exponentiation used by EXP.pop_address/1for opcodes that take an address operand.touch_account/3, which before EIP-161 creates the accounts a message call or fee credit touches. The transaction handler shares it.
Summary
Functions
Pops two values, applies a bitwise function, and pushes the result.
Pops two values from the stack, applies a comparison, and pushes 1 or 0.
Computes base^exp mod m using square-and-multiply (binary exponentiation).
Pops an address operand: the top stack word truncated to its low 160 bits,
as BALANCE, EXTCODESIZE, EXTCODECOPY, EXTCODEHASH, the CALL family, and
SELFDESTRUCT read it (Yellow Paper Appendix H, μs[0] mod 2^160).
Pushes a value onto the stack and advances the program counter by 1.
Same as comparison_op/2 but interprets both values as signed 256-bit integers.
Converts a uint256 value to a signed 256-bit integer (two's complement).
Converts a signed integer back to its uint256 (two's complement) representation.
Before EIP-161 (Spurious Dragon), touching an account that doesn't exist
creates it empty, even when no value moves: the target of every message
call, and the coinbase credited a zero fee (execution-specs Frontier
process_message calls touch_account; process_transaction sets the
coinbase balance). From EIP-161 the account is not created, and touched
empty accounts are deleted when the transaction ends.
Functions
@spec bitwise_op(EEVM.Interpreter.MachineState.t(), (non_neg_integer(), non_neg_integer() -> non_neg_integer())) :: {:ok, EEVM.Interpreter.MachineState.t()} | {:error, atom(), EEVM.Interpreter.MachineState.t()}
Pops two values, applies a bitwise function, and pushes the result.
Used by AND (0x16), OR (0x17), and XOR (0x18). The function fun receives
a and b as uint256 values and must return a uint256.
@spec comparison_op(EEVM.Interpreter.MachineState.t(), (non_neg_integer(), non_neg_integer() -> boolean())) :: {:ok, EEVM.Interpreter.MachineState.t()} | {:error, atom(), EEVM.Interpreter.MachineState.t()}
Pops two values from the stack, applies a comparison, and pushes 1 or 0.
Used by LT (0x10), GT (0x11), and EQ (0x14). The comparison function fun
receives a (first pop) and b (second pop) as unsigned integers.
@spec mod_pow(non_neg_integer(), non_neg_integer(), pos_integer()) :: non_neg_integer()
Computes base^exp mod m using square-and-multiply (binary exponentiation).
Used by EXP (0x0A). The naive approach of multiplying base by itself exp
times would be O(exp) — unusable for 256-bit exponents. Square-and-multiply
is O(log exp), making it practical even for large values.
@spec pop_address(EEVM.Interpreter.Stack.t()) :: {:ok, non_neg_integer(), EEVM.Interpreter.Stack.t()} | {:error, atom()}
Pops an address operand: the top stack word truncated to its low 160 bits,
as BALANCE, EXTCODESIZE, EXTCODECOPY, EXTCODEHASH, the CALL family, and
SELFDESTRUCT read it (Yellow Paper Appendix H, μs[0] mod 2^160).
iex> {:ok, stack} = EEVM.Interpreter.Stack.push(EEVM.Interpreter.Stack.new(), 2 ** 200 + 0xAB)
iex> {:ok, address, _} = EEVM.Interpreter.Instructions.Helpers.pop_address(stack)
iex> address
0xAB
@spec push_value(EEVM.Interpreter.MachineState.t(), non_neg_integer()) :: {:ok, EEVM.Interpreter.MachineState.t()}
Pushes a value onto the stack and advances the program counter by 1.
This is the final step in almost every opcode: after computing a result, push it and move to the next instruction.
@spec signed_comparison_op( EEVM.Interpreter.MachineState.t(), (integer(), integer() -> boolean()) ) :: {:ok, EEVM.Interpreter.MachineState.t()} | {:error, atom(), EEVM.Interpreter.MachineState.t()}
Same as comparison_op/2 but interprets both values as signed 256-bit integers.
Used by SLT (0x12) and SGT (0x13). Converts each uint256 operand to two's complement before applying the comparison, then pushes 1 or 0.
@spec to_signed(non_neg_integer()) :: integer()
Converts a uint256 value to a signed 256-bit integer (two's complement).
Any value with the 255th bit set (>= 2^255) is treated as negative. For example, the uint256 representation of -1 is 2^256 - 1 (all bits set).
@spec to_unsigned(integer()) :: non_neg_integer()
Converts a signed integer back to its uint256 (two's complement) representation.
Negative values are mapped to the upper half of the uint256 range. Positive values (and zero) pass through unchanged.
@spec touch_account(EEVM.Database.t(), non_neg_integer(), EEVM.HardforkConfig.t()) :: EEVM.Database.t()
Before EIP-161 (Spurious Dragon), touching an account that doesn't exist
creates it empty, even when no value moves: the target of every message
call, and the coinbase credited a zero fee (execution-specs Frontier
process_message calls touch_account; process_transaction sets the
coinbase balance). From EIP-161 the account is not created, and touched
empty accounts are deleted when the transaction ends.
Example
iex> alias EEVM.{Database, HardforkConfig}
iex> alias EEVM.Interpreter.Instructions.Helpers
iex> db = EEVM.Database.InMemory.new()
iex> Database.account_exists?(Helpers.touch_account(db, 0xAB, HardforkConfig.new(:frontier)), 0xAB)
true
iex> Database.account_exists?(Helpers.touch_account(db, 0xAB, HardforkConfig.new(:spurious_dragon)), 0xAB)
false