diff --git a/.github/configs/evm.yaml b/.github/configs/evm.yaml index 301d225fb0..8795ef6a87 100644 --- a/.github/configs/evm.yaml +++ b/.github/configs/evm.yaml @@ -13,4 +13,10 @@ eip7692: eip7692-prague: impl: besu repo: hyperledger/besu - ref: main \ No newline at end of file + ref: main + evm-bin: evmone-t8n +verkle: + impl: geth + repo: gballet/go-ethereum + ref: jsign-t8n-verkle-genesis-rebased + evm-bin: evm diff --git a/.github/configs/feature.yaml b/.github/configs/feature.yaml index 09443e39ee..4570540661 100644 --- a/.github/configs/feature.yaml +++ b/.github/configs/feature.yaml @@ -13,4 +13,12 @@ eip7692: eip7692-prague: evm-type: eip7692-prague fill-params: --fork=PragueEIP7692 ./tests/prague -k "not slow" - solc: 0.8.21 \ No newline at end of file + solc: 0.8.21 +verkle-genesis: + evm-type: verkle + fill-params: --fork=Verkle -m blockchain_test + solc: 0.8.24 +verkle-conversion-stride-0: + evm-type: verkle + fill-params: --fork=EIP6800Transition -m blockchain_test + solc: 0.8.24 diff --git a/src/ethereum_test_base_types/__init__.py b/src/ethereum_test_base_types/__init__.py index 3b4ee79fe1..c3fa8f6fa7 100644 --- a/src/ethereum_test_base_types/__init__.py +++ b/src/ethereum_test_base_types/__init__.py @@ -15,6 +15,7 @@ HexNumber, Number, NumberBoundTypeVar, + PaddedFixedSizeBytes, ZeroPaddedHexNumber, ) from .composite_types import Account, Alloc, Storage, StorageRootType @@ -53,6 +54,7 @@ "HexNumber", "Number", "NumberBoundTypeVar", + "PaddedFixedSizeBytes", "ReferenceSpec", "Storage", "StorageRootType", diff --git a/src/ethereum_test_base_types/base_types.py b/src/ethereum_test_base_types/base_types.py index b54ba401b0..da39b5d20d 100644 --- a/src/ethereum_test_base_types/base_types.py +++ b/src/ethereum_test_base_types/base_types.py @@ -276,6 +276,21 @@ def __ne__(self, other: object) -> bool: return not self.__eq__(other) +class PaddedFixedSizeBytes(FixedSizeBytes): + """ + Class that represents fixed-size bytes with padded hex output. + """ + + def hex(self, *args, **kwargs) -> str: + """ + Returns the hexadecimal representation of the bytes with padding. + """ + hex_str = super().hex(*args, **kwargs)[2:] + if len(hex_str) % 2 != 0: + hex_str = "0" + hex_str + return "0x" + hex_str.zfill(self.byte_length * 2) + + class Address(FixedSizeBytes[20]): # type: ignore """ Class that helps represent Ethereum addresses in tests. diff --git a/src/ethereum_test_base_types/pydantic.py b/src/ethereum_test_base_types/pydantic.py index 6ef2c2b0a4..48761c9f09 100644 --- a/src/ethereum_test_base_types/pydantic.py +++ b/src/ethereum_test_base_types/pydantic.py @@ -1,6 +1,7 @@ """ Base pydantic classes used to define the models for Ethereum tests. """ + from typing import TypeVar from pydantic import BaseModel, ConfigDict diff --git a/src/ethereum_test_fixtures/blockchain.py b/src/ethereum_test_fixtures/blockchain.py index d1bde7049d..518e6fd974 100644 --- a/src/ethereum_test_fixtures/blockchain.py +++ b/src/ethereum_test_fixtures/blockchain.py @@ -40,6 +40,7 @@ WithdrawalRequest, WithdrawalRequestGeneric, ) +from ethereum_test_types.verkle import Witness from .base import BaseFixture from .formats import FixtureFormats @@ -412,6 +413,19 @@ def from_consolidation_request( return cls(**d.model_dump()) +class FixtureWitness(Witness): + """ + Structure to represent a single verkle block witness. + """ + + @classmethod + def from_witness(cls, w: Witness) -> "FixtureWitness": + """ + Returns a FixtureWitness from a Witness. + """ + return cls(**w.model_dump()) + + class FixtureBlockBase(CamelModel): """Representation of an Ethereum block within a test Fixture without RLP bytes.""" @@ -423,6 +437,8 @@ class FixtureBlockBase(CamelModel): withdrawal_requests: List[FixtureWithdrawalRequest] | None = None consolidation_requests: List[FixtureConsolidationRequest] | None = None + witness: FixtureWitness | None = None + @computed_field(alias="blocknumber") # type: ignore[misc] @cached_property def block_number(self) -> Number: diff --git a/src/ethereum_test_specs/base.py b/src/ethereum_test_specs/base.py index 3a7bfb6f60..ceb9212566 100644 --- a/src/ethereum_test_specs/base.py +++ b/src/ethereum_test_specs/base.py @@ -14,7 +14,8 @@ from ethereum_test_base_types import to_hex from ethereum_test_fixtures import BaseFixture, FixtureFormats from ethereum_test_forks import Fork -from ethereum_test_types import Alloc, Environment, Transaction, VerkleTree, Withdrawal +from ethereum_test_types import Alloc, Environment, Transaction, Withdrawal +from ethereum_test_types.verkle import VerkleTree from evm_transition_tool import Result, TransitionTool diff --git a/src/ethereum_test_specs/blockchain.py b/src/ethereum_test_specs/blockchain.py index ede436b916..e1f88074a9 100644 --- a/src/ethereum_test_specs/blockchain.py +++ b/src/ethereum_test_specs/blockchain.py @@ -18,7 +18,6 @@ HeaderNonce, HexNumber, Number, - to_json, ) from ethereum_test_exceptions import BlockException, EngineAPIError, TransactionException from ethereum_test_fixtures import BaseFixture, FixtureFormats @@ -34,6 +33,7 @@ FixtureTransaction, FixtureWithdrawal, FixtureWithdrawalRequest, + FixtureWitness, InvalidFixtureBlock, ) from ethereum_test_forks import EIP6800Transition, Fork, Verkle @@ -45,10 +45,10 @@ Removable, Requests, Transaction, - VerkleTree, Withdrawal, WithdrawalRequest, ) +from ethereum_test_types.verkle import VerkleTree, Witness from evm_transition_tool import TransitionTool from .base import BaseTest, verify_result, verify_transactions @@ -328,6 +328,7 @@ class BlockchainTest(BaseTest): def make_genesis( self, fork: Fork, + t8n: TransitionTool, ) -> Tuple[Alloc, FixtureBlock]: """ Create a genesis block from the blockchain test definition. @@ -346,7 +347,14 @@ def make_genesis( ) if empty_accounts := pre_alloc.empty_accounts(): raise Exception(f"Empty accounts in pre state: {empty_accounts}") - state_root = pre_alloc.state_root() + + state_root: bytes + # TODO: refine, currently uses `evm verkle state-root` to get this. + if fork < Verkle or fork is EIP6800Transition: + state_root = pre_alloc.state_root() + else: + state_root = t8n.get_verkle_state_root(mpt_alloc=pre_alloc) + genesis = FixtureHeader( parent_hash=0, ommers_hash=EmptyOmmersRoot, @@ -404,6 +412,7 @@ def generate_block_data( Alloc, Optional[Requests], Optional[VerkleTree], + Optional[Witness], ]: """ Generate common block data for both make_fixture and make_hive_fixture. @@ -436,7 +445,7 @@ def generate_block_data( txs=txs, env=env, fork=fork, - vkt=to_json(previous_vkt) if previous_vkt is not None else None, + vkt=previous_vkt, chain_id=self.chain_id, reward=fork.get_reward(env.number, env.timestamp), eips=eips, @@ -446,6 +455,8 @@ def generate_block_data( try: 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) except Exception as e: print_traces(t8n.get_traces()) pprint(transition_tool_output.result) @@ -453,6 +464,8 @@ def generate_block_data( pprint(transition_tool_output.alloc) 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) raise e if len(rejected_txs) > 0 and block.exception is None: @@ -525,6 +538,11 @@ def generate_block_data( ) ) transition_tool_output.alloc = previous_alloc + # TODO: hack for now + transition_tool_output.witness = Witness( + verkle_proof=transition_tool_output.result.verkle_proof, + state_diff=transition_tool_output.result.state_diff, + ) return ( env, @@ -533,6 +551,7 @@ def generate_block_data( transition_tool_output.alloc, requests, transition_tool_output.vkt, + transition_tool_output.witness, ) def network_info(self, fork: Fork, eips: Optional[List[int]] = None): @@ -556,7 +575,7 @@ def verify_post_state( Verifies the post state after all block/s or payload/s are generated. """ try: - if env.verkle_conversion_started: + if env.verkle_conversion_started or env.verkle_conversion_ended: if vkt is not None: pass # TODO: skip exact account verify checks # verify_post_vkt(t8n=t8n, expected_post=self.post, got_vkt=vkt) @@ -579,13 +598,19 @@ def make_fixture( """ fixture_blocks: List[FixtureBlock | InvalidFixtureBlock] = [] - pre, genesis = self.make_genesis(fork) + pre, genesis = self.make_genesis(fork, t8n) alloc = pre env = environment_from_parent_header(genesis.header) head = genesis.header.block_hash vkt: Optional[VerkleTree] = None + # Filling for verkle genesis tests + if fork is Verkle: + env.verkle_conversion_ended = True + # convert alloc to vkt + vkt = t8n.from_mpt_to_vkt(alloc) + # Hack for filling naive verkle transition tests if fork is EIP6800Transition: # Add a dummy block before the fork transition @@ -603,14 +628,16 @@ def make_fixture( # This is the most common case, the RLP needs to be constructed # based on the transactions to be included in the block. # Set the environment according to the block to execute. - new_env, header, txs, new_alloc, requests, new_vkt = self.generate_block_data( - t8n=t8n, - fork=fork, - block=block, - previous_env=env, - previous_alloc=alloc, - previous_vkt=vkt, - eips=eips, + new_env, header, txs, new_alloc, requests, new_vkt, witness = ( + self.generate_block_data( + t8n=t8n, + fork=fork, + block=block, + previous_env=env, + previous_alloc=alloc, + previous_vkt=vkt, + eips=eips, + ) ) fixture_block = FixtureBlockBase( header=header, @@ -645,6 +672,7 @@ def make_fixture( if requests is not None else None ), + witness=FixtureWitness.from_witness(witness) if witness is not None else None, ).with_rlp(txs=txs, requests=requests) if block.exception is None: fixture_blocks.append(fixture_block) @@ -699,7 +727,7 @@ def make_hive_fixture( """ fixture_payloads: List[FixtureEngineNewPayload] = [] - pre, genesis = self.make_genesis(fork) + pre, genesis = self.make_genesis(fork, t8n) alloc = pre env = environment_from_parent_header(genesis.header) head_hash = genesis.header.block_hash @@ -718,7 +746,8 @@ def make_hive_fixture( self.blocks.append(Block()) for block in self.blocks: - new_env, header, txs, new_alloc, requests, new_vkt = self.generate_block_data( + # TODO: fix witness for hive fixture? Do we need it? + new_env, header, txs, new_alloc, requests, new_vkt, _ = self.generate_block_data( t8n=t8n, fork=fork, block=block, @@ -762,7 +791,7 @@ def make_hive_fixture( # Most clients require the header to start the sync process, so we create an empty # block on top of the last block of the test to send it as new payload and trigger the # sync process. - _, sync_header, _, _, requests, _ = self.generate_block_data( + _, sync_header, _, _, requests, _, _ = self.generate_block_data( t8n=t8n, fork=fork, block=Block(), diff --git a/src/ethereum_test_tools/__init__.py b/src/ethereum_test_tools/__init__.py index 2116aad69d..33720ece20 100644 --- a/src/ethereum_test_tools/__init__.py +++ b/src/ethereum_test_tools/__init__.py @@ -46,7 +46,6 @@ Storage, TestParameterGroup, Transaction, - VerkleTree, Withdrawal, WithdrawalRequest, add_kzg_version, @@ -139,7 +138,6 @@ "TransactionException", "Withdrawal", "WithdrawalRequest", - "VerkleTree", "Yul", "YulCompiler", "add_kzg_version", diff --git a/src/ethereum_test_types/__init__.py b/src/ethereum_test_types/__init__.py index 61557f0c67..b8b0cf5aba 100644 --- a/src/ethereum_test_types/__init__.py +++ b/src/ethereum_test_types/__init__.py @@ -27,7 +27,6 @@ Requests, Storage, Transaction, - VerkleTree, Withdrawal, WithdrawalRequest, ) @@ -54,7 +53,6 @@ "TestPrivateKey", "TestPrivateKey2", "Transaction", - "VerkleTree", "Withdrawal", "WithdrawalRequest", "ZeroPaddedHexNumber", diff --git a/src/ethereum_test_types/types.py b/src/ethereum_test_types/types.py index 5c7ae9e8b3..c688f70d85 100644 --- a/src/ethereum_test_types/types.py +++ b/src/ethereum_test_types/types.py @@ -1259,12 +1259,3 @@ def consolidation_requests(self) -> List[ConsolidationRequest]: Returns the list of consolidation requests. """ return [c for c in self.root if isinstance(c, ConsolidationRequest)] - - -# TODO: use a type like HashInt but that doesn't pad zero. DO NOT PAD THE ZEROS. KEEP ALL ZEROS. -class VerkleTree(RootModel[Dict[str, str]]): - """ - Definition of a verkle tree return from the geth t8n. - """ - - root: Dict[str, str] = Field(default_factory=dict) diff --git a/src/ethereum_test_types/verkle/__init__.py b/src/ethereum_test_types/verkle/__init__.py new file mode 100644 index 0000000000..5ef1229e6f --- /dev/null +++ b/src/ethereum_test_types/verkle/__init__.py @@ -0,0 +1,14 @@ +""" +Ethereum Verkle Test Types. +""" + +from .types import IpaProof, StateDiff, SuffixStateDiff, VerkleProof, VerkleTree, Witness + +__all__ = ( + "IpaProof", + "StateDiff", + "SuffixStateDiff", + "VerkleProof", + "VerkleTree", + "Witness", +) diff --git a/src/ethereum_test_types/verkle/test_verkle_witness.py b/src/ethereum_test_types/verkle/test_verkle_witness.py new file mode 100644 index 0000000000..7938164213 --- /dev/null +++ b/src/ethereum_test_types/verkle/test_verkle_witness.py @@ -0,0 +1,172 @@ +""" +Test suite for the verkle witness. +""" + +import pytest + +from ethereum_test_types.verkle import IpaProof, StateDiff, SuffixStateDiff, VerkleProof, Witness + + +@pytest.fixture +def ipa_proof_data(): + """ + Valid verkle proof data. + """ + return { + "cl": [ + "0x64b54668075852328d955f6f2336a9a06defa7a8b49718a013a3849212988c5a", + "0x360403c03f7e21825e7ff76be9d5b6067032230c6f91c618298796bfc47a0c11", + "0x064fbaf6f5ff7e7d126cec7665789a0a62f43b68f2e6e8167b7cca82455ab6c7", + "0x3b02cb202ecbb384215e8ad74a9be278fb762f667bade979f8f9a39088918af0", + "0x57fa59af60061d72fc92e4264bad153885c0d06991dc6736e4f4b081326a1cdc", + "0x24dd138cd53f71e075a07fe4532168de6d6061bfe491e7768bcc1d7713bc3bcb", + "0x5da706b290cee4ca425d13c49bbe4da202de79010051d9d3b1c052142fb2e7aa", + "0x238f6f59bc86c521e6fce993f84f4b666fcf25248aee148d898a9e614c3fee13", + ], + "cr": [ + "0x2aa66cbd97293e3524945cb9660c97a42b71e29718cdda0841fcf18636b57a1b", + "0x73e20d72f31583e21e5bb204d262a737d190d993af234b3cb4b6ecdddc49b61b", + "0x4b8602d3f96d61c74dc19f771c907224b086086e0207fbe2e24d511627102ba4", + "0x446ac6121c2fbc0a4cc656121c160f049a07f0d63307aae23815c1a7ec80cfdf", + "0x6d5a3fbd3356e5df2d93052cc9e402dab4ef5014af9b35fc32977c584e7613bc", + "0x4399dfaf28ee7a4fda83a04ea189201b0f63b897b74f5d5dc51535ff23d48124", + "0x4441409deb28990e3b51235f7ea131a2772237167fe266fbcd0ba19f422364e3", + "0x39966d71a2cf09311940c164ef8e5f37e09a64226535c4aeaf26944f3f5f1b2e", + ], + "finalEvaluation": "0x0773f10637892f75d48ef0ed3e421b6e435220d17a99ec2914af567d46c70988", + } + + +def test_ipa_proof_validation(ipa_proof_data): + """ + Performs basic IPA proof format validation. + """ + ipa_proof = IpaProof(**ipa_proof_data) + assert ipa_proof.cl[0] == 0x64B54668075852328D955F6F2336A9A06DEFA7A8B49718A013A3849212988C5A + assert ipa_proof.cr[0] == 0x2AA66CBD97293E3524945CB9660C97A42B71E29718CDDA0841FCF18636B57A1B + assert ipa_proof.final_evaluation == ( + 0x0773F10637892F75D48EF0ED3E421B6E435220D17A99EC2914AF567D46C70988 + ) + + +@pytest.fixture +def verkle_proof_data(ipa_proof_data): + """ + Valid verkle proof data. + """ + return { + "otherStems": [ + "0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad7", + "0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad7", + ], + "depthExtensionPresent": "0x0a", + "commitmentsByPath": [ + "0x73bd3673ee58f638feb0e21ba8b0cfeadbc9b280716915338b4f46556aa68226", + "0x12fe9ad68c17edfed0861a1b19f0bc178836f56abf3514742cb2d4645b35ba92", + ], + "d": "0x392ac76ac887f79c7c6fd5fd26ec9cfd44664a69aa5075477cbdfdcb522d2a7a", + "ipaProof": ipa_proof_data, + } + + +def test_verkle_proof_validation(verkle_proof_data): + """ + Performs basic verkle proof format validation. + """ + verkle_proof = VerkleProof(**verkle_proof_data) + assert verkle_proof.other_stems[0] == ( + 0x5B5FDFEDD6A0E932DA408AC7D772A36513D1EEE9B9926E52620C43A433AAD7 + ) + assert verkle_proof.depth_extension_present == 0x0A + assert ( + verkle_proof.commitments_by_path[0] + == 0x73BD3673EE58F638FEB0E21BA8B0CFEADBC9B280716915338B4F46556AA68226 + ) + assert ( + verkle_proof.commitments_by_path[1] + == 0x12FE9AD68C17EDFED0861A1B19F0BC178836F56ABF3514742CB2D4645B35BA92 + ) + assert verkle_proof.d == 0x392AC76AC887F79C7C6FD5FD26EC9CFD44664A69AA5075477CBDFDCB522D2A7A + assert verkle_proof.ipa_proof.final_evaluation == ( + 0x0773F10637892F75D48EF0ED3E421B6E435220D17A99EC2914AF567D46C70988 + ) + + +@pytest.fixture +def suffix_diff_data(): + """ + Valid verkle suffix diff data. + """ + return { + "suffix": 66, + "currentValue": "0x647ed3c87a4f764421ea2f5bfc73195812f6b7dd15ac2b8d295730c1dede1edf", + "newValue": None, + } + + +def test_suffix_diff_validation(suffix_diff_data): + """ + Performs basic suffix diff format validation. + """ + suffix_diff = SuffixStateDiff(**suffix_diff_data) + assert suffix_diff.suffix == 66 + assert suffix_diff.current_value == ( + 0x647ED3C87A4F764421EA2F5BFC73195812F6B7DD15AC2B8D295730C1DEDE1EDF + ) + assert suffix_diff.new_value is None + + +@pytest.fixture +def state_diff_data(suffix_diff_data): + """ + Valid verkle state diff data. + """ + return [ + { + "stem": "0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad7", + "suffixDiffs": [suffix_diff_data], + } + ] + + +def test_state_diff_validation(state_diff_data): + """ + Performs basic state diff format validation. + """ + state_diff = StateDiff(state_diff_data) + assert state_diff.root[0].stem == ( + 0x5B5FDFEDD6A0E932DA408AC7D772A36513D1EEE9B9926E52620C43A433AAD7 + ) + assert state_diff.root[0].suffix_diffs[0].suffix == 66 + assert state_diff.root[0].suffix_diffs[0].current_value == ( + 0x647ED3C87A4F764421EA2F5BFC73195812F6B7DD15AC2B8D295730C1DEDE1EDF + ) + assert state_diff.root[0].suffix_diffs[0].new_value is None + + +@pytest.fixture +def witness_data(state_diff_data, verkle_proof_data): + """ + Valid verkle witness data. + """ + return { + "stateDiff": state_diff_data, + "verkleProof": verkle_proof_data, + } + + +def test_witness_validation(witness_data): + """ + Performs basic witness format validation. + """ + witness = Witness(**witness_data) + assert witness.verkle_proof.depth_extension_present == 0x0A + assert ( + witness.verkle_proof.commitments_by_path[0] + == 0x73BD3673EE58F638FEB0E21BA8B0CFEADBC9B280716915338B4F46556AA68226 + ) + assert ( + witness.state_diff.root[0].suffix_diffs[0].current_value + == 0x647ED3C87A4F764421EA2F5BFC73195812F6B7DD15AC2B8D295730C1DEDE1EDF + ) + assert witness.state_diff.root[0].suffix_diffs[0].new_value is None diff --git a/src/ethereum_test_types/verkle/types.py b/src/ethereum_test_types/verkle/types.py new file mode 100644 index 0000000000..3b703cebe7 --- /dev/null +++ b/src/ethereum_test_types/verkle/types.py @@ -0,0 +1,150 @@ +""" +Useful Verkle types for generating Ethereum tests. +""" + +from typing import Any, Dict, List + +from pydantic import Field, RootModel, field_validator +from pydantic.functional_serializers import model_serializer + +from ethereum_test_base_types import CamelModel, HexNumber, PaddedFixedSizeBytes + +IPA_PROOF_DEPTH = 8 + + +class Hash(PaddedFixedSizeBytes[32]): # type: ignore + """ + Class that helps represent an padded Hash. + """ + + pass + + +class Stem(PaddedFixedSizeBytes[31]): # type: ignore + """ + Class that helps represent Verkle Tree Stem. + """ + + pass + + +class IpaProof(CamelModel): + """ + Definition of an Inner Product Argument (IPA) proof. + + A cryptographic proof primarily used to demonstrate the correctness of an inner product + relation between vectors committed to with polynomial commitments. Used to effectively prove + membership or non-membership of elements in the Verkle tree. + """ + + cl: List[Hash] + cr: List[Hash] + final_evaluation: Hash + + @classmethod + @field_validator("cl", "cr") + def check_list_length(cls, v): + """Validates the length of the left/right vector commitment lists.""" + if len(v) != IPA_PROOF_DEPTH: + raise ValueError(f"List must contain exactly {IPA_PROOF_DEPTH} items") + return v + + +class VerkleProof(CamelModel): + """ + Definition of a Verkle Proof. + + A Verkle proof is used to prove the membership or non-membership of elements in a Verkle tree. + It contains the necessary commitments and evaluations to verify the correctness of the elements + in the tree. + """ + + other_stems: List[Stem] + depth_extension_present: HexNumber + commitments_by_path: List[Hash] + d: Hash + ipa_proof: IpaProof | None = Field(None) + + +class SuffixStateDiff(CamelModel): + """ + Definition of a Suffix State Difference. + + Represents the state difference for a specific suffix in a Verkle tree node. + Includes the current value and the new value for the suffix. + """ + + suffix: int + current_value: Hash | None + new_value: Hash | None + + @model_serializer(mode="wrap") + def custom_serializer(self, handler) -> Dict[str, Any]: + """ + Custom serializer to include None (null) values for current/new value. + """ + output = handler(self) + if self.current_value is None: + output["currentValue"] = None + if self.new_value is None: + output["newValue"] = None + return output + + +class StemStateDiff(CamelModel): + """ + Definition of a Stem State Difference. + + Represents the state difference for a specific stem in a Verkle tree. + Includes a list of differences for its suffixes. + """ + + stem: Stem + suffix_diffs: List[SuffixStateDiff] + + +class StateDiff(RootModel): + """ + Definition of a State Difference. + + Represents the overall state differences in a Verkle tree. + This is a list of state differences for various stems. + """ + + root: List[StemStateDiff] + + def __iter__(self): + """Returns an iterator over the State Diff""" + return iter(self.__root__) + + def __getitem__(self, item): + """Returns an item from the State Diff""" + return self.__root__[item] + + +class Witness(CamelModel): + """ + Definition of an Execution Witness. + + Contains all the necessary data to verify the correctness of a blockchain state transition. + This includes the state differences indicating changes in the Verkle tree, the Verkle proof for + proving element membership or non-membership, and the parent state root which provides the root + hash of the state tree before the current block execution. + """ + + state_diff: StateDiff + verkle_proof: VerkleProof + # parent_state_root: Hash + + +class VerkleTree(RootModel[Dict[Hash, Hash]]): + """ + Definition of a Verkle Tree. + + A Verkle Tree is a data structure used to efficiently prove the membership or non-membership + of elements in a state. This class represents the Verkle Tree as a dictionary, where each key + and value is a Hash (32 bytes). The root attribute holds this dictionary, providing a mapping + of the tree's key/values. + """ + + root: Dict[Hash, Hash] = Field(default_factory=dict) diff --git a/src/evm_transition_tool/geth.py b/src/evm_transition_tool/geth.py index 32e14163ba..65b8d760dc 100644 --- a/src/evm_transition_tool/geth.py +++ b/src/evm_transition_tool/geth.py @@ -2,6 +2,7 @@ Go-ethereum Transition tool interface. """ +import binascii import json import os import shutil @@ -14,7 +15,8 @@ from ethereum_test_base_types import to_json from ethereum_test_forks import Fork -from ethereum_test_types import Alloc, VerkleTree +from ethereum_test_types import Alloc +from ethereum_test_types.verkle import VerkleTree from .transition_tool import FixtureFormats, TransitionTool, dump_files_to_directory @@ -143,6 +145,43 @@ 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: + """ + Returns the VKT state root of 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") + 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:]) + def from_mpt_to_vkt(self, mpt_alloc: Alloc) -> VerkleTree: """ Returns the verkle tree representation for an entire MPT alloc using the verkle subcommand. diff --git a/src/evm_transition_tool/transition_tool.py b/src/evm_transition_tool/transition_tool.py index 752094d992..86cf28ba18 100644 --- a/src/evm_transition_tool/transition_tool.py +++ b/src/evm_transition_tool/transition_tool.py @@ -13,11 +13,12 @@ from itertools import groupby from pathlib import Path from re import Pattern -from typing import Any, Dict, List, Mapping, Optional, Type +from typing import Dict, List, Mapping, Optional, Type from ethereum_test_fixtures import FixtureFormats, FixtureVerifier from ethereum_test_forks import Fork -from ethereum_test_types import Alloc, Environment, Transaction, VerkleTree +from ethereum_test_types import Alloc, Environment, Transaction +from ethereum_test_types.verkle import VerkleTree from .file_utils import dump_files_to_directory, write_json_file from .types import TransactionReceipt, TransitionToolInput, TransitionToolOutput @@ -248,9 +249,9 @@ class TransitionToolData: txs: List[Transaction] env: Environment fork_name: str - vkt: Any = None chain_id: int = field(default=1) reward: int = field(default=0) + vkt: Optional[VerkleTree] = None def to_input(self) -> TransitionToolInput: """ @@ -310,8 +311,15 @@ def _evaluate_filesystem( # TODO: Verkle specific logic, update when Verkle fork is confirmed. if t8n_data.fork_name == "Verkle": - output_paths["vkt"] = os.path.join("output", "vkt.json") - args.extend(["--output.vkt", output_paths["vkt"]]) + output_paths.update( + { + "vkt": os.path.join("output", "vkt.json"), + "witness": os.path.join("output", "witness.json"), + } + ) + args.extend( + ["--output.vkt", output_paths["vkt"], "--output.witness", output_paths["witness"]] + ) if t8n_data.vkt is not None: args.extend(["--input.vkt", input_paths["vkt"]]) @@ -514,9 +522,9 @@ def evaluate( txs: List[Transaction], env: Environment, fork: Fork, - vkt: Any = None, chain_id: int = 1, reward: int = 0, + vkt: Optional[VerkleTree] = None, eips: Optional[List[int]] = None, debug_output_path: str = "", ) -> TransitionToolOutput: @@ -568,6 +576,17 @@ def verify_fixture( "The `verify_fixture()` 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. + + Currently only implemented by geth's evm. + """ + raise NotImplementedError( + "The `get_verkle_state_root` 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 entire MPT alloc using the verkle subcommand. diff --git a/src/evm_transition_tool/types.py b/src/evm_transition_tool/types.py index f1b7b5613d..0065a58797 100644 --- a/src/evm_transition_tool/types.py +++ b/src/evm_transition_tool/types.py @@ -13,9 +13,9 @@ DepositRequest, Environment, Transaction, - VerkleTree, WithdrawalRequest, ) +from ethereum_test_types.verkle import StateDiff, VerkleProof, VerkleTree, Witness class TransactionLog(CamelModel): @@ -99,6 +99,9 @@ class Result(CamelModel): alias="currentConversionStorageProcessed", ) + verkle_proof: VerkleProof | None = None + state_diff: StateDiff | None = None + class TransitionToolInput(CamelModel): """ @@ -120,3 +123,4 @@ class TransitionToolOutput(CamelModel): result: Result body: Bytes | None = None vkt: VerkleTree | None = None + witness: Witness | None = None diff --git a/tests/verkle/__init__.py b/tests/verkle/__init__.py new file mode 100644 index 0000000000..5280f812f9 --- /dev/null +++ b/tests/verkle/__init__.py @@ -0,0 +1,3 @@ +""" +Test cases for EVM functionality introduced in Osaka. +""" diff --git a/tests/verkle/eip4762_verkle_gas_witness/__init__.py b/tests/verkle/eip4762_verkle_gas_witness/__init__.py new file mode 100644 index 0000000000..ebbbd7238a --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/__init__.py @@ -0,0 +1,6 @@ +""" +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). +""" diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_balance.py b/tests/verkle/eip4762_verkle_gas_witness/test_balance.py new file mode 100644 index 0000000000..a2c1aed52e --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_balance.py @@ -0,0 +1,110 @@ +""" +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 + +# 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") +example_address = Address("0xd94f5374fce5edbc8e2a8697c15331677e6ebf0c") + + +# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "target", + [ + example_address, + precompile_address, + system_contract_address, + ], +) +@pytest.mark.parametrize("warm", [True, False]) +def test_balance(blockchain_test: BlockchainTestFiller, fork: str, target, warm): + """ + Test BALANCE witness with/without WARM access. + """ + _balance(blockchain_test, fork, target, [target], 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): + """ + Test BALANCE with insufficient gas. + """ + _balance(blockchain_test, fork, target, [], 21_042) + + +def _balance( + blockchain_test: BlockchainTestFiller, + fork: str, + target: Address, + exp_addr_basic_data: list[Address], + gas_limit=1_000_000, + warm=False, +): + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + 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), + } + + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=TestAddress2, + gas_limit=gas_limit, + gas_price=10, + ) + blocks = [Block(txs=[tx])] + + post = { + TestAddress2: Account(code=pre[TestAddress2].code, storage={0: pre[target].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 new file mode 100644 index 0000000000..5c5a72447a --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_calls.py @@ -0,0 +1,278 @@ +""" +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, + Alloc, + Block, + Bytecode, + BlockchainTestFiller, + Environment, + TestAddress, + TestAddress2, + Transaction, +) +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" + +caller_address = Address("0xd94f5374fce5edbc8e2a8697c15331677e6ebf0c") +system_contract_address = Address("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") +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", + [ + (Op.CALL, 0), + (Op.CALL, 1), + (Op.CALLCODE, 0), + (Op.CALLCODE, 1), + (Op.DELEGATECALL, 0), + (Op.DELEGATECALL, 1), + (Op.STATICCALL, 0), + ], +) +@pytest.mark.parametrize( + "target", + [ + TestAddress2, + precompile_address, + system_contract_address, + ], +) +def test_calls( + blockchain_test: BlockchainTestFiller, + fork: str, + call_instruction: Bytecode, + target: Address, + value, +): + """ + Test *CALL instructions gas and witness. + """ + _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", + [ + Op.CALL, + Op.CALLCODE, + Op.DELEGATECALL, + Op.STATICCALL, + ], +) +def test_calls_warm(blockchain_test: BlockchainTestFiller, fork: str, 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( + "call_instruction, gas_limit", + [ + (Op.CALL, "TBD_insufficient_dynamic_cost"), + (Op.CALL, "TBD_insufficient_value_bearing"), + (Op.CALL, "TBD_insufficient_63/64"), + (Op.CALLCODE, "TBD_insufficient_dynamic_cost"), + (Op.CALLCODE, "TBD_insufficient_value_bearing"), + (Op.CALLCODE, "TBD_insufficient_63/64"), + (Op.DELEGATECALL, "TBD_insufficient_dynamic_cost"), + (Op.DELEGATECALL, "TBD_insufficient_63/64"), + (Op.STATICCALL, "TBD_insufficient_dynamic_cost"), + (Op.STATICCALL, "TBD_insufficient_63/64"), + ], + ids=[ + "CALL_insufficient_dynamic_cost", + "CALL_insufficient_value_bearing", + "CALL_insufficient_minimum_63/64", + "CALLCODE_insufficient_dynamic_cost", + "CALLCODE_insufficient_value_bearing", + "CALLCODE_insufficient_minimum_63/64", + "DELEGATECALL_insufficient_dynamic_cost", + "DELEGATECALL_insufficient_minimum_63/64", + "STATICCALL_insufficient_dynamic_cost", + "STATICCALL_insufficient_minimum_63/64", + ], +) +def test_calls_insufficient_gas( + blockchain_test: BlockchainTestFiller, fork: str, call_instruction: Bytecode, gas_limit +): + """ + Test *CALL witness assertion when there's insufficient gas for different scenarios. + """ + _generic_call( + blockchain_test, + fork, + call_instruction, + TestAddress2, + 0, + gas_limit=gas_limit, + enough_gas_read_witness=False, + ) + + +def _generic_call( + blockchain_test: BlockchainTestFiller, + fork: str, + call_instruction, + target: Address, + value, + gas_limit: int = 100000000, + enough_gas_read_witness: bool = True, + warm=False, +): + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + + if call_instruction == Op.CALL or call_instruction == Op.CALLCODE: + tx_value = 0 + caller_code = call_instruction(1_000, target, value, 0, 0, 0, 32) + if call_instruction == Op.DELEGATECALL or call_instruction == Op.STATICCALL: + tx_value = value + caller_code = call_instruction(1_000, target, 0, 0, 0, 32) + + pre = { + TestAddress: Account(balance=1000000000000000000000), + TestAddress2: Account(code=Op.ADD(1, 2)), + caller_address: Account( + balance=2000000000000000000000, code=caller_code * (2 if warm else 1) + ), + precompile_address: Account(balance=3000000000000000000000), + system_contract_address: Account(balance=4000000000000000000000), + } + + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=caller_address, + gas_limit=gas_limit, + gas_price=10, + value=tx_value, + ) + blocks = [Block(txs=[tx])] + + if call_instruction == Op.DELEGATECALL: + post = { + caller_address: Account( + balance=pre[caller_address].balance + value, code=pre[caller_address].code + ), + target: pre[target], + } + else: + post = { + target: Account(balance=pre[target].balance + value, code=pre[target].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", + [ + (Op.CALL, 100000000, True), + (Op.CALL, 21_042, False), + (Op.CALLCODE, 100000000, True), + (Op.CALLCODE, 21_042, False), + ], +) +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. + """ + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + + call_value = 100 + + pre = { + TestAddress: Account(balance=1000000000000000000000), + caller_address: Account( + balance=2000000000000000000000, + code=call_instruction(1_000, TestAddress2, call_value, 0, 0, 0, 32), + ), + } + + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=caller_address, + gas_limit=gas_limit, + gas_price=10, + ) + blocks = [Block(txs=[tx])] + + post: Alloc = Alloc() + if enough_gas_account_creation: + post = Alloc( + { + TestAddress2: Account(balance=call_value), + } + ) + + # 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) + + 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 new file mode 100644 index 0000000000..2bcd1807ac --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_ext_precompile.py @@ -0,0 +1,79 @@ +""" +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, + Initcode, + TestAddress, + Transaction, +) +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" + +precompile_address = Address("0x04") +system_contract_address = Address("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") + + +# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "target", + [ + precompile_address, + system_contract_address, + ], +) +def test_extcodecopy_precompile(blockchain_test: BlockchainTestFiller, fork: str, target): + """ + Test EXTCODECOPY targeting a precompile or system contract. + """ + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=None, + 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. + + 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 new file mode 100644 index 0000000000..43105ee102 --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_generic.py @@ -0,0 +1,251 @@ +""" +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, + Bytecode, + BlockchainTestFiller, + Environment, + Initcode, + TestAddress, + TestAddress2, + Transaction, + # compute_create_address, +) +from ethereum_test_tools.vm.opcode import Opcodes as Op + +# 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 + + +# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "instruction", + [ + Op.CODECOPY, + Op.EXTCODECOPY, + ], +) +@pytest.mark.parametrize( + "offset, size", + [ + (0, 0), + (0, 127 * 31), + (0, 128 * 31), + (0, code_size - 5), + (0, code_size), + (code_size - 1, 1), + (code_size, 1), + (code_size - 1, 1 + 1), + (code_size - 1, 1 + 31), + ], + ids=[ + "zero_bytes", + "chunks_only_account_header", + "all_chunks_account_header", + "contract_size_after_header_but_incomplete", + "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 +): + """ + Test *CODECOPY witness. + """ + start = offset if offset < size else size + end = offset + size if offset + size < code_size else code_size + witness_code_chunks = range(0, 0) + if start < size and start != end: + start_chunk = start // 31 + end_chunk = (end - 1) // 31 + witness_code_chunks = range(start_chunk, end_chunk + 1) + + _generic_codecopy( + blockchain_test, + fork, + instruction, + offset, + size, + witness_code_chunks, + ) + + +# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "instruction", + [ + Op.CODECOPY, + Op.EXTCODECOPY, + ], +) +def test_generic_codecopy_warm(blockchain_test: BlockchainTestFiller, fork: str, instruction): + """ + Test *CODECOPY with WARM access. + """ + witness_code_chunks = range(0, (code_size - 5) // 31 + 1) + _generic_codecopy( + blockchain_test, + fork, + instruction, + 0, + code_size - 5, + witness_code_chunks, + 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( + "gas_limit, witness_code_chunks", + [ + ("TBD", range(0, 0)), + ("TBD", range(0, 150)), + ], + ids=[ + "not_enough_for_even_first_chunk", + "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 +): + """ + Test CODECOPY with insufficient gas. + """ + _generic_codecopy( + blockchain_test, + fork, + Op.CODECOPY, + 0, + code_size, + witness_code_chunks, + 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( + "gas_limit, witness_target_basic_data, witness_code_chunks", + [ + ("TBD", False, range(0, 0)), + ("TBD", True, range(0, 0)), + ("TBD", True, range(0, 150)), + ], + ids=[ + "insufficient_for_target_basic_data", + "not_enough_for_even_first_chunk", + "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, +): + """ + Test EXTCODECOPY with insufficient gas. + """ + _generic_codecopy( + blockchain_test, + fork, + Op.CODECOPY, + 0, + code_size, + witness_code_chunks, + witness_target_basic_data=witness_target_basic_data, + gas_limit=gas_limit, + ) + + +def _generic_codecopy( + blockchain_test: BlockchainTestFiller, + fork: str, + instr: Op, + offset: int, + size: int, + witness_code_chunks, + witness_target_basic_data=True, + warm=False, + gas_limit=1_000_000, +): + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + repeat = 2 if warm else 1 + codecopy_code = Op.CODECOPY(0, offset, size) * repeat + pre = { + TestAddress: Account(balance=1000000000000000000000), + TestAddress2: Account(code=codecopy_code + Op.PUSH0 * (code_size - len(codecopy_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, + nonce=0, + 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]) + + blockchain_test( + genesis_environment=env, + pre=pre, + post={}, + blocks=blocks, + # witness=witness, + ) 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 new file mode 100644 index 0000000000..f2816ae3bd --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_codecopy_generic_initcode.py @@ -0,0 +1,83 @@ +""" +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, + Block, + BlockchainTestFiller, + Environment, + Initcode, + TestAddress, + Transaction, + 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", + [ + Op.CODECOPY, + Op.EXTCODECOPY, + ], +) +def test_generic_codecopy_initcode(blockchain_test: BlockchainTestFiller, fork: str, instruction): + """ + Test *CODECOPY in initcode targeting itself. + """ + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + + contract_address = compute_create_address(TestAddress, 0) + if instruction == Op.EXTCODECOPY: + deploy_code = Op.EXTCODECOPY(contract_address, 0, 0, 100) + Op.ORIGIN * 100 + data = Initcode(deploy_code=deploy_code) + else: + data = Initcode(deploy_code=Op.CODECOPY(0, 0, 100) + Op.ORIGIN * 100) + + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=None, + gas_limit=1_000_000, + 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. + + 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 new file mode 100644 index 0000000000..77810758c4 --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_coinbase_fees.py @@ -0,0 +1,71 @@ +""" +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 ..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): + """ + Test coinbase witness. + """ + coinbase_addr = Address("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba") + env = Environment( + fee_recipient=coinbase_addr, + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + tx = Transaction( + ty=2, + chain_id=0x01, + nonce=0, + to=TestAddress2, + value=100, + 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]) + + 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 new file mode 100644 index 0000000000..7b46764055 --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_contract_execution.py @@ -0,0 +1,245 @@ +# """ +# 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, +# ) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_creates.py b/tests/verkle/eip4762_verkle_gas_witness/test_creates.py new file mode 100644 index 0000000000..a99e66d906 --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_creates.py @@ -0,0 +1,340 @@ +""" +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, + Block, + BlockchainTestFiller, + Environment, + Opcode, + Bytecode, + Initcode, + TestAddress, + TestAddress2, + Transaction, + compute_create2_address, + compute_create_address, +) +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" + + +# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "create_instruction", + [ + None, + Op.CREATE, + Op.CREATE2, + ], +) +@pytest.mark.parametrize( + "code_size, value", + [ + (0, 0), + (127 * 32, 0), + (130 * 32, 0), + (130 * 32 + 1, 0), + (130 * 32 + 1, 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 +): + """ + 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(TestAddress, 0) + else: + contract_address = compute_create2_address( + TestAddress, 0xDEADBEEF, Initcode(deploy_code=contract_code) + ) + + 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]) + + _create( + blockchain_test, + fork, + create_instruction, + witness_extra, + contract_code, + value=value, + ) + + +# 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( + "create_instruction", + [ + None, + Op.CREATE, + Op.CREATE2, + ], +) +@pytest.mark.parametrize( + "gas_limit, witness_basic_data, witness_codehash, witness_chunk_count", + [ + ("TBD", False, False, 0), + ("TBD", False, False, 0), + ("TBD", True, False, 0), + ("TBD", True, False, 3), + ], + ids=[ + "insufficient_63/64_reservation", + "insufficient_for_contract_init", + "insufficient_for_all_contract_completion", + "insufficient_for_all_code_chunks", + ], +) +def test_create_insufficient_gas( + blockchain_test: BlockchainTestFiller, + fork: str, + create_instruction, + gas_limit, + witness_basic_data: bool, + witness_codehash: bool, + witness_chunk_count: int, +): + """ + 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(TestAddress, 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( + "create_instruction, gas_limit", + [ + (Op.CREATE, "TBD"), + (Op.CREATE2, "TBD"), + ], +) +def test_create_static_cost( + blockchain_test: BlockchainTestFiller, + fork: str, + create_instruction, + gas_limit, +): + """ + Test *CREATE with insufficient gas to pay for 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", + [ + None, + Op.CREATE, + Op.CREATE2, + ], +) +def test_create_collision( + blockchain_test: BlockchainTestFiller, + fork: str, + create_instruction, +): + """ + Test *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), + 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", + [ + None, + Op.CREATE, + Op.CREATE2, + ], +) +def test_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(TestAddress, 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. + + _create( + blockchain_test, + fork, + create_instruction, + Witness(), + contract_code, + value=0, + initcode_stop_prefix=True, + ) + + +def _create( + blockchain_test: BlockchainTestFiller, + fork: str, + create_instruction: Opcode | None, + witness_extra: Witness, + contract_code, + value=1, + gas_limit=10000000000, + generate_collision: bool = False, + initcode_stop_prefix: bool = False, +): + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + + deploy_code = Initcode( + initcode_prefix=Op.STOP if initcode_stop_prefix else Bytecode(), deploy_code=contract_code + ) + 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)) + ) + tx_target = TestAddress2 + tx_value = 0 + tx_data = deploy_code + if generate_collision: + contract_address = compute_create_address(TestAddress, 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) + ) + tx_target = TestAddress2 + tx_value = 0 + tx_data = 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 + if generate_collision: + contract_address = compute_create_address(TestAddress, 0) + pre[contract_address] = Account(nonce=1) + + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=tx_target, + gas_limit=gas_limit, + gas_price=10, + 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) + + 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 new file mode 100644 index 0000000000..3b3ff09c0f --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_extcodehash.py @@ -0,0 +1,171 @@ +""" +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.crypto.hash import keccak256 + +from ethereum_test_tools import ( + Account, + Address, + Block, + BlockchainTestFiller, + Environment, + Hash, + TestAddress, + TestAddress2, + Transaction, +) +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" + +precompile_address = Address("0x04") +system_contract_address = Address("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") +EmptyAddress = Address("0xd94f5374fce5edbc8e2a8697c15331677e6ebf0c") + +TestAccount = Account(balance=1000000000000000000000) + +ExampleAddress = Address("0xfffffff4fce5edbc8e2a8697c15331677e6ebf0c") +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", + [ + TestAddress, + ExampleAddress, + EmptyAddress, + system_contract_address, + precompile_address, + ], + ids=[ + "eoa", + "contract", + "non_existent_account", + "system_contract", + "precompile", + ], +) +def test_extcodehash(blockchain_test: BlockchainTestFiller, fork: str, target): + """ + Test EXTCODEHASH witness. + """ + witness = Witness() + if target == ExampleAddress: + witness.add_account_full(ExampleAddress, ExampleAccount) + elif target == TestAddress: + witness.add_account_basic_data(TestAddress, TestAccount) + elif target == EmptyAddress: + witness.add_account_basic_data(EmptyAddress, None) + # For precompile or system contract, we don't need to add any witness. + + _extcodehash(blockchain_test, fork, target, witness) + + +# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.valid_from("Verkle") +def test_extcodehash_warm(blockchain_test: BlockchainTestFiller, fork: str): + """ + Test EXTCODEHASH with WARM cost. + """ + witness = Witness() + witness.add_account_full(ExampleAddress, ExampleAccount) + + _extcodehash(blockchain_test, fork, ExampleAddress, witness, 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( + "gas_limit, witness_assert_basic_data, witness_assert_codehash", + [ + ("TBD", False, False), + ("TBD", True, False), + ], + ids=[ + "insufficient_gas_basic_data", + "insufficient_gas_codehash", + ], +) +def test_extcodehash_insufficient_gas( + blockchain_test: BlockchainTestFiller, + fork: str, + gas_limit: int, + witness_assert_basic_data, + witness_assert_codehash, +): + """ + Test EXTCODEHASH with insufficient gas. + """ + witness = Witness() + if witness_assert_basic_data: + witness.add_account_basic_data(ExampleAddress, ExampleAccount) + if witness_assert_codehash: + witness.add_account_codehash(ExampleAddress, Hash(keccak256(ExampleAccount.code))) + + _extcodehash(blockchain_test, fork, ExampleAddress, witness, gas_limit, fails=True) + + +def _extcodehash( + blockchain_test: BlockchainTestFiller, + fork: str, + target, + extra_witness, + gas_limit=1_000_000, + warm=False, + fails=False, +): + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: TestAccount, + TestAddress2: Account( + code=Op.EXTCODEHASH(target) * (1 if warm else 2) + Op.PUSH0 + Op.SSTORE + ), + ExampleAddress: ExampleAccount, + } + + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=TestAddress2, + 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) + + 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 new file mode 100644 index 0000000000..e06785aebb --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_extcodesize.py @@ -0,0 +1,148 @@ +""" +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, + Bytecode, + Initcode, + TestAddress, + TestAddress2, + Transaction, + 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" + +precompile_address = Address("0x04") +system_contract_address = Address("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") +EmptyAddress = Address("0xFFFFFFf6D732807Ef1319fB7B8bB8522d0BeacFF") + + +# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "target, bytecode", + [ + (EmptyAddress, ""), + (TestAddress2, Op.ADD(1, 2) * 10), + (precompile_address, ""), + (system_contract_address, ""), + ], + ids=[ + "empty_code", + "non_empty_code", + "precompile", + "system_contract", + ], +) +def test_extcodesize(blockchain_test: BlockchainTestFiller, fork: str, target, bytecode): + """ + Test EXTCODESIZE witness. + """ + witness = Witness() + if target != precompile_address and target != system_contract_address: + account = Account(code=bytecode) + witness.add_account_basic_data(target, account) + + _extcodesize(blockchain_test, fork, target, bytecode, witness) + + +# 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): + """ + Test EXTCODESIZE with insufficient gas. + """ + _extcodesize( + blockchain_test, + fork, + TestAddress2, + Op.PUSH0 * 1000, + Witness(), + 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): + """ + 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) + + +def _extcodesize( + blockchain_test: BlockchainTestFiller, + fork: str, + target: Address, + bytecode: Bytecode, + extra_witness: Witness, + gas_limit=1_000_000, + warm=False, + fails=False, +): + + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + if len(bytecode) > 0: + pre[TestAddress2] = Account(code=bytecode) + + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=Address("0x00"), + 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(TestAddress, tx.nonce) + post[contract_address] = Account(storage={0: len(bytecode)}) + + # witness = Witness() + # witness.add_account_full(env.fee_recipient, None) + # witness.add_account_full(TestAddress, pre[TestAddress]) + # witness.merge(extra_witness) + + 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 new file mode 100644 index 0000000000..1e62259e1b --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_selfdestruct.py @@ -0,0 +1,182 @@ +""" +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, + Alloc, + Address, + Block, + BlockchainTestFiller, + Environment, + TestAddress, + TestAddress2, + Transaction, +) +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" + +ExampleAddress = Address("0xd94f5374fce5edbc8e2a8697c15331677e6ebf0c") +precompile_address = Address("0x04") +system_contract_address = Address("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") + + +# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "target, beneficiary_must_exist", + [ + [ExampleAddress, True], + [ExampleAddress, False], + [TestAddress2, True], + [precompile_address, False], + [system_contract_address, False], + ], + ids=[ + "beneficiary_exists", + "beneficiary_does_not_exist", + "self_beneficiary", + "precompile", + "system_contract", + ], +) +@pytest.mark.parametrize( + "contract_balance", + [0, 1], +) +def test_self_destruct( + blockchain_test: BlockchainTestFiller, + fork: str, + target, + beneficiary_must_exist, + contract_balance, +): + """ + Test SELFDESTRUCT witness. + """ + _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", + [ + ("TBD", True, False, False), + ("TBD", True, False, False), + ("TBD", False, False, False), + ("TBD", False, True, False), + ], + ids=[ + "beneficiary_exist_not_enough_substract_contract_balance", + "beneficiary_exist_not_enough_add_beneficiary_balance", + "beneficiary_doesnt_exist_create_account_basic_data", + "beneficiary_doesnt_exist_create_account_codehash", + ], +) +def test_self_destruct_insufficient_gas( + blockchain_test: BlockchainTestFiller, + fork: str, + gas_limit, + beneficiary_must_exist, + beneficiary_add_basic_data, + beneficiary_add_codehash, +): + """ + Test SELFDESTRUCT insufficient gas. + """ + _selfdestruct( + blockchain_test, + fork, + ExampleAddress, + beneficiary_must_exist, + 100, + beneficiary_add_basic_data, + beneficiary_add_codehash, + gas_limit=gas_limit, + fail=True, + ) + + +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, +): + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + TestAddress2: Account(code=Op.SELFDESTRUCT(beneficiary), balance=contract_balance), + } + + assert beneficiary != TestAddress + if beneficiary_must_exist and beneficiary != TestAddress2: + pre[beneficiary] = Account(balance=0xFF) + + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=TestAddress2, + 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 = {} + 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), + } + + 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 new file mode 100644 index 0000000000..20e8103557 --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_sload.py @@ -0,0 +1,124 @@ +""" +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, + Block, + Bytecode, + BlockchainTestFiller, + Environment, + TestAddress, + TestAddress2, + Transaction, +) +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" + +TestAddress2Storage = {0: 0xAA, 1000: 0xBB} + + +# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "storage_slot_accesses", + [ + [0], + [1000], + [0, 1], + [0, 1000], + [1000, 1000], + [5042], + ], + ids=[ + "in_account_header", + "outside_account_header", + "two_in_same_branch", + "two_in_different_branch", + "cold_plus_warm_access", + "empty", + ], +) +def test_sload(blockchain_test: BlockchainTestFiller, fork: str, storage_slot_accesses): + """ + Test SLOAD witness. + """ + witness = Witness() + for slot in storage_slot_accesses: + witness.add_storage_slot(TestAddress2, slot, TestAddress2Storage.get(slot)) + + _sload(blockchain_test, fork, storage_slot_accesses, witness) + + +# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.valid_from("Verkle") +def test_sload_insufficient_gas(blockchain_test: BlockchainTestFiller, fork: str): + """ + Test SLOAD with insufficient gas. + """ + witness = Witness() + for slot in [1000, 1001]: + witness.add_storage_slot(TestAddress2, slot, TestAddress2Storage.get(slot)) + + _sload(blockchain_test, fork, [1000, 1001, 1002, 1003], witness, gas_limit=21_024) + + +def _sload( + blockchain_test: BlockchainTestFiller, + fork: str, + storage_slot_accesses: list[int], + extra_witness: Witness, + gas_limit=1_000_000, +): + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + code = Bytecode() + for slot in storage_slot_accesses: + code += Op.SLOAD(slot) + + pre = { + TestAddress: Account(balance=1000000000000000000000), + TestAddress2: Account( + code=code, + storage=TestAddress2Storage, + ), + } + + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=TestAddress2, + 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) + + 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 new file mode 100644 index 0000000000..b9c20807af --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_sstore.py @@ -0,0 +1,166 @@ +""" +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, + Block, + BlockchainTestFiller, + Bytecode, + Environment, + TestAddress, + TestAddress2, + Transaction, +) +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" + +TestAddress2Storage = {0: 0xAA, 1000: 0xBB} + + +# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "storage_slot_writes", + [ + [(0, 0xFF)], + [(1000, 0xFF)], + [(0, 0xFF), (1, 0xFF)], + [(0, 0xFF), (1000, 0xFF)], + [(5000, 0xFF)], + [(1000, 0x00)], + [(5000, 0x00)], + [(1000, 0xFF), (1000, 0xFE)], + ], + ids=[ + "subreeedit_chunkedit_in_account_header", + "subreeedit_chunkedit_outside_account_header", + "two_in_same_branch_with_fill_cost", + "two_different_subtreeedit_cost_and_no_fill_cost", + "fill_and_subtree_edit_cost", + "non_zero_slot_value_reset", + "zero_slot_write_zero_value", + "warm_write", + ], +) +def test_sstore(blockchain_test: BlockchainTestFiller, fork: str, 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) + + +# 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, must_be_in_witness", + [ + ("TBD", False), + ("TBD", True), + ], + ids=[ + "cant_pay_subtree_edit_and_chunk_edit_cost", + "cant_pay_fill_cost", + ], +) +def test_sstore_insufficient_gas( + blockchain_test: BlockchainTestFiller, fork: str, 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, + gas_limit=gas_limit, + post_state_mutated_slot_count=0, + ) + + +def _sstore( + blockchain_test: BlockchainTestFiller, + fork: str, + storage_slot_writes: list[tuple[int, int]], + extra_witness: Witness, + gas_limit=1_000_000, + post_state_mutated_slot_count=None, +): + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + code = Bytecode() + for slot_write in storage_slot_writes: + code += Op.SSTORE(slot_write[0], slot_write[1]) + + pre = { + TestAddress: Account(balance=1000000000000000000000), + TestAddress2: Account( + storage=TestAddress2Storage, + code=code, + ), + } + + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=TestAddress2, + gas_limit=gas_limit, + gas_price=10, + ) + blocks = [Block(txs=[tx])] + + postStorage = TestAddress2Storage.copy() + successful_writes = ( + len(storage_slot_writes) + if post_state_mutated_slot_count is None + else post_state_mutated_slot_count + ) + for i in range(successful_writes): + postStorage[storage_slot_writes[i][0]] = storage_slot_writes[i][1] + + post = { + TestAddress2: Account( + code=code, + storage=postStorage, + ), + } + + # 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) + + 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 new file mode 100644 index 0000000000..347a240369 --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_transfer.py @@ -0,0 +1,93 @@ +""" +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 ..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") + + +# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "target", + [ + TestAddress2, + EmptyAddress, + precompile_address, + system_contract_address, + ], +) +@pytest.mark.parametrize( + "value", + [0, 1], +) +def test_transfer(blockchain_test: BlockchainTestFiller, fork: str, target, value): + """ + Test that value transfer generates a correct witness. + """ + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + TestAddress2: Account(balance=2000000000000000000000), + } + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=target, + gas_limit=100000000, + gas_price=10, + value=value, + ) + blocks = [Block(txs=[tx])] + + post_account = pre.get(target, Account()) + if post_account is None: + post_account = Account() + post_account.balance += value + post = { + 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)) + + 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 new file mode 100644 index 0000000000..423febc1b0 --- /dev/null +++ b/tests/verkle/eip4762_verkle_gas_witness/test_withdrawals.py @@ -0,0 +1,70 @@ +""" +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, + Block, + BlockchainTestFiller, + Environment, + TestAddress, + TestAddress2, + Withdrawal, +) + +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): + """ + Test withdrawals witness. + """ + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + + blocks = [ + Block( + txs=[], + withdrawals=[ + Withdrawal(index=0, validator_index=0, amount=3, address=TestAddress), + Withdrawal(index=1, validator_index=1, amount=4, address=TestAddress2), + ], + ) + ] + + 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 new file mode 100644 index 0000000000..761e513621 --- /dev/null +++ b/tests/verkle/eip6800_genesis_verkle_tree/test_contract_codechunking.py @@ -0,0 +1,93 @@ +""" +abstract: Tests [EIP-6800: Ethereum state using a unified verkle tree] +(https://eips.ethereum.org/EIPS/eip-6800) + Tests for [EIP-6800: Ethereum state using a unified verkle tree] + (https://eips.ethereum.org/EIPS/eip-6800). +""" + +import pytest + +from ethereum_test_tools import ( + Account, + Block, + Bytecode, + BlockchainTestFiller, + Environment, + Initcode, + TestAddress, + Transaction, + compute_create_address, +) +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", + [ + Bytecode(), + Op.PUSH0 * 31, + Op.PUSH0 * (31 + 1), + Op.PUSH0 * (31 + 32), + Op.PUSH0 * 24576, + Op.PUSH0 * 10 + Op.PUSH10(0x42), # |...pushnXXXXX...| + Op.PUSH0 * (32 - 1 - 5) + Op.PUSH10(0x42), # |...pushnXXX|XXX...| + Op.PUSH0 * (32 - 1) + Op.PUSH10(0x42), # |...pushn|XXXX...| + Op.PUSH0 * (32 - 1) + Op.PUSH32(0x42), # |...push32|XXXXX|X....| + ], + ids=[ + "empty", + "0mod31", + "1mod31", + "32mod31", + "max_size", + "PUSHX_within_boundaries", + "PUSHX_data_split", + "PUSHX_perfect_split", + "PUSH32_spanning_three_chunks", + ], +) +def test_code_chunking(blockchain_test: BlockchainTestFiller, fork: str, bytecode: Bytecode): + """ + Test that code chunking works correctly. + """ + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=None, + gas_limit=100000000, + gas_price=10, + data=Initcode(deploy_code=bytecode), + ) + blocks = [Block(txs=[tx])] + + contract_address = compute_create_address(TestAddress, tx.nonce) + + post = { + contract_address: Account( + code=bytecode, + ), + } + + blockchain_test( + genesis_environment=env, + pre=pre, + post=post, + blocks=blocks, + ) diff --git a/tests/verkle/eip6800_genesis_verkle_tree/test_contract_creation.py b/tests/verkle/eip6800_genesis_verkle_tree/test_contract_creation.py new file mode 100644 index 0000000000..2c240ebd77 --- /dev/null +++ b/tests/verkle/eip6800_genesis_verkle_tree/test_contract_creation.py @@ -0,0 +1,85 @@ +""" +abstract: Tests [EIP-6800: Ethereum state using a unified verkle tree] +(https://eips.ethereum.org/EIPS/eip-6800) + Tests for [EIP-6800: Ethereum state using a unified verkle tree] + (https://eips.ethereum.org/EIPS/eip-6800). +""" + +import pytest + +from ethereum_test_tools import ( + Account, + Block, + BlockchainTestFiller, + Environment, + Initcode, + TestAddress, + Bytecode, + Transaction, + compute_create_address, +) +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", + [ + Bytecode(), + Op.STOP * (128 * 31 + 1000), + ], + ids=["empty", "non_empty"], +) +@pytest.mark.parametrize( + "value", + [0, 1], + ids=["zero", "non_zero"], +) +def test_contract_creation( + blockchain_test: BlockchainTestFiller, fork: str, value: int, bytecode: Bytecode +): + """ + Test that contract creation works as expected. + """ + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=None, + gas_limit=100000000, + gas_price=10, + value=value, + data=Initcode(deploy_code=bytecode), + ) + blocks = [Block(txs=[tx])] + + contract_address = compute_create_address(TestAddress, tx.nonce) + + post = { + contract_address: Account( + balance=value, + code=bytecode, + ), + } + + blockchain_test( + genesis_environment=env, + pre=pre, + post=post, + blocks=blocks, + ) 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 new file mode 100644 index 0000000000..ff1a22e9df --- /dev/null +++ b/tests/verkle/eip6800_genesis_verkle_tree/test_storage_slot_write.py @@ -0,0 +1,90 @@ +""" +abstract: Tests [EIP-6800: Ethereum state using a unified verkle tree] +(https://eips.ethereum.org/EIPS/eip-6800) + Tests for [EIP-6800: Ethereum state using a unified verkle tree] + (https://eips.ethereum.org/EIPS/eip-6800). +""" + +import pytest + +from ethereum_test_tools import ( + Account, + Address, + Block, + BlockchainTestFiller, + Environment, + TestAddress, + Transaction, + compute_create_address, +) +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", + [ + 0, + 63, + 64, + 255, + 513, + 2**256 - 1, + ], + ids=[ + "header_zero", + "header_limit", + "first_non_header_zero", + "first_non_header_limit", + "other_non_header", + "biggest", + ], +) +def test_storage_slot_write(blockchain_test: BlockchainTestFiller, fork: str, slot_num): + """ + Test that storage slot writes work as expected. + """ + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=None, + gas_limit=100000000, + gas_price=10, + data=Op.SSTORE(slot_num, 0x42), + ) + blocks = [Block(txs=[tx])] + + contract_address = compute_create_address(TestAddress, tx.nonce) + + post = { + contract_address: Account( + storage={ + slot_num: 0x42, + }, + ), + } + + blockchain_test( + genesis_environment=env, + pre=pre, + post=post, + blocks=blocks, + ) diff --git a/tests/verkle/eip6800_genesis_verkle_tree/test_transfer.py b/tests/verkle/eip6800_genesis_verkle_tree/test_transfer.py new file mode 100644 index 0000000000..fb4136c0f3 --- /dev/null +++ b/tests/verkle/eip6800_genesis_verkle_tree/test_transfer.py @@ -0,0 +1,79 @@ +""" +abstract: Tests [EIP-6800: Ethereum state using a unified verkle tree] +(https://eips.ethereum.org/EIPS/eip-6800) + Tests for [EIP-6800: Ethereum state using a unified verkle tree] + (https://eips.ethereum.org/EIPS/eip-6800). +""" + +import pytest + +from ethereum_test_tools import ( + Account, + Address, + Block, + BlockchainTestFiller, + Environment, + TestAddress, + TestAddress2, + 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", + [ + TestAddress2, + precompile_address, + ], + ids=["no_precompile", "precompile"], +) +@pytest.mark.parametrize( + "value", + [0, 6], + ids=["zero", "non_zero"], +) +def test_transfer_eth(blockchain_test: BlockchainTestFiller, fork: str, target, value): + """ + Test that value transfer works as expected targeting accounts and precompiles. + """ + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=target, + gas_limit=100000000, + gas_price=10, + value=value, + ) + blocks = [Block(txs=[tx])] + + post = { + target: Account( + balance=value, + ), + } + + blockchain_test( + genesis_environment=env, + pre=pre, + post=post, + blocks=blocks, + ) diff --git a/tests/verkle/eip7709_blockhash_witness/__init__.py b/tests/verkle/eip7709_blockhash_witness/__init__.py new file mode 100644 index 0000000000..35d3cfd2c2 --- /dev/null +++ b/tests/verkle/eip7709_blockhash_witness/__init__.py @@ -0,0 +1,6 @@ +""" +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). +""" diff --git a/tests/verkle/eip7709_blockhash_witness/test_blockhash_instruction.py b/tests/verkle/eip7709_blockhash_witness/test_blockhash_instruction.py new file mode 100644 index 0000000000..163f97fbe4 --- /dev/null +++ b/tests/verkle/eip7709_blockhash_witness/test_blockhash_instruction.py @@ -0,0 +1,114 @@ +""" +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, +) +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 + + +# TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "block_num_target", + [ + block_number + 10, + block_number, + block_number - 1, + block_number - BLOCKHASH_OLD_WINDOW, + block_number - BLOCKHASH_OLD_WINDOW - 1, + ], + ids=[ + "future_block", + "current_block", + "previous_block", + "last_supported_block", + "too_old_block", + ], +) +def test_blockhash(blockchain_test: BlockchainTestFiller, fork: str, block_num_target: int): + """ + Test BLOCKHASH witness. + """ + _blockhash(blockchain_test, fork, block_num_target) + + +# 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): + """ + Test BLOCKHASH with insufficient gas. + """ + _blockhash(blockchain_test, fork, block_number - 1, gas_limit=21_042, fail=True) + + +def _blockhash( + blockchain_test: BlockchainTestFiller, + fork: str, + block_num_target: int, + gas_limit=1_000_000, + fail=False, +): + env = Environment( + fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + pre = { + TestAddress: Account(balance=1000000000000000000000), + TestAddress2: Account(code=Op.BLOCKHASH(block_num_target)), + } + + tx = Transaction( + ty=0x0, + chain_id=0x01, + nonce=0, + to=TestAddress2, + 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) + + 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 new file mode 100644 index 0000000000..716557b891 --- /dev/null +++ b/tests/verkle/eip7709_blockhash_witness/test_filling.py @@ -0,0 +1,21 @@ +""" +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 new file mode 100644 index 0000000000..e70634d7e4 --- /dev/null +++ b/tests/verkle/temp_verkle_helpers.py @@ -0,0 +1,123 @@ +""" +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 3df47c4624..15d8e4556e 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -124,6 +124,7 @@ eips EIPs el endianness +endian EngineAPI enum env @@ -566,6 +567,7 @@ returndatasize returndatacopy returndataload extcodehash +codehash blockhash coinbase timestamp @@ -624,6 +626,8 @@ push29 push30 push31 push32 +pushn +pushns dup1 dup2 dup3 @@ -673,6 +677,7 @@ create create2 create3 create4 +authcall call callcode return @@ -698,6 +703,10 @@ modexp 0x00 0x01 0x10 +verkle +vkt +chunkify +chunkified fi url