diff --git a/src/ethereum_test_base_types/base_types.py b/src/ethereum_test_base_types/base_types.py index c58d29b1e0..8e2261dc39 100644 --- a/src/ethereum_test_base_types/base_types.py +++ b/src/ethereum_test_base_types/base_types.py @@ -181,18 +181,20 @@ class Sized(cls): # type: ignore return Sized - def __new__(cls, input: NumberConvertible | N): + def __new__(cls, input: NumberConvertible | N | None): """ Creates a new Number object. """ - i = to_number(input) - if i > cls.max_value: - raise ValueError(f"Value {i} is too large for {cls.byte_length} bytes") - if i < 0: - i += cls.max_value + 1 - if i <= 0: - raise ValueError(f"Value {i} is too small for {cls.byte_length} bytes") - return super(FixedSizeHexNumber, cls).__new__(cls, i) + if input is not None: + i = to_number(input) + if i > cls.max_value: + raise ValueError(f"Value {i} is too large for {cls.byte_length} bytes") + if i < 0: + i += cls.max_value + 1 + if i <= 0: + raise ValueError(f"Value {i} is too small for {cls.byte_length} bytes") + return super(FixedSizeHexNumber, cls).__new__(cls, i) + return None def __str__(self) -> str: """ diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index 88c9910f12..8232c376e2 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -404,7 +404,9 @@ def call_opcodes( """ At Homestead, DELEGATECALL opcode was introduced. """ - return [(Opcodes.DELEGATECALL, EVMCodeType.LEGACY),] + super( + return [ + (Opcodes.DELEGATECALL, EVMCodeType.LEGACY), + ] + super( Homestead, cls ).call_opcodes(block_number, timestamp) @@ -449,7 +451,9 @@ def call_opcodes( """ At Byzantium, STATICCALL opcode was introduced. """ - return [(Opcodes.STATICCALL, EVMCodeType.LEGACY),] + super( + return [ + (Opcodes.STATICCALL, EVMCodeType.LEGACY), + ] + super( Byzantium, cls ).call_opcodes(block_number, timestamp) @@ -483,7 +487,9 @@ def create_opcodes( """ At Constantinople, `CREATE2` opcode is added. """ - return [(Opcodes.CREATE2, EVMCodeType.LEGACY),] + super( + return [ + (Opcodes.CREATE2, EVMCodeType.LEGACY), + ] + super( Constantinople, cls ).create_opcodes(block_number, timestamp) @@ -974,7 +980,7 @@ def pre_allocation_blockchain(cls) -> Mapping: type tests. """ new_allocation = { - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE: { + Address(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE): { "nonce": 1, "code": ( "0x60203611603157600143035f35116029575f35612000014311602957612000" @@ -1020,7 +1026,10 @@ def evm_code_types(cls, block_number: int = 0, timestamp: int = 0) -> List[EVMCo """ EOF V1 is supported starting from this fork. """ - return super(CancunEIP7692, cls,).evm_code_types( # noqa: SC200 + return super( + CancunEIP7692, + cls, + ).evm_code_types( # noqa: SC200 block_number, timestamp, ) + [EVMCodeType.EOF_V1] @@ -1049,7 +1058,9 @@ def create_opcodes( """ EOF V1 introduces `EOFCREATE`. """ - return [(Opcodes.EOFCREATE, EVMCodeType.EOF_V1),] + super( + return [ + (Opcodes.EOFCREATE, EVMCodeType.EOF_V1), + ] + super( CancunEIP7692, cls # noqa: SC200 ).create_opcodes(block_number, timestamp) @@ -1084,7 +1095,10 @@ def evm_code_types(cls, block_number: int = 0, timestamp: int = 0) -> List[EVMCo """ EOF V1 is supported starting from this fork. """ - return super(PragueEIP7692, cls,).evm_code_types( # noqa: SC200 + return super( + PragueEIP7692, + cls, + ).evm_code_types( # noqa: SC200 block_number, timestamp, ) + [EVMCodeType.EOF_V1] diff --git a/src/ethereum_test_specs/blockchain.py b/src/ethereum_test_specs/blockchain.py index e1f88074a9..d22e930f30 100644 --- a/src/ethereum_test_specs/blockchain.py +++ b/src/ethereum_test_specs/blockchain.py @@ -2,7 +2,7 @@ Ethereum blockchain test spec definition and filler. """ -from pprint import pprint +from pprint import pformat from typing import Any, Callable, ClassVar, Dict, Generator, List, Optional, Tuple, Type from pydantic import ConfigDict, Field, field_validator @@ -48,7 +48,7 @@ Withdrawal, WithdrawalRequest, ) -from ethereum_test_types.verkle import VerkleTree, Witness +from ethereum_test_types.verkle import StateDiff, VerkleTree, Witness, WitnessCheck from evm_transition_tool import TransitionTool from .base import BaseTest, verify_result, verify_transactions @@ -258,6 +258,10 @@ class Block(Header): """ Custom list of requests to embed in this block. """ + witness_check: WitnessCheck | None = None + """ + Verkle execution witness check for the block. + """ def set_environment(self, env: Environment) -> Environment: """ @@ -452,20 +456,52 @@ def generate_block_data( debug_output_path=self.get_next_transition_tool_output_path(), ) - try: + try: # General checks for the transition tool output rejected_txs = verify_transactions(txs, transition_tool_output.result) verify_result(transition_tool_output.result, env) - # TODO: add verify witness (against vkt) - # verify_witness(transition_tool_output.witness, transition_tool_output.vkt) + if block.witness_check: + if transition_tool_output.result.state_diff is None: + raise Exception( + "no state diff in transition tool output, cannot verify witness" + ) + # TODO: hack for now, temp addition to check hist. storage contract + block.witness_check.add_storage_slot( + address=Address(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE), + storage_slot=env.number - 1, + # value=env.parent_hash, + value=None, + ) + self.verify_witness( + t8n=t8n, + state_diff=transition_tool_output.result.state_diff, + witness_check=block.witness_check, + ) except Exception as e: print_traces(t8n.get_traces()) - pprint(transition_tool_output.result) - pprint(previous_alloc) - pprint(transition_tool_output.alloc) + print( + "\nTransition tool output result:\n" + f"{pformat(transition_tool_output.result.model_dump_json())}" + ) + print( + "\nPrevious transition tool alloc:\n" + f"{pformat(previous_alloc.model_dump_json())}" + ) + if transition_tool_output.alloc is not None: + print( + "\nTransition tool output alloc:\n" + f"{pformat(transition_tool_output.alloc.model_dump_json())}" + ) if transition_tool_output.vkt is not None: - pprint(transition_tool_output.vkt) - if transition_tool_output.witness is not None: - pprint(transition_tool_output.witness) + print( + "\nTransition tools output verkle tree:\n" + f"{pformat(transition_tool_output.vkt.model_dump_json())}" + ) + # TODO: t8n has the witness state diff from the result for now + # if transition_tool_output.witness is not None: + # print( + # "\nTransition tools output witness:\n" + # f"{pformat(transition_tool_output.witness.model_dump_json())}" + # ) raise e if len(rejected_txs) > 0 and block.exception is None: @@ -538,7 +574,7 @@ def generate_block_data( ) ) transition_tool_output.alloc = previous_alloc - # TODO: hack for now + # TODO: hack for now, replace with actual witness output once available from t8n transition_tool_output.witness = Witness( verkle_proof=transition_tool_output.result.verkle_proof, state_diff=transition_tool_output.result.state_diff, @@ -587,6 +623,87 @@ def verify_post_state( print_traces(t8n.get_traces()) raise e + def verify_witness( + self, + t8n: TransitionTool, + state_diff: StateDiff, + witness_check: WitnessCheck, + ) -> None: + """ + Compares the expected witness check allocation account against the values updated + in the block execution witness state diff. + """ + witness_check_state_diff, witness_check_address_mapping = t8n.get_witness_check_mapping( + witness_check + ) + print("\nExpected witness check state diff:") + print(witness_check_state_diff.model_dump_json(indent=4)) + + for stem_state_diff in state_diff.root: + actual_stem = stem_state_diff.stem + address = witness_check_address_mapping.get(actual_stem, None) + print(f"\nChecking witness for stem: {actual_stem} at address: {address}") + # check for stem in the expected witness check + expected_stem_state_diff = next( + (sd for sd in witness_check_state_diff.root if sd.stem == actual_stem), None + ) + if not expected_stem_state_diff: + raise ValueError( + "Witness check failed - missing stem not found in expected witness check.\n\n" + + pformat( + { + "test_account_address": str(address), + "stem": str(actual_stem), + }, + indent=4, + ) + ) + for suffix_diff in stem_state_diff.suffix_diffs: + actual_suffix = suffix_diff.suffix + actual_current_value = suffix_diff.current_value + # check for suffix in the expected witness check + expected_suffix_state_diff = next( + ( + sd + for sd in expected_stem_state_diff.suffix_diffs + if sd.suffix == actual_suffix + ), + None, + ) + if not expected_suffix_state_diff: + raise ValueError( + "Witness check failed - actual suffix not found in expected witness" + " check.\n\n" + + pformat( + { + "test_account_address": str(address), + "stem": str(actual_stem), + "suffix": actual_suffix, + "value_actual": str(actual_current_value), + "value_expected": "value not found", + }, + indent=4, + ) + ) + # check the current value of the actual suffix state diff matches the expected + if str(actual_current_value) != str( + expected_suffix_state_diff.current_value + ): # TODO: temp fix str casting + raise ValueError( + "Witness check failed - current value mismatch. The stem and suffix" + " exist.\n\n" + + pformat( + { + "test_account_address": str(address), + "stem": str(actual_stem), + "suffix": actual_suffix, + "value_actual": str(actual_current_value), + "value_expected": str(expected_suffix_state_diff.current_value), + }, + indent=4, + ) + ) + def make_fixture( self, t8n: TransitionTool, @@ -841,4 +958,5 @@ def generate( BlockchainTestSpec = Callable[[str], Generator[BlockchainTest, None, None]] + BlockchainTestFiller = Type[BlockchainTest] diff --git a/src/ethereum_test_tools/__init__.py b/src/ethereum_test_tools/__init__.py index 33720ece20..7d6adb9296 100644 --- a/src/ethereum_test_tools/__init__.py +++ b/src/ethereum_test_tools/__init__.py @@ -46,8 +46,11 @@ Storage, TestParameterGroup, Transaction, + VerkleTree, Withdrawal, WithdrawalRequest, + Witness, + WitnessCheck, add_kzg_version, ceiling_division, compute_create2_address, @@ -136,6 +139,9 @@ "TestPrivateKey2", "Transaction", "TransactionException", + "VerkleTree", + "Witness", + "WitnessCheck", "Withdrawal", "WithdrawalRequest", "Yul", diff --git a/src/ethereum_test_types/__init__.py b/src/ethereum_test_types/__init__.py index b8b0cf5aba..74dd3a138a 100644 --- a/src/ethereum_test_types/__init__.py +++ b/src/ethereum_test_types/__init__.py @@ -30,6 +30,7 @@ Withdrawal, WithdrawalRequest, ) +from .verkle import VerkleTree, Witness, WitnessCheck __all__ = ( "AccessList", @@ -53,8 +54,11 @@ "TestPrivateKey", "TestPrivateKey2", "Transaction", + "VerkleTree", "Withdrawal", "WithdrawalRequest", + "Witness", + "WitnessCheck", "ZeroPaddedHexNumber", "add_kzg_version", "ceiling_division", diff --git a/src/ethereum_test_types/verkle/__init__.py b/src/ethereum_test_types/verkle/__init__.py index 5ef1229e6f..49402a437e 100644 --- a/src/ethereum_test_types/verkle/__init__.py +++ b/src/ethereum_test_types/verkle/__init__.py @@ -2,13 +2,28 @@ Ethereum Verkle Test Types. """ -from .types import IpaProof, StateDiff, SuffixStateDiff, VerkleProof, VerkleTree, Witness +from .helpers import chunkify_code +from .types import ( + IpaProof, + StateDiff, + Stem, + StemStateDiff, + SuffixStateDiff, + VerkleProof, + VerkleTree, + Witness, + WitnessCheck, +) __all__ = ( "IpaProof", "StateDiff", + "StemStateDiff", "SuffixStateDiff", + "Stem", "VerkleProof", "VerkleTree", "Witness", + "WitnessCheck", + "chunkify_code", ) diff --git a/src/ethereum_test_types/verkle/helpers.py b/src/ethereum_test_types/verkle/helpers.py new file mode 100644 index 0000000000..f8f69f9577 --- /dev/null +++ b/src/ethereum_test_types/verkle/helpers.py @@ -0,0 +1,38 @@ +""" +Helper functions/classes used to generate Ethereum tests. +""" + +from typing import List + +from ethereum_test_types.verkle.types import Hash + + +def chunkify_code(code: bytes) -> List[Hash]: + """ + Verkle utility function to chunkify account code into 32-byte chunks. + + Used to generate code chunks for Witness state diff verification. + """ + code = bytes(code) + if len(code) % 31 != 0: + code += b"\x00" * (31 - (len(code) % 31)) + bytes_to_exec_data = [0] * (len(code) + 32) + pos = 0 + while pos < len(code): + # if PUSH1 <= code[pos] <= PUSH32 + if 0x60 <= code[pos] <= 0x7F: + push_data_bytes = code[pos] - 0x5F # PUSH_OFFSET + else: + push_data_bytes = 0 + pos += 1 + for x in range(push_data_bytes): + bytes_to_exec_data[pos + x] = push_data_bytes - x + pos += push_data_bytes + + chunks = [] + for pos in range(0, len(code), 31): + exec_data_prefix = bytes([min(bytes_to_exec_data[pos], 31)]) + chunk = exec_data_prefix + code[pos : pos + 31] + chunk = chunk.ljust(32, b"\x00") + chunks.append(Hash(chunk)) + return chunks diff --git a/src/ethereum_test_types/verkle/tests/__init__.py b/src/ethereum_test_types/verkle/tests/__init__.py new file mode 100644 index 0000000000..08bab39be4 --- /dev/null +++ b/src/ethereum_test_types/verkle/tests/__init__.py @@ -0,0 +1,3 @@ +""" +Tests for the `ethereum_test_types.verkle` package. +""" diff --git a/src/ethereum_test_types/verkle/tests/test_verkle_helpers.py b/src/ethereum_test_types/verkle/tests/test_verkle_helpers.py new file mode 100644 index 0000000000..61b304db0e --- /dev/null +++ b/src/ethereum_test_types/verkle/tests/test_verkle_helpers.py @@ -0,0 +1,77 @@ +""" +Test suite for `ethereum_test_types.verkle.helpers` module. +""" + +import pytest + +from ..helpers import chunkify_code + + +@pytest.mark.parametrize( + "code,expected_chunks", + [ + pytest.param( + "0x0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + [ + "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + ], + id="basic-bytecode-no-padding", + ), + pytest.param( + "0x0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E", + [ + "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e00", + ], + id="bytecode-with-padding", + ), + pytest.param( + "0x0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20212223", + [ + "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "0x0020212223000000000000000000000000000000000000000000000000000000", + ], + id="bytecode-longer-than-31-bytes", + ), + pytest.param( + "0x0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20", + [ + "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "0x0020000000000000000000000000000000000000000000000000000000000000", + ], + id="exactly-32-bytes", + ), + pytest.param( + "0x60FF0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E", + [ + "0x0060ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d", + "0x001e000000000000000000000000000000000000000000000000000000000000", + ], + id="bytecode-with-push1", + ), + pytest.param( + "0x61FFFF0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E", + [ + "0x0061ffff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c", + "0x001d1e0000000000000000000000000000000000000000000000000000000000", + ], + id="bytecode-with-push2", + ), + pytest.param( + "0x01", + [ + "0x0001000000000000000000000000000000000000000000000000000000000000", + ], + id="very-short-bytecode", + ), + ], +) +def test_chunkify_code(code: str, expected_chunks: list[str]): + """ + Test `chunkify_code` from the helpers module. + """ + code_bytes = bytes.fromhex(code[2:]) + expected_chunks_bytes = [bytes.fromhex(chunk[2:]) for chunk in expected_chunks] + result = chunkify_code(code_bytes) + assert len(result) == len(expected_chunks_bytes) + for i in range(len(result)): + assert result[i] == expected_chunks_bytes[i] diff --git a/src/ethereum_test_types/verkle/test_verkle_witness.py b/src/ethereum_test_types/verkle/tests/test_verkle_witness.py similarity index 100% rename from src/ethereum_test_types/verkle/test_verkle_witness.py rename to src/ethereum_test_types/verkle/tests/test_verkle_witness.py diff --git a/src/ethereum_test_types/verkle/types.py b/src/ethereum_test_types/verkle/types.py index 3b703cebe7..cef345423c 100644 --- a/src/ethereum_test_types/verkle/types.py +++ b/src/ethereum_test_types/verkle/types.py @@ -2,12 +2,16 @@ Useful Verkle types for generating Ethereum tests. """ -from typing import Any, Dict, List +from enum import Enum +from typing import Any, Dict, List, Optional, Tuple +from ethereum.crypto.hash import keccak256 from pydantic import Field, RootModel, field_validator from pydantic.functional_serializers import model_serializer -from ethereum_test_base_types import CamelModel, HexNumber, PaddedFixedSizeBytes +from ethereum_test_base_types import Address, CamelModel, HexNumber, PaddedFixedSizeBytes +from ethereum_test_forks import Fork, Verkle +from ethereum_test_types import Account IPA_PROOF_DEPTH = 8 @@ -76,7 +80,7 @@ class SuffixStateDiff(CamelModel): suffix: int current_value: Hash | None - new_value: Hash | None + new_value: Hash | None = Field(None) @model_serializer(mode="wrap") def custom_serializer(self, handler) -> Dict[str, Any]: @@ -148,3 +152,121 @@ class VerkleTree(RootModel[Dict[Hash, Hash]]): """ root: Dict[Hash, Hash] = Field(default_factory=dict) + + +class WitnessCheck: + """ + Definition of a Witness Check. + + Used as an intermediary class to store all the necessary items required to check + during the filling process of a blockchain test. + """ + + version: int = 0 + + class AccountHeaderEntry(Enum): + """ + Represents all the data entries in an account header. + """ + + BASIC_DATA = 0 + CODEHASH = 1 + + def __init__(self, fork: Fork) -> None: + """ + Initializes a WitnessCheck instance. + """ + assert fork >= Verkle, "WitnessCheck is only supported for Verkle fork and later" + self.account_entries: List[ + Tuple[Address, WitnessCheck.AccountHeaderEntry, Optional[Hash]] + ] = [] + self.storage_slots: List[Tuple[Address, int, Optional[Hash]]] = [] + self.code_chunks: List[Tuple[Address, int, Optional[Hash]]] = [] + + # Add the pre-allocation accounts by default + pre_allocations = fork.pre_allocation_blockchain() + for address, account_data in pre_allocations.items(): + self.add_account_full(Address(address), Account(**account_data)) + + def __repr__(self) -> str: + """ + Provides a detailed string representation of the WitnessCheck object for debugging. + """ + return ( + f"WitnessCheck(\n" + f" account_entries={self.account_entries},\n" + f" storage_slots={self.storage_slots},\n" + f" code_chunks={self.code_chunks}\n" + f")" + ) + + def add_account_full(self, address: Address, account: Account | None) -> None: + """ + Adds the address, nonce, balance, and code. Delays actual key computation until later. + """ + self.add_account_basic_data(address, account) + if account: + if account.code: + code_hash = Hash(keccak256(account.code)) + else: # keccak256 of empty byte array + code_hash = Hash( + 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470 + ) + self.add_account_codehash(address, code_hash) + else: + self.add_account_codehash(address, None) + + def add_account_basic_data(self, address: Address, account: Account | None) -> None: + """ + Adds the basic data witness for the given address. + """ + if account is None: + self.account_entries.append( + (address, WitnessCheck.AccountHeaderEntry.BASIC_DATA, None) + ) + else: # Use big-endian encoding for basic data items + basic_data_value = bytearray(32) + + # Set version to 0 (1 byte at offset 0) + basic_data_value[0] = self.version + + # Bytes 1..4 are reserved for future use, leave them as 0 + + # Set code_size (3 bytes at offset 5) + code_size_bytes = len(account.code).to_bytes(3, byteorder="big") + basic_data_value[5:8] = code_size_bytes + + # Set nonce (8 bytes at offset 8) + nonce_bytes = account.nonce.to_bytes(8, byteorder="big") + basic_data_value[8:16] = nonce_bytes + + # Set balance (16 bytes at offset 16) - encode as big-endian + balance_bytes = account.balance.to_bytes(16, byteorder="big") + basic_data_value[16:32] = balance_bytes + + # Append the encoded basic data to account_entries + self.account_entries.append( + ( + address, + WitnessCheck.AccountHeaderEntry.BASIC_DATA, + Hash(bytes(basic_data_value)), + ) + ) + + def add_account_codehash(self, address: Address, codehash: Optional[Hash]) -> None: + """ + Adds the code hash witness for the given address. + """ + self.account_entries.append((address, WitnessCheck.AccountHeaderEntry.CODEHASH, codehash)) + + def add_storage_slot(self, address: Address, storage_slot: int, value: Optional[Hash]) -> None: + """ + Adds the storage slot witness for the given address and storage slot. + """ + self.storage_slots.append((address, storage_slot, value)) + + def add_code_chunk(self, address: Address, chunk_number: int, value: Optional[Hash]) -> None: + """ + Adds the code chunk witness for the given address and chunk number. + """ + self.code_chunks.append((address, chunk_number, value)) diff --git a/src/evm_transition_tool/geth.py b/src/evm_transition_tool/geth.py index 65b8d760dc..6b63691634 100644 --- a/src/evm_transition_tool/geth.py +++ b/src/evm_transition_tool/geth.py @@ -11,12 +11,18 @@ import textwrap from pathlib import Path from re import compile -from typing import Optional +from typing import Dict, List, Optional, Tuple -from ethereum_test_base_types import to_json +from ethereum_test_base_types import Address, Alloc, ZeroPaddedHexNumber, to_json from ethereum_test_forks import Fork -from ethereum_test_types import Alloc -from ethereum_test_types.verkle import VerkleTree +from ethereum_test_types.verkle import ( + StateDiff, + Stem, + StemStateDiff, + SuffixStateDiff, + VerkleTree, + WitnessCheck, +) from .transition_tool import FixtureFormats, TransitionTool, dump_files_to_directory @@ -145,75 +151,131 @@ def verify_fixture( result_json = [] # there is no parseable format for blocktest output return result_json - def get_verkle_state_root(self, mpt_alloc: Alloc) -> bytes: + def _run_verkle_command(self, subcommand: str, *args: str) -> str: + """ + Helper function to run a verkle subcommand and return the output as a string. + """ + command = [ + str(self.binary), + str(self.verkle_subcommand), + subcommand, + *args, + ] + result = subprocess.run( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + if result.returncode != 0: + raise Exception( + f"Failed to run verkle subcommand: '{' '.join(command)}'. " + f"Error: '{result.stderr.decode()}'" + ) + return result.stdout.decode().strip() + + def from_mpt_to_vkt(self, mpt_alloc: Alloc) -> VerkleTree: """ - Returns the VKT state root of from an input MPT. + Returns the verkle tree representation for an input MPT. """ - # Write the MPT alloc to a temporary file: alloc.json with tempfile.TemporaryDirectory() as temp_dir: - input_dir = os.path.join(temp_dir, "input") - os.mkdir(input_dir) - alloc_path = os.path.join(input_dir, "alloc.json") + alloc_path = os.path.join(temp_dir, "alloc.json") with open(alloc_path, "w") as f: json.dump(to_json(mpt_alloc), f) - # Check if the file was created - if not os.path.exists(alloc_path): - raise Exception(f"Failed to create alloc.json at {alloc_path}") - - # Run the verkle subcommand with the alloc.json file as input - command = [ - str(self.binary), - str(self.verkle_subcommand), - "state-root", - "--input.alloc", - alloc_path, - ] - result = subprocess.run( - command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - if result.returncode != 0: - raise Exception( - f"Failed to run verkle subcommand: '{' '.join(command)}'. " - f"Error: '{result.stderr.decode()}'" - ) - hex_string = result.stdout.decode().strip() - return binascii.unhexlify(hex_string[2:]) + output = self._run_verkle_command("tree-keys", "--input.alloc", alloc_path) + return VerkleTree(json.loads(output)) - def from_mpt_to_vkt(self, mpt_alloc: Alloc) -> VerkleTree: + def get_verkle_state_root(self, mpt_alloc: Alloc) -> bytes: """ - Returns the verkle tree representation for an entire MPT alloc using the verkle subcommand. + Returns the VKT state root from an input MPT. """ - # Write the MPT alloc to a temporary file: alloc.json with tempfile.TemporaryDirectory() as temp_dir: - input_dir = os.path.join(temp_dir, "input") - os.mkdir(input_dir) - alloc_path = os.path.join(input_dir, "alloc.json") + alloc_path = os.path.join(temp_dir, "alloc.json") with open(alloc_path, "w") as f: json.dump(to_json(mpt_alloc), f) - # Check if the file was created - if not os.path.exists(alloc_path): - raise Exception(f"Failed to create alloc.json at {alloc_path}") - - # Run the verkle subcommand with the alloc.json file as input - command = [ - str(self.binary), - str(self.verkle_subcommand), - "tree-keys", - "--input.alloc", - alloc_path, - ] - result = subprocess.run( - command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - if result.returncode != 0: - raise Exception( - f"Failed to run verkle subcommand: '{' '.join(command)}'. " - f"Error: '{result.stderr.decode()}'" + hex_string = self._run_verkle_command("state-root", "--input.alloc", alloc_path) + return binascii.unhexlify(hex_string[2:]) + + def get_verkle_single_key( + self, address: Address, storage_slot: Optional[ZeroPaddedHexNumber] = None + ) -> str: + """ + Returns the VKT key for an account address or storage slot. + """ + args = [str(address)] + if storage_slot is not None: + args.append(str(storage_slot)) + output = self._run_verkle_command("single-key", *args) + return output + + def get_verkle_code_chunk_key(self, address: Address, code_chunk: ZeroPaddedHexNumber) -> str: + """ + Returns the VKT key of a code chunk for an account address. + """ + output = self._run_verkle_command("code-chunk-key", str(address), str(code_chunk)) + return output + + def get_witness_check_mapping( + self, witness_check: WitnessCheck + ) -> Tuple[StateDiff, Dict[Stem, Address]]: + """ + Returns a tuple containing: + A) StateDiff - A pseudo StateDiff type with stems, suffixes, and current values. + B) Dict[Stem, Address] - A mapping of stems to their associated addresses. + """ + stem_account_mapping: Dict[Stem, Address] = {} + state_diff: List[StemStateDiff] = [] + + # Format account entries using `evm verkle single-key` + if witness_check.account_entries: + for address, entry, value in witness_check.account_entries: + account_tree_key_str = self.get_verkle_single_key(address) + tree_key = bytearray.fromhex(account_tree_key_str[2:]) + stem = Stem(bytes(tree_key[:-1])) + stem_account_mapping[stem] = address + suffix_state_diff = SuffixStateDiff( + suffix=entry, + current_value=value, + ) + stem_state_diff = next((sd for sd in state_diff if sd.stem == stem), None) + if stem_state_diff is None: + stem_state_diff = StemStateDiff(stem=stem, suffix_diffs=[]) + state_diff.append(stem_state_diff) + stem_state_diff.suffix_diffs.append(suffix_state_diff) + + # Format storage entries using `evm verkle single-key` + if witness_check.storage_slots: + for address, storage_slot, value in witness_check.storage_slots: + storage_tree_key_str = self.get_verkle_single_key( + address, ZeroPaddedHexNumber(storage_slot) + ) + tree_key = bytearray.fromhex(storage_tree_key_str[2:]) + stem = Stem(bytes(tree_key[:-1])) + suffix = int(tree_key[-1]) + stem_account_mapping[stem] = address + suffix_state_diff = SuffixStateDiff(suffix=suffix, current_value=value) + stem_state_diff = next((sd for sd in state_diff if sd.stem == stem), None) + if stem_state_diff is None: + stem_state_diff = StemStateDiff(stem=stem, suffix_diffs=[]) + state_diff.append(stem_state_diff) + stem_state_diff.suffix_diffs.append(suffix_state_diff) + + # Format code chunks using `evm verkle code-chunk-key` + if witness_check.code_chunks: + for address, code_chunk, value in witness_check.code_chunks: + code_chunk_tree_key_str = self.get_verkle_code_chunk_key( + address, ZeroPaddedHexNumber(code_chunk) ) - return VerkleTree(json.loads(result.stdout.decode())) + tree_key = bytearray.fromhex(code_chunk_tree_key_str[2:]) + stem = Stem(bytes(tree_key[:-1])) + suffix = int(tree_key[-1]) + stem_account_mapping[stem] = address + suffix_state_diff = SuffixStateDiff(suffix=suffix, current_value=value) + stem_state_diff = next((sd for sd in state_diff if sd.stem == stem), None) + if stem_state_diff is None: + stem_state_diff = StemStateDiff(stem=stem, suffix_diffs=[]) + state_diff.append(stem_state_diff) + stem_state_diff.suffix_diffs.append(suffix_state_diff) + + return StateDiff(root=state_diff), stem_account_mapping diff --git a/src/evm_transition_tool/transition_tool.py b/src/evm_transition_tool/transition_tool.py index 86cf28ba18..6bd1d94237 100644 --- a/src/evm_transition_tool/transition_tool.py +++ b/src/evm_transition_tool/transition_tool.py @@ -13,12 +13,13 @@ from itertools import groupby from pathlib import Path from re import Pattern -from typing import Dict, List, Mapping, Optional, Type +from typing import Dict, List, Mapping, Optional, Tuple, Type +from ethereum_test_base_types import Address, Alloc, ZeroPaddedHexNumber from ethereum_test_fixtures import FixtureFormats, FixtureVerifier from ethereum_test_forks import Fork -from ethereum_test_types import Alloc, Environment, Transaction -from ethereum_test_types.verkle import VerkleTree +from ethereum_test_types import Environment, Transaction +from ethereum_test_types.verkle import StateDiff, Stem, VerkleTree, WitnessCheck from .file_utils import dump_files_to_directory, write_json_file from .types import TransactionReceipt, TransitionToolInput, TransitionToolOutput @@ -576,23 +577,52 @@ def verify_fixture( "The `verify_fixture()` function is not supported by this tool. Use geth's evm tool." ) + def from_mpt_to_vkt(self, mpt_alloc: Alloc) -> VerkleTree: + """ + Returns the verkle tree representation for an input MPT. + """ + raise NotImplementedError( + "The `from_mpt_to_vkt()` function is not supported by this tool. Use geth's evm tool." + ) + def get_verkle_state_root(self, mpt_alloc: Alloc) -> bytes: """ - Returns the VKT state root of from an input MPT. + Returns the VKT state root from an input MPT. + """ + raise NotImplementedError( + "The `get_verkle_state_root()` function is not supported by this tool. Use geth's evm" + " tool." + ) - Currently only implemented by geth's evm. + def get_verkle_single_key( + self, address: Address, storage_slot: Optional[ZeroPaddedHexNumber] = None + ) -> str: + """ + Returns the VKT key for an account address or storage slot. """ raise NotImplementedError( - "The `get_verkle_state_root` function is not supported by this tool. Use geth's evm " - "tool." + "The `get_verkle_single_key()` function is not supported by this tool. Use geth's evm" + " tool." ) - def from_mpt_to_vkt(self, mpt_alloc: Alloc) -> VerkleTree: + def get_verkle_code_chunk_key(self, address: Address, code_chunk: ZeroPaddedHexNumber) -> str: + """ + Returns the VKT key of a code chunk for an account address. """ - Returns the verkle tree representation for an entire MPT alloc using the verkle subcommand. + raise NotImplementedError( + "The `get_verkle_code_chunk_key()` function is not supported by this tool. Use geth's" + " evm tool." + ) - Currently only implemented by geth's evm. + def get_witness_check_mapping( + self, witness_check: WitnessCheck + ) -> Tuple[StateDiff, Dict[Stem, Address]]: + """ + Returns a tuple containing: + A) StateDiff - A pseudo StateDiff type with stems, suffixes, and current values. + B) Dict[Stem, Address] - A mapping of stems to their associated addresses. """ raise NotImplementedError( - "The `from_mpt_to_vkt` function is not supported by this tool. Use geth's evm tool." + "The `get_witness_check_mapping()` function is not supported by this tool. Use geth's" + " evm tool." ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_balance.py b/tests/verkle/eip4762_verkle_gas_witness/test_balance.py index a2c1aed52e..967b0b1769 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_balance.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_balance.py @@ -7,6 +7,7 @@ import pytest +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, Address, @@ -16,21 +17,20 @@ TestAddress, TestAddress2, Transaction, + WitnessCheck, ) from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_forks import Fork +from ethereum_test_types.verkle.helpers import chunkify_code -# from ..temp_verkle_helpers import Witness - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" precompile_address = Address("0x04") -system_contract_address = Address("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") +system_contract_address = Address("0xfffffffffffffffffffffffffffffffffffffffe") example_address = Address("0xd94f5374fce5edbc8e2a8697c15331677e6ebf0c") -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "target", @@ -41,28 +41,27 @@ ], ) @pytest.mark.parametrize("warm", [True, False]) -def test_balance(blockchain_test: BlockchainTestFiller, fork: str, target, warm): +def test_balance(blockchain_test: BlockchainTestFiller, fork: Fork, target, warm): """ Test BALANCE witness with/without WARM access. """ - _balance(blockchain_test, fork, target, [target], warm=warm) + _balance(blockchain_test, fork, target, True, warm=warm) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize("target", [example_address, precompile_address]) -def test_balance_insufficient_gas(blockchain_test: BlockchainTestFiller, fork: str, target): +def test_balance_insufficient_gas(blockchain_test: BlockchainTestFiller, fork: Fork, target): """ Test BALANCE with insufficient gas. """ - _balance(blockchain_test, fork, target, [], 21_042) + _balance(blockchain_test, fork, target, False, 21_042) def _balance( blockchain_test: BlockchainTestFiller, - fork: str, + fork: Fork, target: Address, - exp_addr_basic_data: list[Address], + exp_target_basic_data: bool, gas_limit=1_000_000, warm=False, ): @@ -76,10 +75,12 @@ def _balance( pre = { TestAddress: Account(balance=1000000000000000000000), TestAddress2: Account(code=Op.BALANCE(target) * (2 if warm else 1) + Op.PUSH0 + Op.SSTORE), - target: Account(balance=0xF1), - precompile_address: Account(balance=0xF2), + precompile_address: Account(balance=0xF0), } + if target != precompile_address and target != system_contract_address: + pre[target] = Account(balance=0xF2) + tx = Transaction( ty=0x0, chain_id=0x01, @@ -88,23 +89,40 @@ def _balance( gas_limit=gas_limit, gas_price=10, ) - blocks = [Block(txs=[tx])] + + witness_check = WitnessCheck(fork=Verkle) + for address in [TestAddress, TestAddress2, env.fee_recipient]: + witness_check.add_account_full(address=address, account=pre.get(address)) + + code_chunks = chunkify_code(pre[TestAddress2].code) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=TestAddress2, chunk_number=i, value=chunk) + + witness_check.add_storage_slot(address=TestAddress2, storage_slot=0, value=None) + + target_account = ( + pre[target] + if target != system_contract_address + else Account(**fork.pre_allocation_blockchain()[system_contract_address]) + ) + + if exp_target_basic_data: + witness_check.add_account_basic_data(address=target, account=target_account) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] post = { - TestAddress2: Account(code=pre[TestAddress2].code, storage={0: pre[target].balance}), + TestAddress2: Account(code=pre[TestAddress2].code, storage={0: target_account.balance}), } - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # witness.add_account_full(TestAddress2, pre[TestAddress2]) - # for addr in exp_addr_basic_data: - # witness.add_account_basic_data(addr, pre[addr]) - blockchain_test( genesis_environment=env, pre=pre, post=post, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_calls.py b/tests/verkle/eip4762_verkle_gas_witness/test_calls.py index 5c5a72447a..2d10d6c321 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_calls.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_calls.py @@ -7,32 +7,32 @@ import pytest +from ethereum_test_forks import Verkle +from ethereum_test_forks import Fork from ethereum_test_tools import ( Account, Address, Alloc, Block, - Bytecode, BlockchainTestFiller, + Bytecode, Environment, TestAddress, TestAddress2, Transaction, + WitnessCheck, ) from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_types.verkle.helpers import chunkify_code -# from ..temp_verkle_helpers import Witness - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" caller_address = Address("0xd94f5374fce5edbc8e2a8697c15331677e6ebf0c") -system_contract_address = Address("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") +system_contract_address = Address("0xfffffffffffffffffffffffffffffffffffffffe") precompile_address = Address("0x04") -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "call_instruction, value", @@ -56,7 +56,7 @@ ) def test_calls( blockchain_test: BlockchainTestFiller, - fork: str, + fork: Fork, call_instruction: Bytecode, target: Address, value, @@ -67,7 +67,6 @@ def test_calls( _generic_call(blockchain_test, fork, call_instruction, target, value) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "call_instruction", @@ -78,14 +77,13 @@ def test_calls( Op.STATICCALL, ], ) -def test_calls_warm(blockchain_test: BlockchainTestFiller, fork: str, call_instruction: Bytecode): +def test_calls_warm(blockchain_test: BlockchainTestFiller, fork: Fork, call_instruction: Bytecode): """ Test *CALL warm cost. """ _generic_call(blockchain_test, fork, call_instruction, TestAddress2, 0, warm=True) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.skip("Pending TBD gas limits") @pytest.mark.parametrize( @@ -116,7 +114,7 @@ def test_calls_warm(blockchain_test: BlockchainTestFiller, fork: str, call_instr ], ) def test_calls_insufficient_gas( - blockchain_test: BlockchainTestFiller, fork: str, call_instruction: Bytecode, gas_limit + blockchain_test: BlockchainTestFiller, fork: Fork, call_instruction: Bytecode, gas_limit ): """ Test *CALL witness assertion when there's insufficient gas for different scenarios. @@ -134,7 +132,7 @@ def test_calls_insufficient_gas( def _generic_call( blockchain_test: BlockchainTestFiller, - fork: str, + fork: Fork, call_instruction, target: Address, value, @@ -164,7 +162,6 @@ def _generic_call( balance=2000000000000000000000, code=caller_code * (2 if warm else 1) ), precompile_address: Account(balance=3000000000000000000000), - system_contract_address: Account(balance=4000000000000000000000), } tx = Transaction( @@ -176,37 +173,59 @@ def _generic_call( gas_price=10, value=tx_value, ) - blocks = [Block(txs=[tx])] + target_account = ( + pre[target] + if target != system_contract_address + else Account(**fork.pre_allocation_blockchain()[system_contract_address]) + ) + witness_check = WitnessCheck(fork=Verkle) + for address in [TestAddress, caller_address, env.fee_recipient]: + witness_check.add_account_full(address=address, account=pre.get(address)) + if enough_gas_read_witness: + if value > 0 or (target != precompile_address and target != precompile_address): + witness_check.add_account_basic_data(address=target, account=target_account) + + code_chunks = chunkify_code(pre[caller_address].code) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=caller_address, chunk_number=i, value=chunk) + if target != precompile_address: + code_chunks = chunkify_code(target_account.code) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=target, chunk_number=i, value=chunk) + + if target == system_contract_address: + # If the target is the 2935 system contract, we should check for the first storage-slot. + # The account storage address depends on the kind of *CALL done. + sslot_target = system_contract_address if call_instruction == Op.CALL else caller_address + witness_check.add_storage_slot(address=sslot_target, storage_slot=0, value=None) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] if call_instruction == Op.DELEGATECALL: post = { caller_address: Account( balance=pre[caller_address].balance + value, code=pre[caller_address].code ), - target: pre[target], + target: target_account, } else: post = { - target: Account(balance=pre[target].balance + value, code=pre[target].code), + target: Account(balance=target_account.balance + value, code=target_account.code), } - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # witness.add_account_full(caller_address, pre[caller_address]) - # if target != precompile_address and enough_gas_read_witness: - # witness.add_account_basic_data(target, pre[target]) - blockchain_test( genesis_environment=env, pre=pre, post=post, blocks=blocks, - # witness=witness, ) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "call_instruction, gas_limit, enough_gas_account_creation", @@ -219,13 +238,12 @@ def _generic_call( ) def test_call_non_existent_account( blockchain_test: BlockchainTestFiller, - fork: str, call_instruction, gas_limit: int, enough_gas_account_creation: bool, ): """ - Test *CALL witness assertion when there's insufficient gas for different scenarios. + Test *CALL witness assertion when target account does not exist. """ env = Environment( fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", @@ -253,26 +271,32 @@ def test_call_non_existent_account( gas_limit=gas_limit, gas_price=10, ) - blocks = [Block(txs=[tx])] - post: Alloc = Alloc() + witness_check = WitnessCheck(fork=Verkle) + for address in [TestAddress, caller_address, env.fee_recipient]: + witness_check.add_account_full(address=address, account=pre.get(address)) + + code_chunks = chunkify_code(pre[caller_address].code) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=caller_address, chunk_number=i, value=chunk) + if enough_gas_account_creation: - post = Alloc( - { - TestAddress2: Account(balance=call_value), - } + witness_check.add_account_full(address=TestAddress2, account=None) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, ) + ] - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # witness.add_account_full(caller_address, pre[caller_address]) - # witness.add_account_basic_data(TestAddress2, None) + post: Alloc = Alloc() + if enough_gas_account_creation: + post = Alloc({TestAddress2: Account(balance=call_value)}) blockchain_test( genesis_environment=env, pre=pre, post=post, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_ext_precompile.py b/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_ext_precompile.py index 2bcd1807ac..ecb64d2335 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_ext_precompile.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_ext_precompile.py @@ -7,29 +7,30 @@ import pytest +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, Address, Block, BlockchainTestFiller, Environment, - Initcode, TestAddress, + TestAddress2, Transaction, + WitnessCheck, ) from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_types.verkle.helpers import chunkify_code +from ethereum_test_forks import Fork -# from ..temp_verkle_helpers import Witness -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" precompile_address = Address("0x04") -system_contract_address = Address("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") +system_contract_address = Address("0xfffffffffffffffffffffffffffffffffffffffe") -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "target", @@ -38,7 +39,7 @@ system_contract_address, ], ) -def test_extcodecopy_precompile(blockchain_test: BlockchainTestFiller, fork: str, target): +def test_extcodecopy_precompile(blockchain_test: BlockchainTestFiller, fork: Fork, target): """ Test EXTCODECOPY targeting a precompile or system contract. """ @@ -51,29 +52,44 @@ def test_extcodecopy_precompile(blockchain_test: BlockchainTestFiller, fork: str ) pre = { TestAddress: Account(balance=1000000000000000000000), + TestAddress2: Account( + balance=1000000000000000000000, code=Op.EXTCODECOPY(target, 0, 0, 10) + ), } tx = Transaction( ty=0x0, chain_id=0x01, nonce=0, - to=None, + to=TestAddress2, gas_limit=1_000_000, gas_price=10, - data=Initcode(deploy_code=Op.EXTCODECOPY(target, 0, 0, 100)), ) - blocks = [Block(txs=[tx])] - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # No basic data for target. - # No code chunks. + witness_check = WitnessCheck(fork=Verkle) + for address in [env.fee_recipient, TestAddress, TestAddress2]: + witness_check.add_account_full(address=address, account=pre.get(address)) + code_chunks = chunkify_code(pre[TestAddress2].code) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=TestAddress2, chunk_number=i, value=chunk) + + if target == system_contract_address: + code = Account(**fork.pre_allocation_blockchain()[system_contract_address]).code + code_chunks = chunkify_code(code) + witness_check.add_code_chunk( + address=system_contract_address, chunk_number=0, value=code_chunks[0] + ) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] blockchain_test( genesis_environment=env, pre=pre, post={}, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_generic.py b/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_generic.py index 43105ee102..a517bf8dd9 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_generic.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_generic.py @@ -7,31 +7,27 @@ import pytest +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, Address, Block, - Bytecode, BlockchainTestFiller, Environment, - Initcode, TestAddress, TestAddress2, Transaction, - # compute_create_address, + WitnessCheck, ) from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_types.verkle.helpers import chunkify_code -# from ..temp_verkle_helpers import vkt_chunkify, Witness - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -code_size = 200 * 31 + 60 +code_size = 130 * 31 + 60 -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "instruction", @@ -48,6 +44,7 @@ (0, 128 * 31), (0, code_size - 5), (0, code_size), + (code_size // 2, code_size * 2), (code_size - 1, 1), (code_size, 1), (code_size - 1, 1 + 1), @@ -59,37 +56,35 @@ "all_chunks_account_header", "contract_size_after_header_but_incomplete", "contract_size", + "bigger_than_contract_size", "last_byte", "all_out_of_bounds", "partial_out_of_bounds_in_same_last_code_chunk", "partial_out_of_bounds_touching_further_non_existent_code_chunk", ], ) -def test_generic_codecopy( - blockchain_test: BlockchainTestFiller, fork: str, instruction, offset, size -): +def test_generic_codecopy(blockchain_test: BlockchainTestFiller, instruction, offset, size): """ Test *CODECOPY witness. """ - start = offset if offset < size else size + start = offset if offset < code_size else code_size end = offset + size if offset + size < code_size else code_size witness_code_chunks = range(0, 0) - if start < size and start != end: + if start < code_size and start != end: start_chunk = start // 31 - end_chunk = (end - 1) // 31 + end_chunk = end // 31 witness_code_chunks = range(start_chunk, end_chunk + 1) _generic_codecopy( blockchain_test, - fork, instruction, offset, size, witness_code_chunks, + witness_target_basic_data=True, ) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "instruction", @@ -98,23 +93,23 @@ def test_generic_codecopy( Op.EXTCODECOPY, ], ) -def test_generic_codecopy_warm(blockchain_test: BlockchainTestFiller, fork: str, instruction): +def test_generic_codecopy_warm(blockchain_test: BlockchainTestFiller, instruction): """ Test *CODECOPY with WARM access. """ - witness_code_chunks = range(0, (code_size - 5) // 31 + 1) + code_len = 150 + witness_code_chunks = range(0, code_len // 31 + 1) _generic_codecopy( blockchain_test, - fork, instruction, 0, - code_size - 5, + code_len, witness_code_chunks, + witness_target_basic_data=True, warm=True, ) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.skip("Pending to fill TBD gas limit") @pytest.mark.parametrize( @@ -128,25 +123,23 @@ def test_generic_codecopy_warm(blockchain_test: BlockchainTestFiller, fork: str, "partial_code_range", ], ) -# TODO(verkle): consider reusing code from test_generic_codecopy.py. def test_codecopy_insufficient_gas( - blockchain_test: BlockchainTestFiller, fork: str, gas_limit, witness_code_chunks + blockchain_test: BlockchainTestFiller, gas_limit, witness_code_chunks ): """ Test CODECOPY with insufficient gas. """ _generic_codecopy( blockchain_test, - fork, Op.CODECOPY, 0, code_size, witness_code_chunks, + witness_target_basic_data=True, gas_limit=gas_limit, ) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.skip("Pending to fill TBD gas limit") @pytest.mark.parametrize( @@ -162,10 +155,8 @@ def test_codecopy_insufficient_gas( "partial_code_range", ], ) -# TODO(verkle): consider reusing code from test_generic_codecopy.py. def test_extcodecopy_insufficient_gas( blockchain_test: BlockchainTestFiller, - fork: str, gas_limit, witness_target_basic_data, witness_code_chunks, @@ -175,7 +166,6 @@ def test_extcodecopy_insufficient_gas( """ _generic_codecopy( blockchain_test, - fork, Op.CODECOPY, 0, code_size, @@ -187,15 +177,15 @@ def test_extcodecopy_insufficient_gas( def _generic_codecopy( blockchain_test: BlockchainTestFiller, - fork: str, instr: Op, offset: int, size: int, witness_code_chunks, - witness_target_basic_data=True, + witness_target_basic_data, warm=False, gas_limit=1_000_000, ): + dummy_address = Address("0xffff19589531694250d570040a0c4b74576919b8") env = Environment( fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", difficulty=0x20000, @@ -203,20 +193,20 @@ def _generic_codecopy( number=1, timestamp=1000, ) + + to = TestAddress2 + if instr == Op.EXTCODECOPY: + to = dummy_address + repeat = 2 if warm else 1 - codecopy_code = Op.CODECOPY(0, offset, size) * repeat + codecopy_code = Op.CODECOPY(0, offset, size) * repeat + Op.STOP + extcodecopy_code = Op.EXTCODECOPY(TestAddress2, 0, offset, size) * repeat pre = { TestAddress: Account(balance=1000000000000000000000), TestAddress2: Account(code=codecopy_code + Op.PUSH0 * (code_size - len(codecopy_code))), + dummy_address: Account(code=extcodecopy_code), } - to: Address | None = TestAddress2 - data = Bytecode() - if instr == Op.EXTCODECOPY: - to = None - extcodecopy_code = Op.EXTCODECOPY(TestAddress2, 0, offset, size) * repeat - data = Initcode(deploy_code=extcodecopy_code) - tx = Transaction( ty=0x0, chain_id=0x01, @@ -224,28 +214,47 @@ def _generic_codecopy( to=to, gas_limit=gas_limit, gas_price=10, - data=data, ) - blocks = [Block(txs=[tx])] - # tx_target_addr = ( - # TestAddress2 if instr == Op.CODECOPY else compute_create_address(TestAddress, 0) - # ) - - # code_chunks = vkt_chunkify(pre[TestAddress2].code) - - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # witness.add_account_full(tx_target_addr, pre[tx_target_addr]) - # if witness_target_basic_data: - # witness.add_account_basic_data(TestAddress2, pre[TestAddress2]) - # for chunk_num in witness_code_chunks: - # witness.add_code_chunk(TestAddress2, chunk_num, code_chunks[chunk_num]) + + witness_check = WitnessCheck(fork=Verkle) + for address in [TestAddress, to, env.fee_recipient]: + witness_check.add_account_full(address=address, account=pre.get(address)) + + # Add code-chunks related to CODECOPY/EXTCODECOPY execution. + code_chunks = chunkify_code(pre[to].code) + executed_code_len = ( + # In CODECOPY, not all the code is executed, since we right-padded with dummy push0-s + # so we can copy more code than actually executed. If we didn't do that, we can't + # distinguish between the code in the witness that was executed and the code that + # was copied. + (len(codecopy_code) + 31) // 32 + if instr == Op.CODECOPY + # In EXTCODECOPY, all the code is executed since we're copying code from an external + # account. + else (len(extcodecopy_code) + 31) // 32 + ) + for i in range(executed_code_len): + witness_check.add_code_chunk(to, i, code_chunks[i]) + + if instr == Op.EXTCODECOPY: + witness_check.add_account_basic_data(address=TestAddress2, account=pre.get(TestAddress2)) + + # Depending on the CODECOPY/EXTCODECOPY offset and size, we include the extra expected + # code-chunks. + code_chunks = chunkify_code(pre[TestAddress2].code) + for chunk_num in witness_code_chunks: + witness_check.add_code_chunk(TestAddress2, chunk_num, code_chunks[chunk_num]) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] blockchain_test( genesis_environment=env, pre=pre, - post={}, blocks=blocks, - # witness=witness, + post={}, ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_generic_initcode.py b/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_generic_initcode.py index d2ee7769c5..5aa1e886d4 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_generic_initcode.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_generic_initcode.py @@ -7,26 +7,23 @@ import pytest +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, Block, BlockchainTestFiller, Environment, - Initcode, TestAddress, Transaction, + WitnessCheck, compute_create_address, ) from ethereum_test_tools.vm.opcode import Opcodes as Op -# from ..temp_verkle_helpers import Witness - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "instruction", @@ -52,10 +49,9 @@ def test_generic_codecopy_initcode(blockchain_test: BlockchainTestFiller, fork: contract_address = compute_create_address(address=TestAddress, nonce=0) if instruction == Op.EXTCODECOPY: - deploy_code = Op.EXTCODECOPY(contract_address, 0, 0, 100) + Op.ORIGIN * 100 - data = Initcode(deploy_code=deploy_code) + data = Op.EXTCODECOPY(contract_address, 0, 0, 100) + Op.ORIGIN * 100 else: - data = Initcode(deploy_code=Op.CODECOPY(0, 0, 100) + Op.ORIGIN * 100) + data = Op.CODECOPY(0, 0, 100) + Op.ORIGIN * 100 tx = Transaction( ty=0x0, @@ -66,18 +62,21 @@ def test_generic_codecopy_initcode(blockchain_test: BlockchainTestFiller, fork: gas_price=10, data=data, ) - blocks = [Block(txs=[tx])] - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # witness.add_account_full(contract_address, None) - # No code chunks. + witness_check = WitnessCheck(fork=Verkle) + for address in [TestAddress, contract_address, env.fee_recipient]: + witness_check.add_account_full(address=address, account=pre.get(address)) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] blockchain_test( genesis_environment=env, pre=pre, post={}, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_coinbase_fees.py b/tests/verkle/eip4762_verkle_gas_witness/test_coinbase_fees.py index 77810758c4..7065b52954 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_coinbase_fees.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_coinbase_fees.py @@ -7,6 +7,7 @@ import pytest +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, Address, @@ -16,19 +17,16 @@ TestAddress, TestAddress2, Transaction, + WitnessCheck, ) -# from ..temp_verkle_helpers import Witness - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize("priority_fee", [0, 100]) -def test_coinbase_fees(blockchain_test: BlockchainTestFiller, fork: str, priority_fee): +def test_coinbase_fees(blockchain_test: BlockchainTestFiller, priority_fee): """ Test coinbase witness. """ @@ -52,20 +50,26 @@ def test_coinbase_fees(blockchain_test: BlockchainTestFiller, fork: str, priorit gas_limit=1_000_000, gas_price=priority_fee, ) - blocks = [Block(txs=[tx])] # TODO(verkle): change value when filling post = {} if priority_fee == 0 else {coinbase_addr: Account(balance=0x42)} - # witness = Witness() - # witness.add_account_full(coinbase_addr, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # witness.add_account_full(TestAddress2, pre[TestAddress2]) + witness_check = WitnessCheck(fork=Verkle) + witness_check.add_account_full(address=TestAddress, account=pre[TestAddress]) + witness_check.add_account_full(address=TestAddress2, account=None) + if priority_fee > 0: + witness_check.add_account_full(address=coinbase_addr, account=None) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] blockchain_test( genesis_environment=env, pre=pre, post=post, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_contract_execution.py b/tests/verkle/eip4762_verkle_gas_witness/test_contract_execution.py index 7b46764055..d596bd2b05 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_contract_execution.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_contract_execution.py @@ -1,245 +1,329 @@ -# """ -# abstract: Tests [EIP-4762: Statelessness gas cost changes] -# (https://eips.ethereum.org/EIPS/eip-4762) -# Tests for [EIP-4762: Statelessness gas cost changes] -# (https://eips.ethereum.org/EIPS/eip-4762). -# """ - -# import pytest - -# from ethereum_test_tools import ( -# Account, -# Address, -# Block, -# BlockchainTestFiller, -# Environment, -# TestAddress, -# TestAddress2, -# Transaction, -# ) -# from ethereum_test_tools.vm.opcode import Opcodes as Op - -# from ..temp_verkle_helpers import Witness, vkt_chunkify - -# # TODO(verkle): Update reference spec version -# REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" -# REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" - -# precompile_address = Address("0x04") - - -# class Jump: -# """ -# Represents a JUMP instruction to be inserted in the code. -# """ - -# def __init__(self, offset, pc): -# self.offset = offset -# self.pc = pc - - -# class Jumpi(Jump): -# """ -# Represents a JUMPI instruction to be inserted in the code. -# """ - -# def __init__(self, offset, pc, condition): -# Jump.__init__(self, offset, pc) -# self.condition = condition - - -# def code_with_jumps(size, jumps: list[Jump | Jumpi] = []): -# """ -# Returns the requested code with defined size, jumps and PUSHNs -# """ -# code = Op.PUSH0 * size -# for j in jumps: -# if isinstance(j, Jump): -# jump_code = Op.JUMP(j.pc) -# code = code[: j.offset] + jump_code + code[j.offset + len(jump_code) :] -# elif isinstance(j, Jumpi): -# jumpi_code = Op.JUMPI(j.pc, 1 if j.condition else 0) -# code = code[: j.offset] + jumpi_code + code[j.offset + len(jumpi_code) :] - -# code[j.pc] = Op.JUMPDEST - -# return code - - -# # TODO(verkle): update to Osaka when t8n supports the fork. -# @pytest.mark.valid_from("Verkle") -# @pytest.mark.parametrize( -# "bytecode, gas_limit, exp_code_chunk_ranges", -# [ -# ( # only_code_in_account_header -# code_with_jumps(10), -# 1_000_000, -# [[0, 0]], -# ), -# ( # chunks_both_in_and_out_account_header -# code_with_jumps(128 * 31 + 100), -# 1_000_000, -# [[0, 132]], -# ), -# ( # touches_only_first_byte_code_chunk -# code_with_jumps(128 * 31 + 1), -# 1_000_000, -# [[0, 129]], -# ), -# ( # touches_only_last_byte_code_chunk -# code_with_jumps(128 * 31 + 100, [Jump(10, 128 * 31 + 100)]), -# 1_000_000, -# [[0, 0], [132, 132]], -# ), -# ( # pushn_with_data_in_chunk_that_cant_be_paid -# Op.PUSH0 * 30 + Op.PUSH1(42), -# 42, -# [[0, 0]], -# ), -# ( # jump_to_jumpdest_in_pushn_data -# Op.PUSH0 * 10 + Op.JUMP(10 + 2 + 1 + 1000) + Op.PUSH0 * 1000 + Op.PUSH1(0x5B), -# 1_000_000, -# [[0, 0], [33, 33]], -# ), -# ( # jumpi_to_jumpdest_in_pushn_data -# Op.PUSH0 * 10 + Op.JUMPI(10 + 3 + 1 + 1000, 1) + Op.PUSH0 * 1000 + Op.PUSH1(0x5B), -# 1_000_000, -# [[0, 0], [33, 33]], -# ), -# ( # jump_to_non_jumpdest_destiny -# Op.PUSH0 * 10 + Op.JUMP(10 + 2 + 1 + 1000) + Op.PUSH0 * 1000 + Op.ORIGIN, -# 1_000_000, -# [[0, 0], [33, 33]], -# ), -# ( # jumpi_to_non_jumpdest_destiny -# Op.PUSH0 * 10 + Op.JUMPI(10 + 3 + 1 + 1000, 1) + Op.PUSH0 * 1000 + Op.ORIGIN, -# 1_000_000, -# [[0, 0], [33, 33]], -# ), -# ( # linear_execution_stopping_at_first_byte_of_next_chunk -# code_with_jumps(128 * 31 + 1), -# 1_000_000, -# [[0, 129]], -# ), -# ( # false_jumpi -# code_with_jumps(150 * 31 + 10, [Jumpi(50, 1000, False)]), -# 1_000_000, -# [[0, 151]], -# ), -# ( # insufficient_gas_for_jump_instruction -# code_with_jumps(150 * 31, [Jump(50, 1000)]), -# 142, -# [[0, 1]], -# ), -# ( # insufficient_gas_for_jumpi_instruction -# code_with_jumps(150 * 31, [Jumpi(50, 1000, True)]), -# 142, -# [[0, 1]], -# ), -# ( # sufficient_gas_for_jump_instruction_but_not_for_code_chunk -# code_with_jumps(150 * 31, [Jump(50, 1000)]), -# 42, -# [[0, 0], [33, 33]], -# ), -# ( # sufficient_gas_for_jumpi_instruction_but_not_for_code_chunk -# code_with_jumps(150 * 31, [Jumpi(50, 1000, True)]), -# 42, -# [[0, 0], [33, 33]], -# ), -# ( # jump_outside_code_size -# code_with_jumps(150 * 31, [Jump(50, 150 * 31 + 42)]), -# 1_000_000, -# [[0, 0]], -# ), -# ( # jumpi_outside_code_size -# code_with_jumps(150 * 31, [Jumpi(50, 150 * 31 + 42, True)]), -# 1_000_000, -# [[0, 0]], -# ), -# ( # push20 with data split in two chunks -# Op.PUSH0 * (31 - (1 + 10)) + Op.PUSH20(0xAA), -# 1_000_000, -# [[0, 1]], -# ), -# ( # push32 spanning three chunks -# Op.PUSH0 * (31 - 1) + Op.PUSH32(0xAA), -# 1_000_000, -# [[0, 2]], -# ), -# ( # pushn with expected data past code size -# Op.PUSH0 * (31 - 5) + Op.PUSH20, -# 1_000_000, -# [[0, 0]], -# ), -# ], -# ids=[ -# "only_code_in_account_header", -# "chunks_both_in_and_out_account_header", -# "touches_only_first_byte_code_chunk", -# "touches_only_last_byte_code_chunk", -# "pushn_with_data_in_chunk_that_cant_be_paid", -# "jump_to_jumpdest_in_pushn_data", -# "jumpi_to_jumpdest_in_pushn_data", -# "jump_to_non_jumpdest_destiny", -# "jumpi_to_non_jumpdest_destiny", -# "linear_execution_stopping_at_first_byte_of_next_chunk", -# "false_jumpi", -# "insufficient_gas_for_jump_instruction", -# "insufficient_gas_for_jumpi_instruction", -# "sufficient_gas_for_jump_instruction_but_not_for_code_chunk", -# "sufficient_gas_for_jumpi_instruction_but_not_for_code_chunk", -# "jump_outside_code_size", -# "jumpi_outside_code_size", -# "push20_with_data_split_in_two_chunks", -# "push32_spanning_three_chunks", -# "pushn_with_expected_data_past_code_size", -# ], -# ) -# def test_contract_execution( -# blockchain_test: BlockchainTestFiller, -# fork: str, -# bytecode, -# gas_limit, -# witness_code_chunk_numbers, -# ): -# """ -# Test that contract execution generates expected witness. -# """ -# env = Environment( -# fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", -# difficulty=0x20000, -# gas_limit=10000000000, -# number=1, -# timestamp=1000, -# ) -# pre = { -# TestAddress: Account(balance=1000000000000000000000), -# TestAddress2: Account(code=bytecode), -# } -# tx = Transaction( -# ty=0x0, -# chain_id=0x01, -# nonce=0, -# to=TestAddress2, -# gas_limit=gas_limit, -# gas_price=10, -# ) -# blocks = [Block(txs=[tx])] - -# code_chunks = vkt_chunkify(bytecode) -# assert len(code_chunks) > 1 - -# witness = Witness() -# witness.add_account_full(env.fee_recipient, None) -# witness.add_account_full(TestAddress, pre[TestAddress]) -# witness.add_account_full(TestAddress2, pre[TestAddress2]) -# for chunk_number in witness_code_chunk_numbers: -# witness.add_code_chunk(TestAddress2, chunk_number, code_chunks[chunk_number]) - -# blockchain_test( -# genesis_environment=env, -# pre=pre, -# post={}, -# blocks=blocks, -# witness=witness, -# ) +""" +abstract: Tests [EIP-4762: Statelessness gas cost changes] +(https://eips.ethereum.org/EIPS/eip-4762) + Tests for [EIP-4762: Statelessness gas cost changes] + (https://eips.ethereum.org/EIPS/eip-4762). +""" + +import pytest + +from ethereum_test_forks import Verkle +from ethereum_test_tools import ( + Account, + Address, + Block, + BlockchainTestFiller, + Environment, + TestAddress, + TestAddress2, + Transaction, + WitnessCheck, +) +from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_types.verkle.helpers import chunkify_code +from ethereum_test_forks import Fork + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" +REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" + +precompile_address = Address("0x04") + + +class Jump: + """ + Represents a JUMP instruction to be inserted in the code. + """ + + def __init__(self, offset, pc): + self.offset = offset + self.pc = pc + + +class Jumpi(Jump): + """ + Represents a JUMPI instruction to be inserted in the code. + """ + + def __init__(self, offset, pc, condition): + Jump.__init__(self, offset, pc) + self.condition = condition + + +def code_with_jumps(size, jumps: list[Jump | Jumpi] = []): + """ + Returns the requested code with defined size, jumps, and PUSHNs. + """ + code = Op.JUMPDEST * size + result_code = bytes() + last_offset = 0 + for j in jumps: + result_code += bytes(code)[last_offset : j.offset] + if isinstance(j, Jumpi): + jump_code = bytes(Op.JUMPI(j.pc, 1 if j.condition else 0)) + elif isinstance(j, Jump): + jump_code = bytes(Op.JUMP(j.pc)) + result_code += jump_code + last_offset = j.offset + len(jump_code) + + result_code += bytes(code)[last_offset:] + + assert len(result_code) == size + + return result_code + + +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "bytecode, gas_limit, witness_code_chunk_ranges", + [ + ( # only_code_in_account_header + code_with_jumps(10), + 1_000_000, + [[0, 0]], + ), + ( # chunks_both_in_and_out_account_header + code_with_jumps(128 * 31 + 100), + 1_000_000, + [[0, 131]], + ), + ( # touches_only_first_byte_code_chunk + code_with_jumps(128 * 31 + 1), + 1_000_000, + [[0, 128]], + ), + ( # touches_only_last_byte_code_chunk + code_with_jumps(128 * 31 + 100, [Jump(10, 128 * 31 + 99)]), + 1_000_000, + [[0, 0], [131, 131]], + ), + ( # pushn_with_data_in_chunk_that_cant_be_paid + Op.PUSH0 * 30 + Op.PUSH1(42), + 21000, + [[0, 0]], + ), + ( # jump_to_jumpdest_in_pushn_data + Op.PUSH0 * 10 + + Op.JUMP(10 + 3 + 1 + 1000 + 1) # 3+1=PUSH2+JUMP + + Op.PUSH0 * 1000 + + Op.PUSH1(0x5B) + + Op.PUSH0 * 100, # Add more code, but can't be executed due to the invalid jump + 1_000_000, + [[0, 0], [32, 32]], + ), + ( # jumpi_to_jumpdest_in_pushn_data + bytes( + Op.PUSH0 * 10 + + Op.JUMPI(10 + 5 + 1 + 1000 + 1, 1) # 5+1=PUSH1+PUSH2+JUMPI + + Op.PUSH0 * 1000 + + Op.PUSH1(0x5B) + + Op.PUSH0 * 100 + ), + 1_000_000, + [[0, 0], [32, 32]], + ), + ( # jump_to_non_jumpdest_destiny + bytes( + Op.PUSH0 * 10 + Op.JUMP(10 + 3 + 1 + 1000) + Op.PUSH0 * 1000 + Op.ORIGIN + ), # 3+1=PUSH2+JUMP + 1_000_000, + [[0, 0], [32, 32]], + ), + ( # jumpi_to_non_jumpdest_destiny + bytes( + Op.PUSH0 * 10 + Op.JUMPI(10 + 5 + 1 + 1000, 1) + Op.PUSH0 * 1000 + Op.ORIGIN + ), # 5+1=PUSH1+PUSH2+JUMPI + 1_000_000, + [[0, 0], [32, 32]], + ), + ( # linear_execution_stopping_at_first_byte_of_next_chunk + code_with_jumps(128 * 31 + 1), + 1_000_000, + [[0, 128]], + ), + ( # false_jumpi + code_with_jumps(150 * 31 + 10, [Jumpi(50, 1000, False)]), + 1_000_000, + [[0, 150]], + ), + ( # insufficient_gas_for_jump_instruction + code_with_jumps(150 * 31, [Jump(10, 1000)]), + 21000 + 200 + 10 + 3, + [[0, 0]], + ), + ( # insufficient_gas_for_jumpi_instruction + code_with_jumps(150 * 31, [Jumpi(10, 1000, True)]), + 21000 + 200 + 10 + 3 + 3, + [[0, 0]], + ), + ( # sufficient_gas_for_jump_instruction_but_not_for_code_chunk + code_with_jumps(150 * 31, [Jump(10, 1000)]), + 21000 + 200 + 10 + 3 + 8, + [[0, 0]], + ), + ( # sufficient_gas_for_jumpi_instruction_but_not_for_code_chunk + code_with_jumps(150 * 31, [Jumpi(10, 1000, True)]), + 21000 + 200 + 10 + 3 + 3 + 10, + [[0, 0]], + ), + ( # jump_outside_code_size + code_with_jumps(150 * 31, [Jump(10, 150 * 31 + 42)]), + 1_000_000, + [[0, 0]], + ), + ( # jumpi_outside_code_size + code_with_jumps(150 * 31, [Jumpi(50, 150 * 31 + 42, True)]), + 1_000_000, + [[0, 1]], + ), + ( # push20 with data split in two chunks + Op.PUSH0 * (31 - (1 + 10)) + Op.PUSH20(0xAA), + 1_000_000, + [[0, 1]], + ), + ( # push32 spanning three chunks + Op.PUSH0 * (31 - 1) + Op.PUSH32(0xAA), + 1_000_000, + [[0, 2]], + ), + ( # pushn with expected data past code size + Op.PUSH0 * (31 - 5) + Op.PUSH20, + 1_000_000, + [[0, 0]], + ), + ], + ids=[ + "only_code_in_account_header", + "chunks_both_in_and_out_account_header", + "touches_only_first_byte_code_chunk", + "touches_only_last_byte_code_chunk", + "pushn_with_data_in_chunk_that_cant_be_paid", + "jump_to_jumpdest_in_pushn_data", + "jumpi_to_jumpdest_in_pushn_data", + "jump_to_non_jumpdest_destiny", + "jumpi_to_non_jumpdest_destiny", + "linear_execution_stopping_at_first_byte_of_next_chunk", + "false_jumpi", + "insufficient_gas_for_jump_instruction", + "insufficient_gas_for_jumpi_instruction", + "sufficient_gas_for_jump_instruction_but_not_for_code_chunk", + "sufficient_gas_for_jumpi_instruction_but_not_for_code_chunk", + "jump_outside_code_size", + "jumpi_outside_code_size", + "push20_with_data_split_in_two_chunks", + "push32_spanning_three_chunks", + "pushn_with_expected_data_past_code_size", + ], +) +def test_contract_execution( + blockchain_test: BlockchainTestFiller, + bytecode: bytes, + gas_limit: int, + witness_code_chunk_ranges, +): + """ + Test that contract execution generates expected witness. + """ + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + TestAddress2: Account(code=bytecode), + } + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=TestAddress2, + gas_limit=gas_limit, + gas_price=10, + ) + + code_chunks = chunkify_code(bytecode) + assert len(code_chunks) > 0 + + witness_check = WitnessCheck(fork=Verkle) + for address in [TestAddress, TestAddress2, env.fee_recipient]: + witness_check.add_account_full( + address=address, + account=pre.get(address), + ) + for chunk_ranges in witness_code_chunk_ranges: + for chunk_number in range(chunk_ranges[0], chunk_ranges[1] + 1): + witness_check.add_code_chunk(TestAddress2, chunk_number, code_chunks[chunk_number]) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] + + blockchain_test( + genesis_environment=env, + pre=pre, + post={}, + blocks=blocks, + ) + + +@pytest.mark.valid_from("Verkle") +def test_contract_execution_2935_contract(blockchain_test: BlockchainTestFiller, fork: Fork): + """ + Test that contract execution for 2935 system contract includes code-chunks + """ + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + addr = Address("0xfffffffffffffffffffffffffffffffffffffffe") + pre = { + TestAddress: Account(balance=1000000000000000000000), + TestAddress2: Account(code=Op.PUSH1(0) + Op.CALL(1_000, addr, 0, 0, 0, 0, 32)), + } + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=TestAddress2, + gas_limit=1_000_000, + gas_price=10, + ) + + witness_check = WitnessCheck(fork=Verkle) + for address in [TestAddress, TestAddress2, env.fee_recipient]: + witness_check.add_account_full(address=address, account=pre.get(address)) + + # Add TestAddress2 code chunks (i.e: CALL to system contract) + code_chunks = chunkify_code(pre[TestAddress2].code) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=TestAddress2, chunk_number=i, value=chunk) + + # TestAddress2 CALL to 2935 contract + system_contract_account = Account(**fork.pre_allocation_blockchain()[addr]) + witness_check.add_account_basic_data(address=addr, account=system_contract_account) + # A successful execution of the 2935 contract would only include the first two code-chunks. + code_chunks = chunkify_code(system_contract_account.code) + witness_check.add_code_chunk(address=addr, chunk_number=0, value=code_chunks[0]) + witness_check.add_code_chunk(address=addr, chunk_number=1, value=code_chunks[1]) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] + + blockchain_test( + genesis_environment=env, + pre=pre, + post={}, + blocks=blocks, + ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_creates.py b/tests/verkle/eip4762_verkle_gas_witness/test_creates.py index a5434ea9e0..0deba0ddea 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_creates.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_creates.py @@ -7,30 +7,29 @@ import pytest +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, Block, BlockchainTestFiller, - Environment, - Opcode, Bytecode, + Environment, Initcode, + Opcode, TestAddress, TestAddress2, Transaction, + WitnessCheck, compute_create2_address, compute_create_address, ) from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_types.verkle.helpers import chunkify_code -from ..temp_verkle_helpers import Witness, vkt_chunkify - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "create_instruction", @@ -41,55 +40,59 @@ ], ) @pytest.mark.parametrize( - "code_size, value", + "code_size", [ - (0, 0), - (127 * 32, 0), - (130 * 32, 0), - (130 * 32 + 1, 0), - (130 * 32 + 1, 1), + 0, + 127 * 31, + 130 * 31, + 130 * 31 + 1, ], ids=[ "empty", "all_chunks_in_account_header", "chunks_outside_account_header", "with_partial_code_chunk", - "with_value", ], ) -def test_create( - blockchain_test: BlockchainTestFiller, fork: str, create_instruction: Opcode, value, code_size -): +def test_create(blockchain_test: BlockchainTestFiller, create_instruction: Opcode, code_size): """ Test tx contract creation and *CREATE witness. """ - contract_code = Op.PUSH0 * code_size - if create_instruction is None or create_instruction == Op.CREATE: - contract_address = compute_create_address(address=TestAddress, nonce=0) - else: - contract_address = compute_create2_address( - TestAddress, 0xDEADBEEF, Initcode(deploy_code=contract_code) - ) + contract_code = bytes(Op.PUSH0 * code_size) + _create( + blockchain_test, + create_instruction, + contract_code, + value=0, + ) - num_code_chunks = (len(contract_code) + 30) // 31 - code_chunks = vkt_chunkify(contract_code) - witness_extra = Witness() - # witness_extra.add_account_full(contract_address, None) - # for i in range(num_code_chunks): - # witness_extra.add_code_chunk(contract_address, i, code_chunks[i]) +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "create_instruction", + [ + Op.CREATE, + Op.CREATE2, + ], +) +def test_create_with_value_insufficient_balance( + blockchain_test: BlockchainTestFiller, + create_instruction: Opcode, +): + """ + Test tx contract creation and *CREATE value-bearing without sufficient balance. + """ + contract_code = bytes(Op.PUSH0 * 10) _create( blockchain_test, - fork, create_instruction, - witness_extra, contract_code, - value=value, + value=100, # TODO(verkle): generalize with value>0 and enough balance? + creator_balance=0, ) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.skip("Pending TBD gas limits") @pytest.mark.parametrize( @@ -117,7 +120,6 @@ def test_create( ) def test_create_insufficient_gas( blockchain_test: BlockchainTestFiller, - fork: str, create_instruction, gas_limit, witness_basic_data: bool, @@ -128,36 +130,16 @@ def test_create_insufficient_gas( Test *CREATE with insufficient gas at different points of execution. """ contract_code = Op.PUSH0 * (129 * 31 + 42) - if create_instruction is None or create_instruction == Op.CREATE: - contract_address = compute_create_address(address=TestAddress, nonce=0) - else: - contract_address = compute_create2_address( - TestAddress, 0xDEADBEEF, Initcode(deploy_code=contract_code) - ) - - code_chunks = vkt_chunkify(contract_code) - - witness_extra = Witness() - if witness_basic_data and witness_codehash: - witness_extra.add_account_full(contract_address, None) - for i in range(witness_chunk_count): - witness_extra.add_code_chunk(contract_address, i, code_chunks[i]) - elif witness_basic_data and not witness_codehash: - witness_extra.add_account_basic_data(contract_address, None) - # No code chunks since we failed earlier. _create( blockchain_test, - fork, create_instruction, - witness_extra, contract_code, value=0, gas_limit=gas_limit, ) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.skip("Pending TBD gas limits") @pytest.mark.parametrize( @@ -169,7 +151,6 @@ def test_create_insufficient_gas( ) def test_create_static_cost( blockchain_test: BlockchainTestFiller, - fork: str, create_instruction, gas_limit, ): @@ -178,16 +159,13 @@ def test_create_static_cost( """ _create( blockchain_test, - fork, create_instruction, - Witness(), # Static cost failure means the created contract shouldn't be in the witness Op.PUSH0 * (129 * 31 + 42), value=0, gas_limit=gas_limit, ) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "create_instruction", @@ -197,26 +175,19 @@ def test_create_static_cost( Op.CREATE2, ], ) -def test_create_collision( - blockchain_test: BlockchainTestFiller, - fork: str, - create_instruction, -): +def test_create_collision(blockchain_test: BlockchainTestFiller, create_instruction): """ - Test *CREATE with address collision. + Test tx contract creation and *CREATE with address collision. """ _create( blockchain_test, - fork, create_instruction, - Witness(), # Collision means the created contract shouldn't be in the witness - Op.PUSH0 * (129 * 31 + 42), + Op.PUSH0 * (3 * 31 + 42), value=0, generate_collision=True, ) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "create_instruction", @@ -226,32 +197,18 @@ def test_create_collision( Op.CREATE2, ], ) -def test_big_calldata( +def test_create_big_calldata( blockchain_test: BlockchainTestFiller, - fork: str, create_instruction, ): """ Test *CREATE checking that code-chunk touching in the witness is not calculated from calldata size but actual returned code from initcode execution. """ - contract_code = Op.PUSH0 * (1000 * 31 + 42) - if create_instruction is None or create_instruction == Op.CREATE: - contract_address = compute_create_address(address=TestAddress, nonce=0) - else: - contract_address = compute_create2_address( - TestAddress, 0xDEADBEEF, Initcode(deploy_code=contract_code) - ) - - witness_extra = Witness() - witness_extra.add_account_full(contract_address, None) - # No code chunks since we do an immediate STOP in the Initcode. - + contract_code = bytes(Op.PUSH0 * (1000 * 31 + 42)) _create( blockchain_test, - fork, create_instruction, - Witness(), contract_code, value=0, initcode_stop_prefix=True, @@ -260,14 +217,13 @@ def test_big_calldata( def _create( blockchain_test: BlockchainTestFiller, - fork: str, create_instruction: Opcode | None, - witness_extra: Witness, contract_code, - value=1, + value: int = 0, gas_limit=10000000000, generate_collision: bool = False, initcode_stop_prefix: bool = False, + creator_balance: int = 0, ): env = Environment( fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", @@ -277,7 +233,7 @@ def _create( timestamp=1000, ) pre = { - TestAddress: Account(balance=1000000000000000000000), + TestAddress: Account(balance=1_000_000_000_000), } deploy_code = Initcode( @@ -285,31 +241,33 @@ def _create( ) if create_instruction is not None and create_instruction.int() == Op.CREATE.int(): pre[TestAddress2] = Account( - code=Op.CALLDATACOPY(0, 0, len(deploy_code)) + Op.CREATE(value, 0, len(deploy_code)) + code=Op.CALLDATACOPY(0, 0, len(deploy_code)) + Op.CREATE(value, 0, len(deploy_code)), + balance=creator_balance, ) tx_target = TestAddress2 tx_value = 0 tx_data = deploy_code + contract_address = compute_create_address(address=TestAddress2, nonce=0) if generate_collision: - contract_address = compute_create_address(address=TestAddress, nonce=0) pre[contract_address] = Account(nonce=1) elif create_instruction is not None and create_instruction.int() == Op.CREATE2.int(): pre[TestAddress2] = Account( code=Op.CALLDATACOPY(0, 0, len(deploy_code)) - + Op.CREATE2(value, 0, len(deploy_code), 0xDEADBEEF) + + Op.CREATE2(value, 0, len(deploy_code), 0xDEADBEEF), + balance=creator_balance, ) tx_target = TestAddress2 tx_value = 0 tx_data = deploy_code + contract_address = compute_create2_address(TestAddress2, 0xDEADBEEF, deploy_code) if generate_collision: - contract_address = compute_create2_address(TestAddress, 0xDEADBEEF, deploy_code) pre[contract_address] = Account(nonce=1) else: tx_target = None tx_value = value tx_data = deploy_code + contract_address = compute_create_address(address=TestAddress, nonce=0) if generate_collision: - contract_address = compute_create_address(address=TestAddress, nonce=0) pre[contract_address] = Account(nonce=1) tx = Transaction( @@ -322,19 +280,37 @@ def _create( value=tx_value, data=tx_data, ) - blocks = [Block(txs=[tx])] - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # if tx_target is not None: - # witness.add_account_full(tx_target, pre[tx_target]) - # witness.merge(witness_extra) + witness_check = WitnessCheck(fork=Verkle) + witness_check.add_account_full(env.fee_recipient, None) + witness_check.add_account_full(TestAddress, pre[TestAddress]) + if tx_target is not None: + witness_check.add_account_full(tx_target, pre[tx_target]) + # Include code that executes the CREATE* + code_chunks = chunkify_code(pre[tx_target].code) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=tx_target, chunk_number=i, value=chunk) + + # The contract address will always appear in the witness: + # - If there's a collision, it should contain the existing contract for the collision check. + # - Otherwise, it should prove there's no collision. + witness_check.add_account_full(contract_address, pre.get(contract_address)) + # Assert the code-chunks where the contract is deployed are provided + if not generate_collision: + code_chunks = chunkify_code(bytes(deploy_code.deploy_code)) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=contract_address, chunk_number=i, value=None) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] blockchain_test( genesis_environment=env, pre=pre, post={}, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_extcodehash.py b/tests/verkle/eip4762_verkle_gas_witness/test_extcodehash.py index 3b3ff09c0f..e3172e1b58 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_extcodehash.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_extcodehash.py @@ -8,6 +8,7 @@ import pytest from ethereum.crypto.hash import keccak256 +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, Address, @@ -18,17 +19,17 @@ TestAddress, TestAddress2, Transaction, + WitnessCheck, ) from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_types.verkle.types import Hash as HashVerkle +from ethereum_test_types.verkle.helpers import chunkify_code -from ..temp_verkle_helpers import Witness - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" precompile_address = Address("0x04") -system_contract_address = Address("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") +system_contract_address = Address("0xfffffffffffffffffffffffffffffffffffffffe") EmptyAddress = Address("0xd94f5374fce5edbc8e2a8697c15331677e6ebf0c") TestAccount = Account(balance=1000000000000000000000) @@ -37,7 +38,6 @@ ExampleAccount = Account(code=Op.PUSH0 * 300) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "target", @@ -60,31 +60,34 @@ def test_extcodehash(blockchain_test: BlockchainTestFiller, fork: str, target): """ Test EXTCODEHASH witness. """ - witness = Witness() + witness_check_extra = WitnessCheck(fork=Verkle) if target == ExampleAddress: - witness.add_account_full(ExampleAddress, ExampleAccount) + witness_check_extra.add_account_codehash( + ExampleAddress, HashVerkle(keccak256(ExampleAccount.code)) + ) elif target == TestAddress: - witness.add_account_basic_data(TestAddress, TestAccount) + witness_check_extra.add_account_codehash( + TestAddress, HashVerkle(keccak256(TestAccount.code)) + ) elif target == EmptyAddress: - witness.add_account_basic_data(EmptyAddress, None) + witness_check_extra.add_account_codehash(EmptyAddress, None) # For precompile or system contract, we don't need to add any witness. - - _extcodehash(blockchain_test, fork, target, witness) + _extcodehash(blockchain_test, target, witness_check_extra) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") -def test_extcodehash_warm(blockchain_test: BlockchainTestFiller, fork: str): +def test_extcodehash_warm(blockchain_test: BlockchainTestFiller): """ Test EXTCODEHASH with WARM cost. """ - witness = Witness() - witness.add_account_full(ExampleAddress, ExampleAccount) + witness_check_extra = WitnessCheck(fork=Verkle) + witness_check_extra.add_account_codehash( + ExampleAddress, HashVerkle(keccak256(ExampleAccount.code)) + ) - _extcodehash(blockchain_test, fork, ExampleAddress, witness, warm=True) + _extcodehash(blockchain_test, ExampleAddress, witness_check_extra, warm=True) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.skip("Pending TBD gas limits") @pytest.mark.parametrize( @@ -100,7 +103,6 @@ def test_extcodehash_warm(blockchain_test: BlockchainTestFiller, fork: str): ) def test_extcodehash_insufficient_gas( blockchain_test: BlockchainTestFiller, - fork: str, gas_limit: int, witness_assert_basic_data, witness_assert_codehash, @@ -108,20 +110,21 @@ def test_extcodehash_insufficient_gas( """ Test EXTCODEHASH with insufficient gas. """ - witness = Witness() + witness_check_extra = WitnessCheck(fork=Verkle) if witness_assert_basic_data: - witness.add_account_basic_data(ExampleAddress, ExampleAccount) + witness_check_extra.add_account_basic_data(ExampleAddress, ExampleAccount) if witness_assert_codehash: - witness.add_account_codehash(ExampleAddress, Hash(keccak256(ExampleAccount.code))) + witness_check_extra.add_account_codehash( + ExampleAddress, Hash(keccak256(ExampleAccount.code)) # type: ignore + ) - _extcodehash(blockchain_test, fork, ExampleAddress, witness, gas_limit, fails=True) + _extcodehash(blockchain_test, ExampleAddress, witness_check_extra, gas_limit, fails=True) def _extcodehash( blockchain_test: BlockchainTestFiller, - fork: str, target, - extra_witness, + witness_check_extra: WitnessCheck, gas_limit=1_000_000, warm=False, fails=False, @@ -149,23 +152,32 @@ def _extcodehash( gas_limit=gas_limit, gas_price=10, ) - blocks = [Block(txs=[tx])] post = {} if not fails: - # TODO(verkle): assign correct storage slot value when filling post[TestAddress2] = Account(code=pre[TestAddress2].code, storage={0: 0x424242}) - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # witness.add_account_full(TestAddress2, pre[TestAddress2]) - # witness.merge(extra_witness) + witness_check = witness_check_extra + for address in [TestAddress, TestAddress2, env.fee_recipient]: + witness_check.add_account_full(address=address, account=pre.get(address)) + + code_chunks = chunkify_code(pre[TestAddress2].code) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=TestAddress2, chunk_number=i, value=chunk) + + if not fails: + witness_check.add_storage_slot(address=TestAddress2, storage_slot=0, value=None) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] blockchain_test( genesis_environment=env, pre=pre, post=post, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_extcodesize.py b/tests/verkle/eip4762_verkle_gas_witness/test_extcodesize.py index 019f5f9434..f8ce8423a6 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_extcodesize.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_extcodesize.py @@ -7,6 +7,7 @@ import pytest +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, Address, @@ -14,26 +15,22 @@ BlockchainTestFiller, Environment, Bytecode, - Initcode, TestAddress, TestAddress2, Transaction, - compute_create_address, + WitnessCheck, ) from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_types.verkle.helpers import chunkify_code -from ..temp_verkle_helpers import Witness - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" precompile_address = Address("0x04") -system_contract_address = Address("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") +system_contract_address = Address("0xfffffffffffffffffffffffffffffffffffffffe") EmptyAddress = Address("0xFFFFFFf6D732807Ef1319fB7B8bB8522d0BeacFF") -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "target, bytecode", @@ -50,54 +47,52 @@ "system_contract", ], ) -def test_extcodesize(blockchain_test: BlockchainTestFiller, fork: str, target, bytecode): +def test_extcodesize(blockchain_test: BlockchainTestFiller, target, bytecode): """ Test EXTCODESIZE witness. """ - witness = Witness() - if target != precompile_address and target != system_contract_address: + witness_check_extra = WitnessCheck(fork=Verkle) + if target == EmptyAddress: + witness_check_extra.add_account_basic_data(target, None) + elif target != precompile_address and target != system_contract_address: account = Account(code=bytecode) - witness.add_account_basic_data(target, account) + witness_check_extra.add_account_basic_data(target, account) - _extcodesize(blockchain_test, fork, target, bytecode, witness) + _extcodesize(blockchain_test, target, bytecode, witness_check_extra) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") -def test_extcodesize_insufficient_gas(blockchain_test: BlockchainTestFiller, fork: str): +def test_extcodesize_insufficient_gas(blockchain_test: BlockchainTestFiller): """ Test EXTCODESIZE with insufficient gas. """ _extcodesize( blockchain_test, - fork, TestAddress2, Op.PUSH0 * 1000, - Witness(), + WitnessCheck(fork=Verkle), gas_limit=53_540, fails=True, ) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") -def test_extcodesize_warm(blockchain_test: BlockchainTestFiller, fork: str): +def test_extcodesize_warm(blockchain_test: BlockchainTestFiller): """ Test EXTCODESIZE with WARM cost. """ bytecode = Op.ADD(1, 2) * 10 account = Account(code=bytecode) - witness = Witness() - witness.add_account_basic_data(TestAddress2, account) - _extcodesize(blockchain_test, fork, TestAddress2, bytecode, witness, warm=True) + witness_check_extra = WitnessCheck(fork=Verkle) + witness_check_extra.add_account_basic_data(TestAddress2, account) + _extcodesize(blockchain_test, TestAddress2, bytecode, witness_check_extra, warm=True) def _extcodesize( blockchain_test: BlockchainTestFiller, - fork: str, target: Address, bytecode: Bytecode, - extra_witness: Witness, + witness_check_extra: WitnessCheck, gas_limit=1_000_000, warm=False, fails=False, @@ -112,6 +107,9 @@ def _extcodesize( ) pre = { TestAddress: Account(balance=1000000000000000000000), + TestAddress2: Account( + code=Op.EXTCODESIZE(target) * (2 if warm else 1) + Op.PUSH0 + Op.SSTORE + ), } if len(bytecode) > 0: pre[TestAddress2] = Account(code=bytecode) @@ -120,29 +118,37 @@ def _extcodesize( ty=0x0, chain_id=0x01, nonce=0, - to=Address("0x00"), + to=TestAddress2, gas_limit=gas_limit, gas_price=10, - data=Initcode( - deploy_code=Op.EXTCODESIZE(target) * (2 if warm else 1) + Op.PUSH0 + Op.SSTORE - ), ) - blocks = [Block(txs=[tx])] post = {} if not fails: - contract_address = compute_create_address(address=TestAddress, nonce=tx.nonce) - post[contract_address] = Account(storage={0: len(bytecode)}) + post[TestAddress2] = Account(code=pre[TestAddress2].code, storage={0: 0x424242}) + + witness_check = witness_check_extra + witness_check.add_account_full(env.fee_recipient, None) + witness_check.add_account_full(TestAddress, pre[TestAddress]) + witness_check.add_account_full(TestAddress2, pre[TestAddress2]) + + code_chunks = chunkify_code(pre[TestAddress2].code) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=TestAddress2, chunk_number=i, value=chunk) + + if not fails: + witness_check.add_storage_slot(address=TestAddress2, storage_slot=0, value=None) - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # witness.merge(extra_witness) + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] blockchain_test( genesis_environment=env, pre=pre, post={}, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_selfdestruct.py b/tests/verkle/eip4762_verkle_gas_witness/test_selfdestruct.py index 1e62259e1b..edc016b36b 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_selfdestruct.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_selfdestruct.py @@ -7,31 +7,30 @@ import pytest +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, - Alloc, Address, + Alloc, Block, BlockchainTestFiller, Environment, TestAddress, TestAddress2, Transaction, + WitnessCheck, ) from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_types.verkle.helpers import chunkify_code -from ..temp_verkle_helpers import Witness - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" ExampleAddress = Address("0xd94f5374fce5edbc8e2a8697c15331677e6ebf0c") precompile_address = Address("0x04") -system_contract_address = Address("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") +system_contract_address = Address("0xfffffffffffffffffffffffffffffffffffffffe") -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "target, beneficiary_must_exist", @@ -56,7 +55,6 @@ ) def test_self_destruct( blockchain_test: BlockchainTestFiller, - fork: str, target, beneficiary_must_exist, contract_balance, @@ -66,25 +64,21 @@ def test_self_destruct( """ _selfdestruct( blockchain_test, - fork, target, beneficiary_must_exist, contract_balance, - contract_balance > 0, - contract_balance > 0 and not beneficiary_must_exist, ) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.skip("TBD gas limit") @pytest.mark.parametrize( - "gas_limit, beneficiary_must_exist, beneficiary_add_basic_data, beneficiary_add_codehash", + "gas_limit, beneficiary_must_exist, beneficiary_add_basic_data", [ - ("TBD", True, False, False), - ("TBD", True, False, False), - ("TBD", False, False, False), - ("TBD", False, True, False), + ("TBD", True, False), + ("TBD", True, False), + ("TBD", False, False), + ("TBD", False, True), ], ids=[ "beneficiary_exist_not_enough_substract_contract_balance", @@ -95,7 +89,6 @@ def test_self_destruct( ) def test_self_destruct_insufficient_gas( blockchain_test: BlockchainTestFiller, - fork: str, gas_limit, beneficiary_must_exist, beneficiary_add_basic_data, @@ -106,12 +99,9 @@ def test_self_destruct_insufficient_gas( """ _selfdestruct( blockchain_test, - fork, ExampleAddress, beneficiary_must_exist, 100, - beneficiary_add_basic_data, - beneficiary_add_codehash, gas_limit=gas_limit, fail=True, ) @@ -119,12 +109,9 @@ def test_self_destruct_insufficient_gas( def _selfdestruct( blockchain_test: BlockchainTestFiller, - fork: str, beneficiary: Address, beneficiary_must_exist: bool, contract_balance: int, - beneficiary_add_basic_data: bool, - beneficiary_add_codehash: bool, gas_limit=1_000_000, fail=False, ): @@ -152,31 +139,41 @@ def _selfdestruct( gas_limit=gas_limit, gas_price=10, ) - blocks = [Block(txs=[tx])] - - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # witness.add_account_full(TestAddress2, pre[TestAddress2]) - # if beneficiary_add_basic_data: - # witness.add_account_basic_data(beneficiary, pre.get(beneficiary)) - # if beneficiary_add_codehash: - # witness.add_account_codehash(beneficiary, None) - - post: Alloc = {} + + witness_check = WitnessCheck(fork=Verkle) + for address in [TestAddress, TestAddress2, env.fee_recipient]: + witness_check.add_account_full(address=address, account=pre.get(address)) + if contract_balance > 0 or (beneficiary != precompile_address): + witness_check.add_account_basic_data(beneficiary, pre.get(beneficiary)) + if contract_balance > 0 and not beneficiary_must_exist: + witness_check.add_account_full(beneficiary, pre.get(beneficiary)) + + code_chunks = chunkify_code(pre[TestAddress2].code) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=TestAddress2, chunk_number=i, value=chunk) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] + + post: Alloc = Alloc({}) if not fail and contract_balance > 0 and beneficiary != TestAddress2: beneficiary_account = pre.get(beneficiary) beneficiary_balance = 0 if beneficiary_account is None else beneficiary_account.balance pre[TestAddress2] - post = { - TestAddress2: Account(code=pre[TestAddress2].code, balance=0), - beneficiary: Account(balance=beneficiary_balance + contract_balance), - } + post = Alloc( + { + TestAddress2: Account(code=pre[TestAddress2].code, balance=0), + beneficiary: Account(balance=beneficiary_balance + contract_balance), + } + ) blockchain_test( genesis_environment=env, pre=pre, post=post, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_sload.py b/tests/verkle/eip4762_verkle_gas_witness/test_sload.py index 20e8103557..7ee01ea9bc 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_sload.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_sload.py @@ -7,28 +7,28 @@ import pytest +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, Block, - Bytecode, BlockchainTestFiller, + Bytecode, Environment, TestAddress, TestAddress2, Transaction, + WitnessCheck, ) from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_types.verkle.types import Hash +from ethereum_test_types.verkle.helpers import chunkify_code -from ..temp_verkle_helpers import Witness - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -TestAddress2Storage = {0: 0xAA, 1000: 0xBB} +TestAddress2Storage: dict[int, Hash] = {0: Hash(0xAA), 1000: Hash(0xBB)} -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "storage_slot_accesses", @@ -49,35 +49,34 @@ "empty", ], ) -def test_sload(blockchain_test: BlockchainTestFiller, fork: str, storage_slot_accesses): +def test_sload(blockchain_test: BlockchainTestFiller, storage_slot_accesses): """ Test SLOAD witness. """ - witness = Witness() + witness_check_extra = WitnessCheck(fork=Verkle) for slot in storage_slot_accesses: - witness.add_storage_slot(TestAddress2, slot, TestAddress2Storage.get(slot)) + witness_check_extra.add_storage_slot(TestAddress2, slot, TestAddress2Storage.get(slot)) - _sload(blockchain_test, fork, storage_slot_accesses, witness) + _sload(blockchain_test, storage_slot_accesses, witness_check_extra) -# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.skip("Unskip when geth fixes Touch* witness inclusion with insufficient gas") @pytest.mark.valid_from("Verkle") def test_sload_insufficient_gas(blockchain_test: BlockchainTestFiller, fork: str): """ Test SLOAD with insufficient gas. """ - witness = Witness() + witness_check_extra = WitnessCheck(fork=Verkle) for slot in [1000, 1001]: - witness.add_storage_slot(TestAddress2, slot, TestAddress2Storage.get(slot)) + witness_check_extra.add_storage_slot(TestAddress2, slot, TestAddress2Storage.get(slot)) - _sload(blockchain_test, fork, [1000, 1001, 1002, 1003], witness, gas_limit=21_024) + _sload(blockchain_test, [1000, 1001, 1002, 1003], witness_check_extra, gas_limit=23_506) def _sload( blockchain_test: BlockchainTestFiller, - fork: str, storage_slot_accesses: list[int], - extra_witness: Witness, + witness_check_extra: WitnessCheck, gas_limit=1_000_000, ): env = Environment( @@ -107,18 +106,27 @@ def _sload( gas_limit=gas_limit, gas_price=10, ) - blocks = [Block(txs=[tx])] - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # witness.add_account_full(TestAddress2, pre[TestAddress2]) - # witness.merge(extra_witness) + witness_check = witness_check_extra + for address in [TestAddress, TestAddress2, env.fee_recipient]: + witness_check.add_account_full( + address=address, + account=pre.get(address), + ) + code_chunks = chunkify_code(pre[TestAddress2].code) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=TestAddress2, chunk_number=i, value=chunk) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] blockchain_test( genesis_environment=env, pre=pre, post={}, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_sstore.py b/tests/verkle/eip4762_verkle_gas_witness/test_sstore.py index b9c20807af..69a65e146c 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_sstore.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_sstore.py @@ -7,6 +7,7 @@ import pytest +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, Block, @@ -16,19 +17,18 @@ TestAddress, TestAddress2, Transaction, + WitnessCheck, ) from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_types.verkle.types import Hash +from ethereum_test_types.verkle.helpers import chunkify_code -from ..temp_verkle_helpers import Witness - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -TestAddress2Storage = {0: 0xAA, 1000: 0xBB} +TestAddress2Storage: dict[int, Hash] = {0: Hash(0xAA), 1000: Hash(0xBB)} -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "storage_slot_writes", @@ -43,8 +43,8 @@ [(1000, 0xFF), (1000, 0xFE)], ], ids=[ - "subreeedit_chunkedit_in_account_header", - "subreeedit_chunkedit_outside_account_header", + "chunkedit_in_account_header", + "chunkedit_outside_account_header", "two_in_same_branch_with_fill_cost", "two_different_subtreeedit_cost_and_no_fill_cost", "fill_and_subtree_edit_cost", @@ -53,18 +53,13 @@ "warm_write", ], ) -def test_sstore(blockchain_test: BlockchainTestFiller, fork: str, storage_slot_writes): +def test_sstore(blockchain_test: BlockchainTestFiller, storage_slot_writes): """ Test SSTORE witness. """ - witness = Witness() - for sstore in storage_slot_writes: - witness.add_storage_slot(TestAddress2, sstore[0], TestAddress2Storage.get(sstore[0])) - - _sstore(blockchain_test, fork, storage_slot_writes, witness) + _sstore(blockchain_test, storage_slot_writes) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.skip("TBD gas limit") @pytest.mark.parametrize( @@ -79,20 +74,14 @@ def test_sstore(blockchain_test: BlockchainTestFiller, fork: str, storage_slot_w ], ) def test_sstore_insufficient_gas( - blockchain_test: BlockchainTestFiller, fork: str, gas_limit: int, must_be_in_witness: bool + blockchain_test: BlockchainTestFiller, gas_limit: int, must_be_in_witness: bool ): """ Test SSTORE with insufficient gas. """ - witness = Witness() - if must_be_in_witness: - witness.add_storage_slot(TestAddress2, 5000, None) - _sstore( blockchain_test, - fork, - [(5000, 0xFF)], - witness, + [(5000, Hash(0xFF))], gas_limit=gas_limit, post_state_mutated_slot_count=0, ) @@ -100,9 +89,7 @@ def test_sstore_insufficient_gas( def _sstore( blockchain_test: BlockchainTestFiller, - fork: str, - storage_slot_writes: list[tuple[int, int]], - extra_witness: Witness, + storage_slot_writes: list[tuple[int, Hash]], gas_limit=1_000_000, post_state_mutated_slot_count=None, ): @@ -133,7 +120,6 @@ def _sstore( gas_limit=gas_limit, gas_price=10, ) - blocks = [Block(txs=[tx])] postStorage = TestAddress2Storage.copy() successful_writes = ( @@ -151,16 +137,32 @@ def _sstore( ), } - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # witness.add_account_full(TestAddress2, pre[TestAddress2]) - # witness.merge(extra_witness) + witness_check = WitnessCheck(fork=Verkle) + for address in [TestAddress, TestAddress2, env.fee_recipient]: + witness_check.add_account_full( + address=address, + account=pre.get(address), + ) + code_chunks = chunkify_code(pre[TestAddress2].code) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=TestAddress2, chunk_number=i, value=chunk) + for i in range(successful_writes): + witness_check.add_storage_slot( + TestAddress2, + storage_slot_writes[i][0], + TestAddress2Storage.get(storage_slot_writes[i][0]), + ) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] blockchain_test( genesis_environment=env, pre=pre, post=post, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_transfer.py b/tests/verkle/eip4762_verkle_gas_witness/test_transfer.py index 347a240369..e51cae4c71 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_transfer.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_transfer.py @@ -7,6 +7,7 @@ import pytest +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, Address, @@ -16,20 +17,19 @@ TestAddress, TestAddress2, Transaction, + WitnessCheck, ) +from ethereum_test_forks import Fork +from ethereum_test_types.verkle.helpers import chunkify_code -from ..temp_verkle_helpers import Witness - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" EmptyAddress = Address("0xffff5374fce5edbc8e2a8697c15331677e6ebfff") precompile_address = Address("0x04") -system_contract_address = Address("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") +system_contract_address = Address("0xfffffffffffffffffffffffffffffffffffffffe") -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "target", @@ -44,7 +44,7 @@ "value", [0, 1], ) -def test_transfer(blockchain_test: BlockchainTestFiller, fork: str, target, value): +def test_transfer(blockchain_test: BlockchainTestFiller, fork: Fork, target, value): """ Test that value transfer generates a correct witness. """ @@ -68,7 +68,6 @@ def test_transfer(blockchain_test: BlockchainTestFiller, fork: str, target, valu gas_price=10, value=value, ) - blocks = [Block(txs=[tx])] post_account = pre.get(target, Account()) if post_account is None: @@ -78,16 +77,29 @@ def test_transfer(blockchain_test: BlockchainTestFiller, fork: str, target, valu target: post_account, } - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # if target != precompile_address and target != system_contract_address: - # witness.add_account_full(target, pre.get(target)) + witness_check = WitnessCheck(fork=Verkle) + witness_check.add_account_full(env.fee_recipient, None) + witness_check.add_account_full(TestAddress, pre[TestAddress]) + witness_check.add_account_full(target, pre.get(target)) + + if target == system_contract_address: + code = Account(**fork.pre_allocation_blockchain()[system_contract_address]).code + code_chunks = chunkify_code(code) + for i, code_chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk( + address=system_contract_address, chunk_number=i, value=code_chunk + ) + + blocks = [ + Block( + txs=[tx], + witness_check=witness_check, + ) + ] blockchain_test( genesis_environment=env, pre=pre, post=post, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_withdrawals.py b/tests/verkle/eip4762_verkle_gas_witness/test_withdrawals.py index 423febc1b0..863570ece7 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_withdrawals.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_withdrawals.py @@ -7,6 +7,7 @@ import pytest +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, Block, @@ -15,16 +16,13 @@ TestAddress, TestAddress2, Withdrawal, + WitnessCheck, ) -from ..temp_verkle_helpers import Witness - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") def test_withdrawals(blockchain_test: BlockchainTestFiller, fork: str): """ @@ -41,6 +39,18 @@ def test_withdrawals(blockchain_test: BlockchainTestFiller, fork: str): TestAddress: Account(balance=1000000000000000000000), } + post = { + TestAddress: Account(balance=1000000000003000000000), + TestAddress2: Account(balance=4000000000), + } + + witness_check = WitnessCheck(fork=Verkle) + for address in [TestAddress, TestAddress2]: + witness_check.add_account_full( + address=address, + account=pre.get(address), + ) + blocks = [ Block( txs=[], @@ -48,23 +58,13 @@ def test_withdrawals(blockchain_test: BlockchainTestFiller, fork: str): Withdrawal(index=0, validator_index=0, amount=3, address=TestAddress), Withdrawal(index=1, validator_index=1, amount=4, address=TestAddress2), ], + witness_check=witness_check, ) ] - post = { - TestAddress: Account(balance=1000000000003000000000), - TestAddress2: Account(balance=4000000000), - } - - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # witness.add_account_full(TestAddress2, pre[TestAddress2]) - blockchain_test( genesis_environment=env, pre=pre, post=post, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip6800_genesis_verkle_tree/test_contract_codechunking.py b/tests/verkle/eip6800_genesis_verkle_tree/test_contract_codechunking.py index c63d8dd2d7..1d083cf277 100644 --- a/tests/verkle/eip6800_genesis_verkle_tree/test_contract_codechunking.py +++ b/tests/verkle/eip6800_genesis_verkle_tree/test_contract_codechunking.py @@ -20,12 +20,10 @@ ) from ethereum_test_tools.vm.opcode import Opcodes as Op -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6800.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "bytecode", diff --git a/tests/verkle/eip6800_genesis_verkle_tree/test_contract_creation.py b/tests/verkle/eip6800_genesis_verkle_tree/test_contract_creation.py index 210d49007a..28988bb220 100644 --- a/tests/verkle/eip6800_genesis_verkle_tree/test_contract_creation.py +++ b/tests/verkle/eip6800_genesis_verkle_tree/test_contract_creation.py @@ -20,12 +20,10 @@ ) from ethereum_test_tools.vm.opcode import Opcodes as Op -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6800.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "bytecode", diff --git a/tests/verkle/eip6800_genesis_verkle_tree/test_storage_slot_write.py b/tests/verkle/eip6800_genesis_verkle_tree/test_storage_slot_write.py index 9d8391a07e..5f38a93e50 100644 --- a/tests/verkle/eip6800_genesis_verkle_tree/test_storage_slot_write.py +++ b/tests/verkle/eip6800_genesis_verkle_tree/test_storage_slot_write.py @@ -19,14 +19,12 @@ ) from ethereum_test_tools.vm.opcode import Opcodes as Op -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6800.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" precompile_address = Address("0x04") -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "slot_num", diff --git a/tests/verkle/eip6800_genesis_verkle_tree/test_transfer.py b/tests/verkle/eip6800_genesis_verkle_tree/test_transfer.py index fb4136c0f3..fc7d00136c 100644 --- a/tests/verkle/eip6800_genesis_verkle_tree/test_transfer.py +++ b/tests/verkle/eip6800_genesis_verkle_tree/test_transfer.py @@ -18,14 +18,12 @@ Transaction, ) -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6800.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" precompile_address = Address("0x04") -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( "target", diff --git a/tests/verkle/eip6800_verkle_tree/__init__.py b/tests/verkle/eip6800_verkle_tree_transition/__init__.py similarity index 100% rename from tests/verkle/eip6800_verkle_tree/__init__.py rename to tests/verkle/eip6800_verkle_tree_transition/__init__.py diff --git a/tests/verkle/eip6800_verkle_tree/test_verkle_from_mpt_conversion.py b/tests/verkle/eip6800_verkle_tree_transition/test_verkle_from_mpt_conversion.py similarity index 100% rename from tests/verkle/eip6800_verkle_tree/test_verkle_from_mpt_conversion.py rename to tests/verkle/eip6800_verkle_tree_transition/test_verkle_from_mpt_conversion.py diff --git a/tests/verkle/eip6800_verkle_tree/tracker.md b/tests/verkle/eip6800_verkle_tree_transition/tracker.md similarity index 100% rename from tests/verkle/eip6800_verkle_tree/tracker.md rename to tests/verkle/eip6800_verkle_tree_transition/tracker.md diff --git a/tests/verkle/eip7709_blockhash_witness/test_blockhash_instruction.py b/tests/verkle/eip7709_blockhash_witness/test_blockhash_instruction.py index 163f97fbe4..09efdb4582 100644 --- a/tests/verkle/eip7709_blockhash_witness/test_blockhash_instruction.py +++ b/tests/verkle/eip7709_blockhash_witness/test_blockhash_instruction.py @@ -7,6 +7,7 @@ import pytest +from ethereum_test_forks import Verkle from ethereum_test_tools import ( Account, Address, @@ -16,76 +17,106 @@ TestAddress, TestAddress2, Transaction, + WitnessCheck, ) +from ethereum_test_types.verkle.helpers import Hash, chunkify_code from ethereum_test_tools.vm.opcode import Opcodes as Op -from ..temp_verkle_helpers import Witness - -# TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7709.md" REFERENCE_SPEC_VERSION = "TODO" -# TODO(verkle): to be confirmed -blockhash_system_contract_address = Address("0xa4690f0ed0d089faa1e0ad94c8f1b4a2fd4b0734") -HISTORY_STORAGE_ADDRESS = 8192 -BLOCKHASH_OLD_WINDOW = 256 -block_number = 1000 +system_contract_address = Address("0xfffffffffffffffffffffffffffffffffffffffe") +HISTORY_SERVE_WINDOW = 8192 +BLOCKHASH_SERVE_WINDOW = 256 +block_number = BLOCKHASH_SERVE_WINDOW + 5 -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( - "block_num_target", + "blocknum_target", [ - block_number + 10, + block_number + 1, block_number, block_number - 1, - block_number - BLOCKHASH_OLD_WINDOW, - block_number - BLOCKHASH_OLD_WINDOW - 1, + block_number - 2, + block_number - BLOCKHASH_SERVE_WINDOW, + block_number - BLOCKHASH_SERVE_WINDOW - 1, ], ids=[ "future_block", "current_block", - "previous_block", + "previous_block", # Note this block is also written by EIP-2935 + "previous_previous_block", "last_supported_block", "too_old_block", ], ) -def test_blockhash(blockchain_test: BlockchainTestFiller, fork: str, block_num_target: int): +def test_blockhash(blockchain_test: BlockchainTestFiller, blocknum_target: int): """ Test BLOCKHASH witness. """ - _blockhash(blockchain_test, fork, block_num_target) + # TODO(verkle): Today the only way to create these assertions is by hardcoding the values. + # If in the future the testing library can calculate these upfront, we should + # calculate them instead of hardcoding them. + hardcoded_blockhashes = { + block_number - 2: Hash(0x127986F98B6BAB3B2AF5AC250912018D9982696E7B9B364D28190FA68C0AB49D), + block_number + - BLOCKHASH_SERVE_WINDOW: Hash( + 0xBBB791529CE751AC1FDC021C21B0A7F13A22905BD5BB396EC3F57EBF97C0A14D + ), + } + _blockhash(blockchain_test, blocknum_target, hardcoded_blockhashes) + + +@pytest.mark.valid_from("Verkle") +def test_blockhash_warm(blockchain_test: BlockchainTestFiller): + """ + Test BLOCKHASH witness with warm cost. + """ + # TODO(verkle): Today the only way to create these assertions is by hardcoding the values. + # If in the future the testing library can calculate these upfront, we should + # calculate them instead of hardcoding them. + hardcoded_blockhashes = { + block_number - 2: Hash(0x1B027321A3F7FE2F073F9B9C654CF3E62ABD2A8324A198FD7C46D056BC3CE976), + } + _blockhash(blockchain_test, block_number - 2, hardcoded_blockhashes, warm=True) -# TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") -def test_blockhash_insufficient_gas(blockchain_test: BlockchainTestFiller, fork: str): +def test_blockhash_insufficient_gas(blockchain_test: BlockchainTestFiller): """ - Test BLOCKHASH with insufficient gas. + Test BLOCKHASH with insufficient gas for witness addition. """ - _blockhash(blockchain_test, fork, block_number - 1, gas_limit=21_042, fail=True) + # 21_223 = 21_000 + (one code-chunk) 200 + (PUSH2) 3 + (BLOCKHASH constant cost) 20 + _blockhash(blockchain_test, block_number - 2, {}, gas_limit=21_223, fail=True) def _blockhash( blockchain_test: BlockchainTestFiller, - fork: str, - block_num_target: int, - gas_limit=1_000_000, - fail=False, + blocknum_target: int, + hardcoded_blockhashes: dict[int, Hash], + gas_limit: int = 1_000_000, + warm: bool = False, + fail: bool = False, ): env = Environment( fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", difficulty=0x20000, gas_limit=10000000000, - number=1, + number=0, timestamp=1000, ) + pre = { TestAddress: Account(balance=1000000000000000000000), - TestAddress2: Account(code=Op.BLOCKHASH(block_num_target)), + TestAddress2: Account(code=Op.BLOCKHASH(blocknum_target) * (2 if warm else 1)), } + # Create block_number-1 empty blocks to fill the ring buffer. + blocks: list[Block] = [] + for b in range(block_number - 1): + blocks.append(Block()) + tx = Transaction( ty=0x0, chain_id=0x01, @@ -94,21 +125,40 @@ def _blockhash( gas_limit=gas_limit, gas_price=10, ) - blocks = [Block(txs=[tx])] - # witness = Witness() - # witness.add_account_full(env.fee_recipient, None) - # witness.add_account_full(TestAddress, pre[TestAddress]) - # witness.add_account_full(TestAddress2, pre[TestAddress2]) - # if not fail: - # storage_slot = block_num_target % HISTORY_STORAGE_ADDRESS - # value = None # TODO(verkle): TODO. - # witness.add_storage_slot(blockhash_system_contract_address, storage_slot, value) + witness_check = WitnessCheck(fork=Verkle) + for address in [env.fee_recipient, TestAddress, TestAddress2]: + witness_check.add_account_full(address=address, account=pre.get(address)) + code_chunks = chunkify_code(pre[TestAddress2].code) + for i, chunk in enumerate(code_chunks, start=0): + witness_check.add_code_chunk(address=TestAddress2, chunk_number=i, value=chunk) + + # TODO(verkle): when system contract exhaustive checks are supported in + # the testing library, add here the 2935 actions on the witness too. + + # If the execution isn't expected to fail due to insufficient gas, and we satisfy the + # block number target condition defined in the spec, we should assert the appropriate slot + # is in the witness. + if not fail and not ( + blocknum_target >= block_number or blocknum_target + BLOCKHASH_SERVE_WINDOW < block_number + ): + witness_check.add_storage_slot( + system_contract_address, + blocknum_target % HISTORY_SERVE_WINDOW, + hardcoded_blockhashes.get(blocknum_target), + ) + + # The last block contains a single transaction with the BLOCKHASH instruction(s). + blocks.append( + Block( + txs=[tx], + witness_check=witness_check, + ) + ) blockchain_test( genesis_environment=env, pre=pre, post={}, blocks=blocks, - # witness=witness, ) diff --git a/tests/verkle/eip7709_blockhash_witness/test_filling.py b/tests/verkle/eip7709_blockhash_witness/test_filling.py deleted file mode 100644 index 716557b891..0000000000 --- a/tests/verkle/eip7709_blockhash_witness/test_filling.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -abstract: Tests [EIP-7709: Read BLOCKHASH from storage and update cost] -(https://eips.ethereum.org/EIPS/eip-7709) - Tests for [EIP-7709: Read BLOCKHASH from storage and update cost] - (https://eips.ethereum.org/EIPS/eip-7709). -""" - -# import pytest - -# from ethereum_test_tools import ( -# Account, -# Address, -# Block, -# BlockchainTestFiller, -# Environment, -# TestAddress, -# TestAddress2, -# Transaction, -# ) - -# TODO(verkle): add test for per-block filling of blockhash history in state and witness. diff --git a/tests/verkle/temp_verkle_helpers.py b/tests/verkle/temp_verkle_helpers.py deleted file mode 100644 index e70634d7e4..0000000000 --- a/tests/verkle/temp_verkle_helpers.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -VERKLE HELPERS - -NOTE: This file is temporary, probably it should live in other place in the library -""" - -from enum import Enum - -from typing import ClassVar, Dict - -from ethereum.crypto.hash import keccak256 - -from ethereum_test_tools import Account, Address, Hash, Bytecode - - -class AccountHeaderEntry(Enum): - """ - Represents all the data entries in an account header. - """ - - BASIC_DATA = 0 - CODEHASH = 1 - - PRESENT: ClassVar[None] = None - - -class Witness: - """ - Witness is a list of witness key-values. - """ - - # TODO(verkle): "Hash" as value type isn't the right name but correct underlying type. - # Change to appropriate type. - # witness: Dict[Hash, Hash | None] - - def add_account_full(self, addr: Address, account: Account | None): - """ - Add the full account present witness for the given address. - """ - # self.add_account_basic_data(addr, account) - # self.add_account_codehash( - # addr, Hash(keccak256(account.code)) if account is not None else None - # ) - - def add_account_basic_data(self, address: Address, account: Account | None): - """ - Adds the basic data witness for the given address. - """ - # if account is None: - # self.witness[_account_key(address, AccountHeaderEntry.BASIC_DATA)] = None - # return - - # | Name | Offset | Size | - # | ----------- | ------ | ---- | - # | `version` | 0 | 1 | - # | `nonce` | 4 | 8 | - # | `code_size` | 12 | 4 | - # | `balance` | 16 | 16 | - # basic_data_value = Hash(0) # TODO(verkle): encode as little_endian(table_above) - # self.witness[_account_key(address, AccountHeaderEntry.BASIC_DATA)] = basic_data_value - - def add_account_codehash(self, address: Address, codehash: Hash | None): - """ - Adds the CODEHASH witness for the given address. - """ - # self.witness[_account_key(address, AccountHeaderEntry.CODEHASH)] = codehash - - def add_storage_slot(self, address, storage_slot, value): - """ - Adds the storage slot witness for the given address and storage slot. - """ - # TODO(verkle): - # Must call `evm transition verkle-key ` which returns a - # 32-byte key in hex. - # tree_key = {} - - # self.witness[tree_key] = value - - def add_code_chunk(self, address, chunk_number, value): - """ - Adds the code chunk witness for the given address and chunk number. - """ - # TODO(verkle): - # Must call `evm transition code-chunk-key ` which returns a - # 32-byte key in hex. - # tree_key = {} - - # self.witness[tree_key] = value - - def merge(self, other): - """ - Merge the provided witness into this witness. - """ - # self.witness.update(other.witness) - - -def vkt_chunkify(bytecode: Bytecode): - """ - Return the chunkification of the provided bytecode. - """ - # TODO(verkle): - # Must call `evm transition verkle-chunkify-code ` which returns a hex of - # the chunkified code. The returned byte length is a multiple of 32. `code_chunks` must be - # a list of 32-byte chunks (i.e: partition the returned bytes into 32-byte bytes) - code_chunks: list[bytes] = [] - - return code_chunks - - -def _account_key(address: Address, entry: AccountHeaderEntry): - """ - Return the Verkle Tree key for the address for the given address and entry. - """ - # TODO(verkle): - # Must call `evm transition verkle-key ` which returns a - # 32-byte key in hex. - tree_key = {} - - # We override the least-significant byte of the returned address with the - # provided sub-index. - tree_key[31] = entry - - return tree_key diff --git a/whitelist.txt b/whitelist.txt index 815e125d81..9e81559edf 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -712,6 +712,7 @@ verkle vkt chunkify chunkified +sslot fi url diff --git a/witness.json b/witness.json deleted file mode 100644 index 13695fa616..0000000000 --- a/witness.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "Proof": { - "otherStems": [], - "depthExtensionPresent": "0x0a", - "commitmentsByPath": [ - "0x66b9f91f610b1fe686f83bf54efdcecc5e5b0fd62ee2941d14377838f1e8101e", - "0x1544ce230375d0d206db5a741b9e59d4a685ebd9a15eebe4d1f1a20a100bd454" - ], - "d": "0x6dbbbed029d5abf727beb606844b0eeb0b8f739b49ea3f11a5471cfd899ae450", - "ipaProof": { - "cl": [ - "0x28c12e36f5bbe599c1b2b876ad09643c0412d4eae70cf14a8b538af50f41b5bd", - "0x57296cd19a2fbebb701f529df7cb00e396288606035b01b7c8e870d09ad4787f", - "0x73a59e5580294f3fb92dc14f9e58ba2b3aa0321d0ae33ebd4ef8c71b6f1777e5", - "0x5f1c7793472832c2473dcff06f901e18f818bc576258c55cc084d2b25e3ffa98", - "0x275b57feb89bfd01a471edcc419a9b6295d1a6af915af390e894e441b33ba4d2", - "0x37e7e563ceb4e2674739c5d6b9542a74c8755d07613cd74fa3690d32328204e6", - "0x0e522fc0755ae72d7177aadcd1f14b53443fb86f05fb05db8cfcb0cf1ff2cbff", - "0x17fd80c0b67bb377180827b9632f40eb88c60107dc333cb67e5c4ffc29fccfc1" - ], - "cr": [ - "0x62a8236f8e81c919c4e07485a1a742869eee302d3e7af4560a4119fb75a485e5", - "0x21eefed457fae336fbba4391c3d901ff724e0661fb0583b8cea48d104f08a373", - "0x52783d61cebaa060ee262294ea2fcbe299cb028caaee3b9bd13f777cb104d4f6", - "0x485750b4bf6234237c3b5ca6a22bf19979157686ec8eaa915c6ba45b31ddcdb9", - "0x23b543f109342b3c6cdf36852a260d56fbdf857829c4342356845bd1d34cfca5", - "0x147ace37f477ac4b6d106d36a8a0ceb3754d09118925af585dad7b013ba411b3", - "0x0cfba4ea846db30184d334a9f706c9b50542552bd6380b42d1052f5319d703b8", - "0x0dc2d9935f70b6c8e542c531868fa83b6fa327e39687ba88518f4a82f59c530a" - ], - "finalEvaluation": "0x16ffa49bd62610ba46b6ae6216c56f46ce3760243c746bce8dd234a0eafc51dc" - } - }, - "Diff": [ - { - "stem": "0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad7", - "suffixDiffs": [ - { - "suffix": 66, - "currentValue": "0x2ebf905fd662e749159cde4fd12e0fb6f7ae9a102dc0d5edc798e82310803b69", - "newValue": null - } - ] - } - ] -} \ No newline at end of file