Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/testing/src/execution_testing/forks/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Ethereum test fork definitions."""

from .base_fork import ActivationInstall, RefundTypes, SystemCallPhase
from .base_fork import RefundTypes, SystemCallPhase
from .forks.eips.amsterdam.eip_8282 import (
BuilderDepositRequest,
BuilderExitRequest,
Expand Down Expand Up @@ -121,7 +121,6 @@
"TransitionForkAdapter",
"TransitionForkOrNoneAdapter",
"RefundTypes",
"ActivationInstall",
"Amsterdam",
"AmsterdamToBogotaAtTime15k",
"ArrowGlacier",
Expand Down
13 changes: 0 additions & 13 deletions packages/testing/src/execution_testing/forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import re
from abc import ABCMeta, abstractmethod
from dataclasses import dataclass
from enum import Enum, auto
from functools import lru_cache
from typing import (
Expand Down Expand Up @@ -45,18 +44,6 @@
from .requests import SystemContractRequest


@dataclass(frozen=True)
class ActivationInstall:
"""
An `activation_code_installs` entry that, besides writing the code,
raises the account's nonce to at least `min_nonce`, keeping any
higher nonce, the balance and the storage.
"""

code: bytes
min_nonce: int = 0


class MemoryExpansionGasCalculator(Protocol):
"""
A protocol to calculate the gas cost of memory expansion at a given fork.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

Replace the single sender nonce of a frame transaction with a bounded
set of nonce keys sharing one sequence number, the non-zero keys
selecting independent sequences held by a nonce manager system
contract.
selecting independent sequences held by a nonce manager contract.

https://eips.ethereum.org/EIPS/eip-8250
"""
Expand All @@ -16,9 +15,9 @@

from execution_testing.base_types import Bytes

from ....base_fork import ActivationInstall, BaseFork
from ....base_fork import BaseFork

NONCE_MANAGER_ADDRESS = 0x0000000000000000000000000000000000008250
NONCE_MANAGER_ADDRESS = 0x8250968C12E01A19D6F667B9B2F3B3A4D0E51CB7
NONCE_MANAGER_BYTECODE = bytes.fromhex("60006000fd")


Expand All @@ -42,7 +41,7 @@ def _frame_transaction_nonce_bytes(

@classmethod
def pre_allocation(cls) -> Mapping:
"""Pre-allocate the nonce manager as installed at activation."""
"""Pre-allocate the nonce manager contract."""
return {
NONCE_MANAGER_ADDRESS: {
"nonce": 1,
Expand All @@ -51,13 +50,11 @@ def pre_allocation(cls) -> Mapping:
} | super(EIP8250, cls).pre_allocation() # type: ignore

@classmethod
def activation_code_installs(cls) -> Mapping:
"""
Install the nonce manager when the fork activates: its code, and
a nonce of at least one, keeping any balance it already held.
"""
def pre_allocation_blockchain(cls) -> Mapping:
"""Pre-allocate the nonce manager contract."""
return {
NONCE_MANAGER_ADDRESS: ActivationInstall(
code=NONCE_MANAGER_BYTECODE, min_nonce=1
),
} | super(EIP8250, cls).activation_code_installs() # type: ignore
NONCE_MANAGER_ADDRESS: {
"nonce": 1,
"code": NONCE_MANAGER_BYTECODE,
}
} | super(EIP8250, cls).pre_allocation_blockchain() # type: ignore
68 changes: 17 additions & 51 deletions src/ethereum/forks/bogota/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,8 @@
)
from ethereum.forks.amsterdam.blocks import Header as PreviousHeader
from ethereum.merkle_patricia_trie import root, trie_set
from ethereum.state import (
EMPTY_ACCOUNT,
EMPTY_CODE_HASH,
Account,
Address,
BlockDiff,
)
from ethereum.state_mpt import (
State,
apply_changes_to_state,
set_account,
store_code,
)
from ethereum.state import EMPTY_CODE_HASH, Address, BlockDiff
from ethereum.state_mpt import State, apply_changes_to_state

from . import vm
from .block_access_lists import (
Expand Down Expand Up @@ -96,11 +85,7 @@
recover_sender,
validate_transaction,
)
from .transactions.frame_transaction import (
NONCE_MANAGER,
NONCE_MANAGER_CODE,
FrameTransaction,
)
from .transactions.frame_transaction import FrameTransaction
from .utils.address import compute_contract_address
from .utils.hexadecimal import hex_to_address
from .vm.eoa_delegation import is_valid_delegation
Expand Down Expand Up @@ -188,44 +173,25 @@ class BlockChain:

def apply_fork(old: BlockChain) -> BlockChain:
"""
Transform the state from the previous hard fork (`old`) into the
block chain object for this hard fork and return it.
Transforms the state from the previous hard fork (`old`) into the block
chain object for this hard fork and returns it.

The nonce manager is installed when this fork activates (see
[`install_nonce_manager`][inm]).
When forks need to implement an irregular state transition, this function
is used to handle the irregularity. See the :ref:`DAO Fork <dao-fork>` for
an example.

[inm]: ref:ethereum.forks.bogota.fork.install_nonce_manager
"""
install_nonce_manager(old.state)
return old
Parameters
----------
old :
Previous block chain object.

Returns
-------
new : `BlockChain`
Upgraded block chain object for this hard fork.

def install_nonce_manager(state: State) -> None:
"""
Install the nonce manager system contract, as required by
[EIP-8250] when the fork activates.

The account gets [`NONCE_MANAGER_CODE`][nmc] and a nonce of at
least one, keeping any higher nonce and any balance it already
held. The address is chosen to hold no code and no storage before
activation, so the account's storage stays empty.

[EIP-8250]: https://eips.ethereum.org/EIPS/eip-8250
[nmc]: ref:ethereum.forks.bogota.transactions.frame_transaction.NONCE_MANAGER_CODE
""" # noqa: E501
existing_account = state.get_account_optional(NONCE_MANAGER)
if existing_account is None:
existing_account = EMPTY_ACCOUNT

set_account(
state,
NONCE_MANAGER,
Account(
nonce=max(existing_account.nonce, Uint(1)),
balance=existing_account.balance,
code_hash=store_code(state, NONCE_MANAGER_CODE),
),
)
return old


def get_last_256_block_hashes(chain: BlockChain) -> List[Hash32]:
Expand Down
4 changes: 2 additions & 2 deletions src/ethereum/forks/bogota/keyed_nonces.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
A frame transaction selects a set of nonce keys sharing one sequence
number. The legacy key set aliases the sender's account nonce; every
other key selects an independent sequence held in the storage of the
nonce manager system contract, so transactions whose key sets are
disjoint do not order each other.
nonce manager contract, so transactions whose key sets are disjoint do
not order each other.

Keyed nonce reads and writes are protocol bookkeeping: they bypass the
access lists and `SSTORE` pricing of ordinary storage access, and warm
Expand Down
26 changes: 12 additions & 14 deletions src/ethereum/forks/bogota/transactions/frame_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,26 +86,24 @@
"""

NONCE_MANAGER: Final[Address] = Address(
bytes.fromhex("0000000000000000000000000000000000008250")
bytes.fromhex("8250968c12e01a19d6f667b9b2f3b3a4d0e51cb7")
)
"""
Address of the nonce manager system contract, whose storage holds the
Address of the nonce manager contract, whose storage holds the
keyed nonce sequences of every sender (see [`nonce_slot`][ns]).

[ns]: ref:ethereum.forks.bogota.keyed_nonces.nonce_slot
"""
The nonce manager is an ordinary contract, deployed by the pre-signed
creation transaction that [EIP-8250] publishes, from a synthetic sender
whose only transaction it is. The protocol does not install it, and it
must already be in the state when the fork activates. A chain without
it violates the EIP, which leaves keyed transactions on such a chain
undefined, as does this specification. The EIP gives its canonical
runtime code, which reverts on every ordinary call: only the protocol
writes keyed nonces.

NONCE_MANAGER_CODE: Final[Bytes] = Bytes(bytes.fromhex("60006000fd"))
[EIP-8250]: https://eips.ethereum.org/EIPS/eip-8250
[ns]: ref:ethereum.forks.bogota.keyed_nonces.nonce_slot
"""
Runtime code of the nonce manager, installed at [`NONCE_MANAGER`][nm]
when the fork activates (see [`apply_fork`][af]).

The code is `revert(0, 0)`: only the protocol writes keyed nonces, and
any ordinary call to the contract reverts with empty return data.

[nm]: ref:ethereum.forks.bogota.transactions.frame_transaction.NONCE_MANAGER
[af]: ref:ethereum.forks.bogota.fork.apply_fork
""" # noqa: E501

MAX_NONCE_KEYS: Final[Uint] = Uint(16)
"""
Expand Down
2 changes: 1 addition & 1 deletion tests/bogota/eip8250_keyed_nonces/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def verify_only_tx_gas_used(
def nonce_manager_with_slots(pre: Alloc, slots: dict[int, int]) -> None:
"""
Seed the nonce manager's storage in the pre-state, keeping its
activation code and nonce. The test must be `pre_alloc_mutable`.
deployed code and nonce. The test must be `pre_alloc_mutable`.
"""
pre[Spec.NONCE_MANAGER] = Account(
nonce=Spec.NONCE_MANAGER_NONCE,
Expand Down
16 changes: 16 additions & 0 deletions tests/bogota/eip8250_keyed_nonces/nonce_manager_deploy_tx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"type": "0x0",
"nonce": "0x0",
"to": null,
"gas": "0x3d090",
"gasPrice": "0xe8d4a51000",
"maxPriorityFeePerGas": null,
"maxFeePerGas": null,
"value": "0x0",
"input": "0x60058060095f395ff360006000fd",
"v": "0x1b",
"r": "0x539",
"s": "0x825000000000000141de",
"hash": "0x5fb73d939ec0b319de03454f0e787a57543d7edcf889a1a8e3fd0473f8fb6c1f",
"sender": "0x7A57307E9985b0e26B66A7BB920BcaF0396b8198"
}
4 changes: 2 additions & 2 deletions tests/bogota/eip8250_keyed_nonces/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ReferenceSpec:


ref_spec_8250 = ReferenceSpec(
"EIPS/eip-8250.md", "e49b435eb2ddab14b15c56eea93f719d75886f38"
"EIPS/eip-8250.md", "8b225fbc980e98831058a393c0f35df6e878bda0"
)


Expand All @@ -26,7 +26,7 @@ class Spec:
https://eips.ethereum.org/EIPS/eip-8250.
"""

NONCE_MANAGER = Address(0x8250)
NONCE_MANAGER = Address(0x8250968C12E01A19D6F667B9B2F3B3A4D0E51CB7)
NONCE_MANAGER_CODE = bytes.fromhex("60006000fd")
NONCE_MANAGER_NONCE = 1
MAX_NONCE_KEYS = 16
Expand Down
75 changes: 75 additions & 0 deletions tests/bogota/eip8250_keyed_nonces/test_contract_deployment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""
Deployment tests for the nonce manager of
[EIP-8250: Keyed Nonces for Frame Transactions](https://eips.ethereum.org/EIPS/eip-8250).

The nonce manager is an ordinary contract deployed by the pre-signed
creation transaction published in the EIP, from a synthetic sender whose
only transaction it is. The protocol does not install it at activation,
so these tests deploy it with that transaction before, at, and after the
fork block, and check that it lands at `Spec.NONCE_MANAGER` with the
canonical runtime code and that a keyed nonce is then consumed into its
storage.
"""

from os.path import realpath
from pathlib import Path
from typing import Generator

from execution_testing import (
Account,
Alloc,
Block,
DeploymentTestType,
Transaction,
generate_system_contract_deploy_test,
)
from execution_testing.checklists import EIPChecklist
from execution_testing.forks import Bogota, TransitionFork

from ..eip8141_frame_transactions.helpers import verify_frame
from .helpers import NONCE_KEY
from .spec import Spec, keyed_nonce_slot, ref_spec_8250

REFERENCE_SPEC_GIT_PATH = ref_spec_8250.git_path
REFERENCE_SPEC_VERSION = ref_spec_8250.version


@EIPChecklist.SystemContract.Test.Deployment.Address()
@EIPChecklist.SystemContract.Test.Deployment.Missing()
@generate_system_contract_deploy_test(
fork=Bogota,
tx_json_path=Path(realpath(__file__)).parent
/ "nonce_manager_deploy_tx.json",
expected_deploy_address=Spec.NONCE_MANAGER,
fail_on_empty_code=False,
)
def test_nonce_manager_deployment(
*,
fork: TransitionFork,
pre: Alloc,
post: Alloc,
test_type: DeploymentTestType,
) -> Generator[Block, None, None]:
"""
Verify the nonce manager deployment, then consume a keyed nonce into
its storage.

A fork block without the nonce manager is valid: clients do not
check for it at the fork.
"""
sender = pre.fund_eoa()
tx = Transaction(
sender=sender,
frames=[verify_frame()],
nonce_keys=[NONCE_KEY],
nonce=0,
)

yield Block(txs=[tx])

post[Spec.NONCE_MANAGER] = Account(
nonce=Spec.NONCE_MANAGER_NONCE,
code=Spec.NONCE_MANAGER_CODE,
storage={keyed_nonce_slot(sender, NONCE_KEY): 1},
)
post[sender] = Account(nonce=0)
Loading
Loading