Ethereum Virtual Machine (EVM) System Instructions
Table of Contents
Introduction
Implementations of the EVM system related instructions.
Module Contents
Functions
Core logic used by the CREATE* family of opcodes. |
|
Creates a new account with associated code. |
|
Creates a new account with associated code. |
|
Halts execution returning output data. |
|
Perform the core logic of the CALL* family of opcodes. |
|
Message-call into an account. |
|
Message-call into this account with alternative account’s code. |
|
Halt execution and register account for later deletion. |
|
Message-call into an account. |
|
Message-call into an account. |
|
Stop execution and revert state changes, without consuming all provided gas |
Module Details
generic_create
- generic_create(evm: ethereum.berlin.vm.Evm, endowment: ethereum.base_types.U256, contract_address: ethereum.berlin.fork_types.Address, memory_start_position: ethereum.base_types.U256, memory_size: ethereum.base_types.U256) → None
Core logic used by the CREATE* family of opcodes.
def generic_create(
evm: Evm,
endowment: U256,
contract_address: Address,
memory_start_position: U256,
memory_size: U256,
) -> None:
# This import causes a circular import error
# if it's not moved inside this method
from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message
evm.accessed_addresses.add(contract_address)
create_message_gas = max_message_call_gas(Uint(evm.gas_left))
evm.gas_left -= U256(create_message_gas)
ensure(not evm.message.is_static, WriteInStaticContext)
evm.return_data = b""
sender_address = evm.message.current_target
sender = get_account(evm.env.state, sender_address)
if (
sender.balance < endowment
or sender.nonce == Uint(2**64 - 1)
or evm.message.depth + 1 > STACK_DEPTH_LIMIT
):
evm.gas_left += create_message_gas
push(evm.stack, U256(0))
return
if account_has_code_or_nonce(evm.env.state, contract_address):
increment_nonce(evm.env.state, evm.message.current_target)
push(evm.stack, U256(0))
return
call_data = memory_read_bytes(
evm.memory, memory_start_position, memory_size
)
increment_nonce(evm.env.state, evm.message.current_target)
child_message = Message(
caller=evm.message.current_target,
target=Bytes0(),
gas=U256(create_message_gas),
value=endowment,
data=b"",
code=call_data,
current_target=contract_address,
depth=evm.message.depth + 1,
code_address=None,
should_transfer_value=True,
is_static=False,
accessed_addresses=evm.accessed_addresses.copy(),
accessed_storage_keys=evm.accessed_storage_keys.copy(),
)
child_evm = process_create_message(child_message, evm.env)
if child_evm.has_erred:
incorporate_child_on_error(evm, child_evm)
evm.return_data = child_evm.output
push(evm.stack, U256(0))
else:
incorporate_child_on_success(evm, child_evm)
evm.return_data = b""
push(evm.stack, U256.from_be_bytes(child_evm.message.current_target))
create
- create(evm: ethereum.berlin.vm.Evm) → None
Creates a new account with associated code.
- Parameters
evm – The current EVM frame.
def create(evm: Evm) -> None:
# STACK
endowment = pop(evm.stack)
memory_start_position = pop(evm.stack)
memory_size = pop(evm.stack)
# GAS
extend_memory = calculate_gas_extend_memory(
evm.memory, [(memory_start_position, memory_size)]
)
charge_gas(evm, GAS_CREATE + extend_memory.cost)
# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
contract_address = compute_contract_address(
evm.message.current_target,
get_account(evm.env.state, evm.message.current_target).nonce,
)
generic_create(
evm, endowment, contract_address, memory_start_position, memory_size
)
# PROGRAM COUNTER
evm.pc += 1
create2
- create2(evm: ethereum.berlin.vm.Evm) → None
Creates a new account with associated code.
It’s similar to CREATE opcode except that the address of new account depends on the init_code instead of the nonce of sender.
- Parameters
evm – The current EVM frame.
def create2(evm: Evm) -> None:
# STACK
endowment = pop(evm.stack)
memory_start_position = pop(evm.stack)
memory_size = pop(evm.stack)
salt = pop(evm.stack).to_be_bytes32()
# GAS
extend_memory = calculate_gas_extend_memory(
evm.memory, [(memory_start_position, memory_size)]
)
call_data_words = ceil32(Uint(memory_size)) // 32
charge_gas(
evm,
GAS_CREATE + GAS_KECCAK256_WORD * call_data_words + extend_memory.cost,
)
# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
contract_address = compute_create2_contract_address(
evm.message.current_target,
salt,
memory_read_bytes(evm.memory, memory_start_position, memory_size),
)
generic_create(
evm, endowment, contract_address, memory_start_position, memory_size
)
# PROGRAM COUNTER
evm.pc += 1
return
- return_(evm: ethereum.berlin.vm.Evm) → None
Halts execution returning output data.
- Parameters
evm – The current EVM frame.
def return_(evm: Evm) -> None:
# STACK
memory_start_position = pop(evm.stack)
memory_size = pop(evm.stack)
# GAS
extend_memory = calculate_gas_extend_memory(
evm.memory, [(memory_start_position, memory_size)]
)
charge_gas(evm, GAS_ZERO + extend_memory.cost)
# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
evm.output = memory_read_bytes(
evm.memory, memory_start_position, memory_size
)
evm.running = False
# PROGRAM COUNTER
pass
generic_call
- generic_call(evm: ethereum.berlin.vm.Evm, gas: ethereum.base_types.Uint, value: ethereum.base_types.U256, caller: ethereum.berlin.fork_types.Address, to: ethereum.berlin.fork_types.Address, code_address: ethereum.berlin.fork_types.Address, should_transfer_value: bool, is_staticcall: bool, memory_input_start_position: ethereum.base_types.U256, memory_input_size: ethereum.base_types.U256, memory_output_start_position: ethereum.base_types.U256, memory_output_size: ethereum.base_types.U256) → None
Perform the core logic of the CALL* family of opcodes.
def generic_call(
evm: Evm,
gas: Uint,
value: U256,
caller: Address,
to: Address,
code_address: Address,
should_transfer_value: bool,
is_staticcall: bool,
memory_input_start_position: U256,
memory_input_size: U256,
memory_output_start_position: U256,
memory_output_size: U256,
) -> None:
from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message
evm.return_data = b""
if evm.message.depth + 1 > STACK_DEPTH_LIMIT:
evm.gas_left += gas
push(evm.stack, U256(0))
return
call_data = memory_read_bytes(
evm.memory, memory_input_start_position, memory_input_size
)
code = get_account(evm.env.state, code_address).code
child_message = Message(
caller=caller,
target=to,
gas=U256(gas),
value=value,
data=call_data,
code=code,
current_target=to,
depth=evm.message.depth + 1,
code_address=code_address,
should_transfer_value=should_transfer_value,
is_static=True if is_staticcall else evm.message.is_static,
accessed_addresses=evm.accessed_addresses.copy(),
accessed_storage_keys=evm.accessed_storage_keys.copy(),
)
child_evm = process_message(child_message, evm.env)
if child_evm.has_erred:
incorporate_child_on_error(evm, child_evm)
evm.return_data = child_evm.output
push(evm.stack, U256(0))
else:
incorporate_child_on_success(evm, child_evm)
evm.return_data = child_evm.output
push(evm.stack, U256(1))
actual_output_size = min(memory_output_size, U256(len(child_evm.output)))
memory_write(
evm.memory,
memory_output_start_position,
child_evm.output[:actual_output_size],
)
call
- call(evm: ethereum.berlin.vm.Evm) → None
Message-call into an account.
- Parameters
evm – The current EVM frame.
def call(evm: Evm) -> None:
# STACK
gas = Uint(pop(evm.stack))
to = to_address(pop(evm.stack))
value = pop(evm.stack)
memory_input_start_position = pop(evm.stack)
memory_input_size = pop(evm.stack)
memory_output_start_position = pop(evm.stack)
memory_output_size = pop(evm.stack)
# GAS
extend_memory = calculate_gas_extend_memory(
evm.memory,
[
(memory_input_start_position, memory_input_size),
(memory_output_start_position, memory_output_size),
],
)
if to in evm.accessed_addresses:
access_gas_cost = GAS_WARM_ACCESS
else:
evm.accessed_addresses.add(to)
access_gas_cost = GAS_COLD_ACCOUNT_ACCESS
create_gas_cost = (
Uint(0)
if is_account_alive(evm.env.state, to) or value == 0
else GAS_NEW_ACCOUNT
)
transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE
message_call_gas = calculate_message_call_gas(
value,
gas,
Uint(evm.gas_left),
extend_memory.cost,
access_gas_cost + create_gas_cost + transfer_gas_cost,
)
charge_gas(evm, message_call_gas.cost + extend_memory.cost)
# OPERATION
ensure(not evm.message.is_static or value == U256(0), WriteInStaticContext)
evm.memory += b"\x00" * extend_memory.expand_by
sender_balance = get_account(
evm.env.state, evm.message.current_target
).balance
if sender_balance < value:
push(evm.stack, U256(0))
evm.return_data = b""
evm.gas_left += message_call_gas.stipend
else:
generic_call(
evm,
message_call_gas.stipend,
value,
evm.message.current_target,
to,
to,
True,
False,
memory_input_start_position,
memory_input_size,
memory_output_start_position,
memory_output_size,
)
# PROGRAM COUNTER
evm.pc += 1
callcode
- callcode(evm: ethereum.berlin.vm.Evm) → None
Message-call into this account with alternative account’s code.
- Parameters
evm – The current EVM frame.
def callcode(evm: Evm) -> None:
# STACK
gas = Uint(pop(evm.stack))
code_address = to_address(pop(evm.stack))
value = pop(evm.stack)
memory_input_start_position = pop(evm.stack)
memory_input_size = pop(evm.stack)
memory_output_start_position = pop(evm.stack)
memory_output_size = pop(evm.stack)
# GAS
to = evm.message.current_target
extend_memory = calculate_gas_extend_memory(
evm.memory,
[
(memory_input_start_position, memory_input_size),
(memory_output_start_position, memory_output_size),
],
)
if code_address in evm.accessed_addresses:
access_gas_cost = GAS_WARM_ACCESS
else:
evm.accessed_addresses.add(code_address)
access_gas_cost = GAS_COLD_ACCOUNT_ACCESS
transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE
message_call_gas = calculate_message_call_gas(
value,
gas,
Uint(evm.gas_left),
extend_memory.cost,
access_gas_cost + transfer_gas_cost,
)
charge_gas(evm, message_call_gas.cost + extend_memory.cost)
# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
sender_balance = get_account(
evm.env.state, evm.message.current_target
).balance
if sender_balance < value:
push(evm.stack, U256(0))
evm.return_data = b""
evm.gas_left += message_call_gas.stipend
else:
generic_call(
evm,
message_call_gas.stipend,
value,
evm.message.current_target,
to,
code_address,
True,
False,
memory_input_start_position,
memory_input_size,
memory_output_start_position,
memory_output_size,
)
# PROGRAM COUNTER
evm.pc += 1
selfdestruct
- selfdestruct(evm: ethereum.berlin.vm.Evm) → None
Halt execution and register account for later deletion.
- Parameters
evm – The current EVM frame.
def selfdestruct(evm: Evm) -> None:
# STACK
beneficiary = to_address(pop(evm.stack))
# GAS
if beneficiary not in evm.accessed_addresses:
evm.accessed_addresses.add(beneficiary)
charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS)
if (
not is_account_alive(evm.env.state, beneficiary)
and get_account(evm.env.state, evm.message.current_target).balance != 0
):
charge_gas(evm, GAS_SELF_DESTRUCT + GAS_SELF_DESTRUCT_NEW_ACCOUNT)
else:
charge_gas(evm, GAS_SELF_DESTRUCT)
# OPERATION
ensure(not evm.message.is_static, WriteInStaticContext)
originator = evm.message.current_target
beneficiary_balance = get_account(evm.env.state, beneficiary).balance
originator_balance = get_account(evm.env.state, originator).balance
# First Transfer to beneficiary
set_account_balance(
evm.env.state, beneficiary, beneficiary_balance + originator_balance
)
# Next, Zero the balance of the address being deleted (must come after
# sending to beneficiary in case the contract named itself as the
# beneficiary).
set_account_balance(evm.env.state, originator, U256(0))
# register account for deletion
evm.accounts_to_delete.add(originator)
# mark beneficiary as touched
if account_exists_and_is_empty(evm.env.state, beneficiary):
evm.touched_accounts.add(beneficiary)
# HALT the execution
evm.running = False
# PROGRAM COUNTER
pass
delegatecall
- delegatecall(evm: ethereum.berlin.vm.Evm) → None
Message-call into an account.
- Parameters
evm – The current EVM frame.
def delegatecall(evm: Evm) -> None:
# STACK
gas = Uint(pop(evm.stack))
code_address = to_address(pop(evm.stack))
memory_input_start_position = pop(evm.stack)
memory_input_size = pop(evm.stack)
memory_output_start_position = pop(evm.stack)
memory_output_size = pop(evm.stack)
# GAS
extend_memory = calculate_gas_extend_memory(
evm.memory,
[
(memory_input_start_position, memory_input_size),
(memory_output_start_position, memory_output_size),
],
)
if code_address in evm.accessed_addresses:
access_gas_cost = GAS_WARM_ACCESS
else:
evm.accessed_addresses.add(code_address)
access_gas_cost = GAS_COLD_ACCOUNT_ACCESS
message_call_gas = calculate_message_call_gas(
U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost
)
charge_gas(evm, message_call_gas.cost + extend_memory.cost)
# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
generic_call(
evm,
message_call_gas.stipend,
evm.message.value,
evm.message.caller,
evm.message.current_target,
code_address,
False,
False,
memory_input_start_position,
memory_input_size,
memory_output_start_position,
memory_output_size,
)
# PROGRAM COUNTER
evm.pc += 1
staticcall
- staticcall(evm: ethereum.berlin.vm.Evm) → None
Message-call into an account.
- Parameters
evm – The current EVM frame.
def staticcall(evm: Evm) -> None:
# STACK
gas = Uint(pop(evm.stack))
to = to_address(pop(evm.stack))
memory_input_start_position = pop(evm.stack)
memory_input_size = pop(evm.stack)
memory_output_start_position = pop(evm.stack)
memory_output_size = pop(evm.stack)
# GAS
extend_memory = calculate_gas_extend_memory(
evm.memory,
[
(memory_input_start_position, memory_input_size),
(memory_output_start_position, memory_output_size),
],
)
if to in evm.accessed_addresses:
access_gas_cost = GAS_WARM_ACCESS
else:
evm.accessed_addresses.add(to)
access_gas_cost = GAS_COLD_ACCOUNT_ACCESS
message_call_gas = calculate_message_call_gas(
U256(0),
gas,
Uint(evm.gas_left),
extend_memory.cost,
access_gas_cost,
)
charge_gas(evm, message_call_gas.cost + extend_memory.cost)
# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
generic_call(
evm,
message_call_gas.stipend,
U256(0),
evm.message.current_target,
to,
to,
True,
True,
memory_input_start_position,
memory_input_size,
memory_output_start_position,
memory_output_size,
)
# PROGRAM COUNTER
evm.pc += 1
revert
- revert(evm: ethereum.berlin.vm.Evm) → None
Stop execution and revert state changes, without consuming all provided gas and also has the ability to return a reason :param evm: The current EVM frame.
def revert(evm: Evm) -> None:
# STACK
memory_start_index = pop(evm.stack)
size = pop(evm.stack)
# GAS
extend_memory = calculate_gas_extend_memory(
evm.memory, [(memory_start_index, size)]
)
charge_gas(evm, extend_memory.cost)
# OPERATION
evm.memory += b"\x00" * extend_memory.expand_by
output = memory_read_bytes(evm.memory, memory_start_index, size)
evm.output = bytes(output)
raise Revert
# PROGRAM COUNTER
pass