Skip to content
Draft
427 changes: 290 additions & 137 deletions src/ethereum/forks/amsterdam/fork.py

Large diffs are not rendered by default.

92 changes: 0 additions & 92 deletions src/ethereum/forks/amsterdam/utils/message.py

This file was deleted.

63 changes: 44 additions & 19 deletions src/ethereum/forks/amsterdam/vm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,24 @@
Introduction
------------

The abstract computer which runs the code stored in an
The abstract computer that runs the code stored in an
`.fork_types.Account`.

Execution is organized around a single call frame, the [`Evm`][evm]: one
object carrying a call's parameters, its machine state (program counter,
stack, memory), the gas it has left, and the effects it accrues (logs,
refunds, accounts to delete). A call spawns a child frame, and each

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
refunds, accounts to delete). A call spawns a child frame, and each
refunds, accounts to delete). A call (or create) spawns a child frame, and each

top-level call is a frame at depth zero; a transaction executes one or
more such frames, so `.interpreter` drives every frame the same way.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
more such frames, so `.interpreter` drives every frame the same way.
more such frames, so [`interpreter`](ref:ethereum.forks.amsterdam.vm.interpreter) drives every frame the same way.


Each frame meters gas in two pools, bundled in its [`GasMeter`][meter].
Regular gas pays for computation; the state gas reservoir pays the
state-growth costs, and when it is exhausted a charge spills into regular
gas, with a revert refilling both pools in LIFO order so the two never
drift. `.gas` holds both the charges and that pool accounting.

[evm]: ref:ethereum.forks.amsterdam.vm.Evm
[meter]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter
"""

from dataclasses import dataclass, field
Expand All @@ -31,7 +47,12 @@
from ..transactions import LegacyTransaction
from .gas import GasMeter

__all__ = ("Environment", "Evm", "Message")
__all__ = (
"BlockEnvironment",
"BlockOutput",
"TransactionEnvironment",
"Evm",
)
TRANSFER_TOPIC = keccak256(b"Transfer(address,address,uint256)")
SYSTEM_ADDRESS = Address(
bytes.fromhex("fffffffffffffffffffffffffffffffffffffffe")
Expand Down Expand Up @@ -121,9 +142,8 @@ class TransactionEnvironment:
"""

origin: Address
recipient: Bytes0 | Address
value: U256
gas_price: Uint
blob_gas_fee: Uint
gas: Uint
state_gas_reservoir: Uint
access_list_addresses: Set[Address]
Expand All @@ -137,45 +157,50 @@ class TransactionEnvironment:

@final
@dataclass

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, can you make Evm frozen?

class Message:
class Evm:
"""
Items that are used by contract creation or message call.
A single call frame: its parameters, gas meter, machine state, and
accrued effects.

A call spawns a child frame and each top-level call is a frame at
depth zero, so one dataclass describes them all. What was once a
separate ``Message`` -- the call's fixed parameters -- now lives on
the frame directly, alongside its mutable machine state.
"""

# Environment.
block_env: BlockEnvironment
tx_env: TransactionEnvironment

# Call parameters, fixed at frame creation.
caller: Address
target: Bytes0 | Address
current_target: Address

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't really thought about it, but could you take this opportunity to use more clear names for code_address and current_target? executing_as and executing_from, just to throw out some ideas.

gas: Uint
state_gas_reservoir: Uint
code_address: Optional[Address]
value: U256
data: Bytes
code_address: Optional[Address]
code: Optional[Bytes]
depth: Uint
should_transfer_value: bool
is_static: bool
accessed_addresses: Set[Address]
accessed_storage_keys: Set[Tuple[Address, Bytes32]]
disable_precompiles: bool
parent_evm: Optional["Evm"]


@final
@dataclass
class Evm:
"""The internal state of the virtual machine."""

# Machine state, gas meter, and accrued effects. Field order follows
# the predecessor fork's `Evm` for diff stability. The frame's gas --
# its entry grants and everything it consumes -- lives on the
# [`GasMeter`][meter].
#
# [meter]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter
Comment on lines +188 to +193

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments won't get rendered, unless they're in a docstring.

pc: Uint
stack: List[U256]
memory: bytearray
# Init code for a creation; for a call a placeholder that
# `resolve_dispatch` replaces once authorizations are applied.
Comment on lines +197 to +198

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these comments are for a specific member, I think they belong in a docstring under that member, not as a comment.

code: Bytes
gas_meter: GasMeter
valid_jump_destinations: Set[Uint]
logs: Tuple[Log, ...]
running: bool
message: Message
output: Bytes
accounts_to_delete: Set[Address]
return_data: Bytes
Expand Down
33 changes: 14 additions & 19 deletions src/ethereum/forks/amsterdam/vm/eoa_delegation.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
charge_gas,
charge_state_gas,
)
from . import Evm, Message
from . import Evm

SET_CODE_TX_MAGIC = b"\x05"
EOA_DELEGATION_MARKER = b"\xef\x01\x00"
Expand Down Expand Up @@ -141,7 +141,7 @@ def calculate_delegation_cost(
The delegation address and access gas cost.

"""
tx_state = evm.message.tx_env.state
tx_state = evm.tx_env.state

code = get_code(tx_state, get_account(tx_state, address).code_hash)

Expand All @@ -158,18 +158,16 @@ def calculate_delegation_cost(
return True, delegated_address, delegation_gas_cost


def validate_authorization(
message: Message, auth: Authorization
) -> Optional[Address]:
def validate_authorization(evm: Evm, auth: Authorization) -> Optional[Address]:
"""
Check if the given `Authorization` is valid against the current state.

Returns the `authority` address, or `None` if the validation was
unsuccessful.
"""
tx_state = message.tx_env.state
tx_state = evm.tx_env.state

if auth.chain_id not in (message.block_env.chain_id, U256(0)):
if auth.chain_id not in (evm.block_env.chain_id, U256(0)):
return None

if auth.nonce >= U64.MAX_VALUE:
Expand All @@ -180,7 +178,7 @@ def validate_authorization(
except InvalidSignatureError:
return None

message.accessed_addresses.add(authority)
evm.accessed_addresses.add(authority)

authority_account = get_account(tx_state, authority)
authority_code = get_code(tx_state, authority_account.code_hash)
Expand Down Expand Up @@ -231,24 +229,21 @@ def set_delegation(evm: Evm) -> None:
The top-level transaction frame.

"""
message = evm.message
tx_state = message.tx_env.state
tx_state = evm.tx_env.state
# Accounts whose write the transaction has already priced: the
# sender's leaf was written at inclusion (nonce bump and fee
# deduction), and a value-bearing transaction prepays the
# recipient's balance write -- the transfer itself only happens at
# frame entry, after these charges.
written_accounts: Set[Address] = {message.tx_env.origin}
if evm.message.tx_env.value > U256(0):
written_accounts.add(evm.message.current_target)
written_accounts: Set[Address] = {evm.tx_env.origin}
if evm.value > U256(0):
written_accounts.add(evm.current_target)
# Authorities a delegation was set for earlier in this transaction.
delegation_set_for: Set[Address] = set()
for auth in message.tx_env.authorizations:
match validate_authorization(message, auth):
case None:
continue
case authority:
pass
for auth in evm.tx_env.authorizations:
authority = validate_authorization(evm, auth)
if authority is None:
continue

if not account_exists(tx_state, authority):
charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT)
Expand Down
Loading
Loading