Skip to content

Commit

Permalink
feat(forks,tests): EIP-2935: Contract update (#1046)
Browse files Browse the repository at this point in the history
* feat(forks,tests): EIP-2935: Contract update

* Update tests/prague/eip2935_historical_block_hashes_from_state/test_contract_deployment.py

Co-authored-by: danceratopz <[email protected]>

* fix: Add storage hint

* refactor(tools): Use `Block` instead of `Tuple`

* fix(tools): pass test_type

* new(tests): Add block hash checking on deployment test

---------

Co-authored-by: danceratopz <[email protected]>
  • Loading branch information
marioevz and danceratopz authored Jan 10, 2025
1 parent 3c60c4f commit 76069fe
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 62 deletions.
5 changes: 3 additions & 2 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ Test fixtures for use by clients are available for each release on the [Github r
-[EIP-7623](https://eips.ethereum.org/EIPS/eip-7623) Increase calldata cost ([#1004](https://github.com/ethereum/execution-spec-tests/pull/1004))
- ✨ Add generic precompile-absence test ([#1036](https://github.com/ethereum/execution-spec-tests/pull/1036))
- ✨ Add test for [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537) which uses the full discount table of G2 MSM ([#1038](https://github.com/ethereum/execution-spec-tests/pull/1038))
- 🔀 Update EIP-7251 according to [spec updates](https://github.com/ethereum/EIPs/pull/9127) ([#1024](https://github.com/ethereum/execution-spec-tests/pull/1024)).
- 🔀 Update EIP-7002 according to [spec updates](https://github.com/ethereum/EIPs/pull/9119) ([#1024](https://github.com/ethereum/execution-spec-tests/pull/1024)).
- 🔀 Update EIP-2935 according to [spec updates](https://github.com/ethereum/EIPs/pull/9144) ([#1046](https://github.com/ethereum/execution-spec-tests/pull/1046))

### 🛠️ Framework

Expand Down Expand Up @@ -67,8 +70,6 @@ Test fixtures for use by clients are available for each release on the [Github r
- ✨ Add a default location for evm logs (`--evm-dump-dir`) when filling tests ([#999](https://github.com/ethereum/execution-spec-tests/pull/999)).
- ✨ Slow tests now have greater timeout when making a request to the T8N server ([#1037](https://github.com/ethereum/execution-spec-tests/pull/1037)).
- ✨ Introduce [`pytest.mark.parametrize_by_fork`](https://ethereum.github.io/execution-spec-tests/main/writing_tests/test_markers/#pytestmarkfork_parametrize) helper marker ([#1019](https://github.com/ethereum/execution-spec-tests/pull/1019), [#1057](https://github.com/ethereum/execution-spec-tests/pull/1057)).
- 🔀 Update EIP-7251 according to [spec updates](https://github.com/ethereum/EIPs/pull/9127) ([#1024](https://github.com/ethereum/execution-spec-tests/pull/1024)).
- 🔀 Update EIP-7002 according to [spec updates](https://github.com/ethereum/EIPs/pull/9119) ([#1024](https://github.com/ethereum/execution-spec-tests/pull/1024)).
- 🐞 fix(consume): allow absolute paths with `--evm-bin` ([#1052](https://github.com/ethereum/execution-spec-tests/pull/1052)).

### 🔧 EVM Tools
Expand Down
Binary file modified src/ethereum_test_forks/forks/contracts/history_contract.bin
Binary file not shown.
4 changes: 2 additions & 2 deletions src/ethereum_test_forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,7 @@ def system_contracts(cls, block_number: int = 0, timestamp: int = 0) -> List[Add
Address(0x00000000219AB540356CBB839CBE05303D7705FA),
Address(0x0C15F14308530B7CDB8460094BBB9CC28B9AAAAA),
Address(0x00431F263CE400F4455C2DCF564E53007CA4BBBB),
Address(0x0AAE40965E6800CD9B1F4B05FF21581047E3F91E),
Address(0x0F792BE4B0C0CB4DAE440EF133E90C0ECD48CCCC),
] + super(Prague, cls).system_contracts(block_number, timestamp)

@classmethod
Expand Down Expand Up @@ -1126,7 +1126,7 @@ def pre_allocation_blockchain(cls) -> Mapping:
with open(CURRENT_FOLDER / "contracts" / "history_contract.bin", mode="rb") as f:
new_allocation.update(
{
0x0AAE40965E6800CD9B1F4B05FF21581047E3F91E: {
0x0F792BE4B0C0CB4DAE440EF133E90C0ECD48CCCC: {
"nonce": 1,
"code": f.read(),
}
Expand Down
3 changes: 2 additions & 1 deletion src/ethereum_test_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
Yul,
YulCompiler,
)
from .utility.generators import generate_system_contract_deploy_test
from .utility.generators import DeploymentTestType, generate_system_contract_deploy_test
from .utility.pytest import extend_with_defaults

__all__ = (
Expand All @@ -108,6 +108,7 @@
"Conditional",
"ConsolidationRequest",
"DepositRequest",
"DeploymentTestType",
"EngineAPIError",
"Environment",
"EOFException",
Expand Down
55 changes: 27 additions & 28 deletions src/ethereum_test_tools/utility/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@
import json
from enum import Enum
from pathlib import Path
from typing import Dict, Generator, List, Protocol, Tuple
from typing import Dict, Generator, List, Protocol

import pytest

from ethereum_test_base_types import Account, Address
from ethereum_test_forks import Fork
from ethereum_test_specs import BlockchainTestFiller
from ethereum_test_specs.blockchain import Block, Header
from ethereum_test_specs.blockchain import Block
from ethereum_test_types import Alloc, Transaction


class DeploymentTestType(Enum):
"""Represents the type of deployment test."""

DEPLOY_BEFORE_FORK = "deploy_before_fork"
DEPLOY_AFTER_FORK = "deploy_after_fork"


class SystemContractDeployTestFunction(Protocol):
"""
Represents a function to be decorated with the `generate_system_contract_deploy_test`
Expand All @@ -25,33 +32,31 @@ def __call__(
*,
fork: Fork,
pre: Alloc,
) -> Generator[Tuple[Transaction | None, Header | None], None, None]:
post: Alloc,
test_type: DeploymentTestType,
) -> Generator[Block, None, None]:
"""
Args:
fork (Fork): The fork to test.
pre (Alloc): The pre state of the blockchain.
post (Alloc): The post state of the blockchain.
test_type (DeploymentTestType): The type of deployment test currently being filled.
Yields:
Tuple[Transaction | None, Header | None]: Once per block to add after the contract is
deployed, with a single transaction to execute and the header object used to
verify the block.
Block: To add after the block where the contract was deployed (e.g. can contain extra
transactions to execute after the system contract has been deployed, and/or a header
object to verify that the headers are correct).
"""
...


class DeploymentTestType(Enum):
"""Represents the type of deployment test."""

DEPLOY_BEFORE_FORK = "deploy_before_fork"
DEPLOY_AFTER_FORK = "deploy_after_fork"


def generate_system_contract_deploy_test(
*,
fork: Fork,
tx_json_path: Path,
expected_deploy_address: Address,
expected_system_contract_storage: Dict | None,
expected_system_contract_storage: Dict | None = None,
):
"""
Generate a test that verifies the correct deployment of a system contract.
Expand All @@ -66,7 +71,8 @@ def generate_system_contract_deploy_test(
contract.
Providing a JSON file is useful to copy-paste the transaction from the EIP.
expected_deploy_address (Address): The expected address of the deployed contract.
expected_system_contract_storage (Dict): The expected storage of the system contract.
expected_system_contract_storage (Dict | None): The expected storage of the system
contract.
"""
with open(tx_json_path, mode="r") as f:
Expand Down Expand Up @@ -126,18 +132,6 @@ def wrapper(
),
]

for tx_header_verify in func(fork=fork, pre=pre):
txs = []
if tx_header_verify[0] is not None:
txs.append(tx_header_verify[0])
header_verify = tx_header_verify[1]
blocks.append(
Block(
txs=txs,
header_verify=header_verify,
)
)

pre[expected_deploy_address] = Account(
code=b"", # Remove the code that is automatically allocated on the fork
nonce=0,
Expand All @@ -149,7 +143,7 @@ def wrapper(

expected_deploy_address_int = int.from_bytes(expected_deploy_address, "big")

post = {}
post = Alloc()
fork_pre_allocation = fork.pre_allocation_blockchain()
assert expected_deploy_address_int in fork_pre_allocation
expected_code = fork_pre_allocation[expected_deploy_address_int]["code"]
Expand All @@ -167,6 +161,11 @@ def wrapper(
post[deployer_address] = Account(
nonce=1,
)

# Extra blocks (if any) returned by the decorated function to add after the
# contract is deployed.
blocks += list(func(fork=fork, pre=pre, post=post, test_type=test_type))

blockchain_test(
pre=pre,
blocks=blocks,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"type": "0x0",
"nonce": "0x0",
"to": null,
"gas": "0x3d090",
"gasPrice": "0xe8d4a51000",
"maxPriorityFeePerGas": null,
"maxFeePerGas": null,
"value": "0x0",
"input": "0x60538060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500",
"v": "0x1b",
"r": "0x539",
"s": "0xbaefe09f0109759",
"hash": "0x8c7bd2d3713a0b2bb693463d2a78c4d612ac47dd38ecb74f8996a4b6fc96f03c"
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ReferenceSpec:
version: str


ref_spec_2935 = ReferenceSpec("EIPS/eip-2935.md", "68d54a80a4f5b9c0cf4ae3a10586d63ef221de36")
ref_spec_2935 = ReferenceSpec("EIPS/eip-2935.md", "a04da454a5a6ba86a87bb9e15f811feaff3c849a")


@dataclass(frozen=True)
Expand All @@ -22,6 +22,6 @@ class Spec:
"""

FORK_TIMESTAMP = 15_000
HISTORY_STORAGE_ADDRESS = 0x0AAE40965E6800CD9B1F4B05FF21581047E3F91E
HISTORY_SERVE_WINDOW = 8192
HISTORY_STORAGE_ADDRESS = 0x0F792BE4B0C0CB4DAE440EF133E90C0ECD48CCCC
HISTORY_SERVE_WINDOW = 8191
BLOCKHASH_OLD_WINDOW = 256
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,9 @@ def test_block_hashes_history(
@pytest.mark.parametrize(
"block_number,reverts",
[
pytest.param(1, False, id="current_block"),
pytest.param(2, False, id="future_block"),
pytest.param(2**64 - 1, False, id="2**64-1"),
pytest.param(1, True, id="current_block"),
pytest.param(2, True, id="future_block"),
pytest.param(2**64 - 1, True, id="2**64-1"),
pytest.param(2**64, True, id="2**64"),
],
)
Expand All @@ -337,13 +337,24 @@ def test_invalid_history_contract_calls(
returned_block_hash_slot = storage.store_next(0)
block_hash_opcode_slot = storage.store_next(0)

return_offset = 64
return_size = 32
args_size = 32

# Check the first block outside of the window if any
code = (
Op.MSTORE(0, block_number)
+ Op.SSTORE(
return_code_slot, Op.CALL(Op.GAS, Spec.HISTORY_STORAGE_ADDRESS, 0, 0, 32, 32, 64)
return_code_slot,
Op.CALL(
address=Spec.HISTORY_STORAGE_ADDRESS,
args_offset=0,
args_size=args_size,
ret_offset=return_offset,
ret_size=return_size,
),
)
+ Op.SSTORE(returned_block_hash_slot, Op.MLOAD(32))
+ Op.SSTORE(returned_block_hash_slot, Op.MLOAD(return_offset))
+ Op.SSTORE(block_hash_opcode_slot, Op.BLOCKHASH(block_number))
)
check_contract_address = pre.deploy_contract(code, storage=storage.canary())
Expand All @@ -364,3 +375,63 @@ def test_invalid_history_contract_calls(
post=post,
reverts=reverts,
)


@pytest.mark.parametrize(
"args_size,reverts",
[
pytest.param(0, True, id="zero_size"),
pytest.param(33, True, id="too_large"),
pytest.param(31, True, id="too_small"),
],
)
@pytest.mark.valid_from("Prague")
def test_invalid_history_contract_calls_input_size(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
reverts: bool,
args_size: int,
):
"""Test calling the history contract with invalid input sizes."""
storage = Storage()

return_code_slot = storage.store_next(not reverts, "history storage call result")
returned_block_hash_slot = storage.store_next(0)

return_offset = 64
return_size = 32
block_number = 0

# Check the first block outside of the window if any
code = (
Op.MSTORE(0, block_number)
+ Op.SSTORE(
return_code_slot,
Op.CALL(
address=Spec.HISTORY_STORAGE_ADDRESS,
args_offset=0,
args_size=args_size,
ret_offset=return_offset,
ret_size=return_size,
),
)
+ Op.SSTORE(returned_block_hash_slot, Op.MLOAD(return_offset))
)
check_contract_address = pre.deploy_contract(code, storage=storage.canary())

txs = [
Transaction(
to=check_contract_address,
gas_limit=10_000_000,
sender=pre.fund_eoa(),
)
]
post = {check_contract_address: Account(storage=storage)}

blocks = [Block(txs=txs)]
blockchain_test(
pre=pre,
blocks=blocks,
post=post,
reverts=reverts,
)
Loading

0 comments on commit 76069fe

Please sign in to comment.