diff --git a/src/ethereum_test_types/verkle/helpers.py b/src/ethereum_test_types/verkle/helpers.py index 107aa434b5..f8f69f9577 100644 --- a/src/ethereum_test_types/verkle/helpers.py +++ b/src/ethereum_test_types/verkle/helpers.py @@ -4,7 +4,7 @@ from typing import List -from ethereum_test_base_types.base_types import Hash +from ethereum_test_types.verkle.types import Hash def chunkify_code(code: bytes) -> List[Hash]: @@ -13,6 +13,7 @@ def chunkify_code(code: bytes) -> List[Hash]: Used to generate code chunks for Witness state diff verification. """ + code = bytes(code) if len(code) % 31 != 0: code += b"\x00" * (31 - (len(code) % 31)) bytes_to_exec_data = [0] * (len(code) + 32) diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_contract_execution.py b/tests/verkle/eip4762_verkle_gas_witness/test_contract_execution.py index c749b4fed2..9ca5c43847 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_contract_execution.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_contract_execution.py @@ -52,27 +52,29 @@ def code_with_jumps(size, jumps: list[Jump | Jumpi] = []): """ Returns the requested code with defined size, jumps, and PUSHNs. """ - code = Op.PUSH0 * size + code = Op.JUMPDEST * size result_code = bytes() last_offset = 0 for j in jumps: result_code += bytes(code)[last_offset : j.offset] - if isinstance(j, Jump): - jump_code = bytes(Op.JUMP(j.pc)) - elif isinstance(j, Jumpi): + if isinstance(j, Jumpi): jump_code = bytes(Op.JUMPI(j.pc, 1 if j.condition else 0)) + elif isinstance(j, Jump): + jump_code = bytes(Op.JUMP(j.pc)) result_code += jump_code last_offset = j.offset + len(jump_code) - result_code += bytes(Op.JUMPDEST) result_code += bytes(code)[last_offset:] + + assert len(result_code) == size + return result_code # TODO(verkle): update to Osaka when t8n supports the fork. @pytest.mark.valid_from("Verkle") @pytest.mark.parametrize( - "bytecode, gas_limit, witness_code_chunk_numbers", + "bytecode, gas_limit, witness_code_chunk_ranges", [ ( # only_code_in_account_header code_with_jumps(10), @@ -82,75 +84,89 @@ def code_with_jumps(size, jumps: list[Jump | Jumpi] = []): ( # chunks_both_in_and_out_account_header code_with_jumps(128 * 31 + 100), 1_000_000, - [[0, 132]], + [[0, 131]], ), ( # touches_only_first_byte_code_chunk code_with_jumps(128 * 31 + 1), 1_000_000, - [[0, 129]], + [[0, 128]], ), ( # touches_only_last_byte_code_chunk - code_with_jumps(128 * 31 + 100, [Jump(10, 128 * 31 + 100)]), + code_with_jumps(128 * 31 + 100, [Jump(10, 128 * 31 + 9)]), 1_000_000, - [[0, 0], [132, 132]], + [[0, 0], [131, 131]], ), ( # pushn_with_data_in_chunk_that_cant_be_paid Op.PUSH0 * 30 + Op.PUSH1(42), - 42, + 21000, [[0, 0]], ), ( # jump_to_jumpdest_in_pushn_data - Op.PUSH0 * 10 + Op.JUMP(10 + 2 + 1 + 1000) + Op.PUSH0 * 1000 + Op.PUSH1(0x5B), + Op.PUSH0 * 10 + + Op.JUMP(10 + 3 + 1 + 1000 + 1) # 3+1=PUSH2+JUMP + + Op.PUSH0 * 1000 + + Op.PUSH1(0x5B) + + Op.PUSH0 * 100, # Add more code, but can't be executed due to the invalid jump 1_000_000, - [[0, 0], [33, 33]], + [[0, 0], [32, 32]], ), ( # jumpi_to_jumpdest_in_pushn_data - Op.PUSH0 * 10 + Op.JUMPI(10 + 3 + 1 + 1000, 1) + Op.PUSH0 * 1000 + Op.PUSH1(0x5B), + bytes( + Op.PUSH0 * 10 + + Op.JUMPI(10 + 5 + 1 + 1000 + 1, 1) # 5+1=PUSH1+PUSH2+JUMPI + + Op.PUSH0 * 1000 + + Op.PUSH1(0x5B) + + Op.PUSH0 * 100 + ), 1_000_000, - [[0, 0], [33, 33]], + [[0, 0], [32, 32]], ), ( # jump_to_non_jumpdest_destiny - Op.PUSH0 * 10 + Op.JUMP(10 + 2 + 1 + 1000) + Op.PUSH0 * 1000 + Op.ORIGIN, + bytes( + Op.PUSH0 * 10 + Op.JUMP(10 + 3 + 1 + 1000) + Op.PUSH0 * 1000 + Op.ORIGIN + ), # 3+1=PUSH2+JUMP 1_000_000, - [[0, 0], [33, 33]], + [[0, 0], [32, 32]], ), ( # jumpi_to_non_jumpdest_destiny - Op.PUSH0 * 10 + Op.JUMPI(10 + 3 + 1 + 1000, 1) + Op.PUSH0 * 1000 + Op.ORIGIN, + bytes( + Op.PUSH0 * 10 + Op.JUMPI(10 + 5 + 1 + 1000, 1) + Op.PUSH0 * 1000 + Op.ORIGIN + ), # 5+1=PUSH1+PUSH2+JUMPI 1_000_000, - [[0, 0], [33, 33]], + [[0, 0], [32, 32]], ), ( # linear_execution_stopping_at_first_byte_of_next_chunk code_with_jumps(128 * 31 + 1), 1_000_000, - [[0, 129]], + [[0, 128]], ), ( # false_jumpi code_with_jumps(150 * 31 + 10, [Jumpi(50, 1000, False)]), 1_000_000, - [[0, 151]], + [[0, 150]], ), ( # insufficient_gas_for_jump_instruction - code_with_jumps(150 * 31, [Jump(50, 1000)]), - 142, - [[0, 1]], + code_with_jumps(150 * 31, [Jump(10, 1000)]), + 21000 + 200 + 10 + 3, + [[0, 0]], ), ( # insufficient_gas_for_jumpi_instruction - code_with_jumps(150 * 31, [Jumpi(50, 1000, True)]), - 142, - [[0, 1]], + code_with_jumps(150 * 31, [Jumpi(10, 1000, True)]), + 21000 + 200 + 10 + 3 + 3, + [[0, 0]], ), ( # sufficient_gas_for_jump_instruction_but_not_for_code_chunk - code_with_jumps(150 * 31, [Jump(50, 1000)]), - 42, - [[0, 0], [33, 33]], + code_with_jumps(150 * 31, [Jump(10, 1000)]), + 21000 + 200 + 10 + 3 + 8, + [[0, 0]], ), ( # sufficient_gas_for_jumpi_instruction_but_not_for_code_chunk - code_with_jumps(150 * 31, [Jumpi(50, 1000, True)]), - 42, - [[0, 0], [33, 33]], + code_with_jumps(150 * 31, [Jumpi(10, 1000, True)]), + 21000 + 200 + 10 + 3 + 3 + 10, + [[0, 0]], ), ( # jump_outside_code_size - code_with_jumps(150 * 31, [Jump(50, 150 * 31 + 42)]), + code_with_jumps(150 * 31, [Jump(10, 150 * 31 + 42)]), 1_000_000, [[0, 0]], ), @@ -200,9 +216,9 @@ def code_with_jumps(size, jumps: list[Jump | Jumpi] = []): ) def test_contract_execution( blockchain_test: BlockchainTestFiller, - bytecode, - gas_limit, - witness_code_chunk_numbers, + bytecode: bytes, + gas_limit: int, + witness_code_chunk_ranges, ): """ Test that contract execution generates expected witness. @@ -228,16 +244,17 @@ def test_contract_execution( ) code_chunks = chunkify_code(bytecode) - assert len(code_chunks) > 1 + assert len(code_chunks) > 0 witness_check = WitnessCheck() for address in [TestAddress, TestAddress2, env.fee_recipient]: witness_check.add_account_full( address=address, - account=(None if address == env.fee_recipient else pre[address]), + account=pre.get(address), ) - for chunk_number in witness_code_chunk_numbers: - witness_check.add_code_chunk(TestAddress2, chunk_number, code_chunks[chunk_number]) + for chunk_ranges in witness_code_chunk_ranges: + for chunk_number in range(chunk_ranges[0], chunk_ranges[1] + 1): + witness_check.add_code_chunk(TestAddress2, chunk_number, code_chunks[chunk_number]) blocks = [ Block( diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_creates.py b/tests/verkle/eip4762_verkle_gas_witness/test_creates.py index 1e098e7ed2..f081896a45 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_creates.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_creates.py @@ -41,50 +41,74 @@ ], ) @pytest.mark.parametrize( - "code_size, value", + "code_size", [ - (0, 0), - (127 * 32, 0), - (130 * 32, 0), - (130 * 32 + 1, 0), - (130 * 32 + 1, 1), + 0, + 127 * 31, + 130 * 31, + 130 * 31 + 1, ], ids=[ "empty", "all_chunks_in_account_header", "chunks_outside_account_header", "with_partial_code_chunk", - "with_value", ], ) -def test_create( - blockchain_test: BlockchainTestFiller, create_instruction: Opcode, value, code_size -): +def test_create(blockchain_test: BlockchainTestFiller, create_instruction: Opcode, code_size): """ Test tx contract creation and *CREATE witness. """ - contract_code = Op.PUSH0 * code_size - if create_instruction is None or create_instruction == Op.CREATE: + contract_code = bytes(Op.PUSH0 * code_size) + if create_instruction is None: contract_address = compute_create_address(address=TestAddress, nonce=0) + elif create_instruction == Op.CREATE: + contract_address = compute_create_address(address=TestAddress2, nonce=0) else: contract_address = compute_create2_address( - TestAddress, 0xDEADBEEF, Initcode(deploy_code=contract_code) + TestAddress2, 0xDEADBEEF, Initcode(deploy_code=contract_code) ) num_code_chunks = (len(contract_code) + 30) // 31 - code_chunks = chunkify_code(contract_code) witness_check_extra = WitnessCheck() witness_check_extra.add_account_full(contract_address, None) for i in range(num_code_chunks): - witness_check_extra.add_code_chunk(contract_address, i, code_chunks[i]) # type: ignore + witness_check_extra.add_code_chunk(contract_address, i, None) _create( blockchain_test, create_instruction, witness_check_extra, contract_code, - value=value, + value=0, + ) + + +@pytest.mark.valid_from("Verkle") +@pytest.mark.parametrize( + "create_instruction", + [ + Op.CREATE, + Op.CREATE2, + ], +) +def test_create_with_value_insufficient_balance( + blockchain_test: BlockchainTestFiller, + create_instruction: Opcode, +): + """ + Test tx contract creation and *CREATE value-bearing without sufficient balance. + """ + contract_code = bytes(Op.PUSH0 * 10) + + _create( + blockchain_test, + create_instruction, + WitnessCheck(), + contract_code, + value=100, + creator_balance=0, ) @@ -197,7 +221,7 @@ def test_create_collision( create_instruction, ): """ - Test *CREATE with address collision. + Test tx contract creation and *CREATE with address collision. """ _create( blockchain_test, @@ -227,12 +251,14 @@ def test_big_calldata( 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_code = bytes(Op.PUSH0 * (1000 * 31 + 42)) + if create_instruction is None: contract_address = compute_create_address(address=TestAddress, nonce=0) + elif create_instruction == Op.CREATE: + contract_address = compute_create_address(address=TestAddress2, nonce=0) else: contract_address = compute_create2_address( - TestAddress, 0xDEADBEEF, Initcode(deploy_code=contract_code) + TestAddress2, 0xDEADBEEF, Initcode(initcode_prefix=Op.STOP, deploy_code=contract_code) ) witness_check_extra = WitnessCheck() @@ -242,7 +268,7 @@ def test_big_calldata( _create( blockchain_test, create_instruction, - WitnessCheck(), + witness_check_extra, contract_code, value=0, initcode_stop_prefix=True, @@ -254,10 +280,11 @@ def _create( create_instruction: Opcode | None, witness_check_extra: WitnessCheck, contract_code, - value=1, + value: int = 0, gas_limit=10000000000, generate_collision: bool = False, initcode_stop_prefix: bool = False, + creator_balance: int = 0, ): env = Environment( fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", @@ -267,7 +294,7 @@ def _create( timestamp=1000, ) pre = { - TestAddress: Account(balance=1000000000000000000000), + TestAddress: Account(balance=1_000_000_000_000), } deploy_code = Initcode( @@ -275,24 +302,26 @@ def _create( ) if create_instruction is not None and create_instruction.int() == Op.CREATE.int(): pre[TestAddress2] = Account( - code=Op.CALLDATACOPY(0, 0, len(deploy_code)) + Op.CREATE(value, 0, len(deploy_code)) + code=Op.CALLDATACOPY(0, 0, len(deploy_code)) + Op.CREATE(value, 0, len(deploy_code)), + balance=creator_balance, ) tx_target = TestAddress2 tx_value = 0 tx_data = deploy_code if generate_collision: - contract_address = compute_create_address(address=TestAddress, nonce=0) + contract_address = compute_create_address(address=TestAddress2, nonce=0) pre[contract_address] = Account(nonce=1) elif create_instruction is not None and create_instruction.int() == Op.CREATE2.int(): pre[TestAddress2] = Account( code=Op.CALLDATACOPY(0, 0, len(deploy_code)) - + Op.CREATE2(value, 0, len(deploy_code), 0xDEADBEEF) + + Op.CREATE2(value, 0, len(deploy_code), 0xDEADBEEF), + balance=creator_balance, ) tx_target = TestAddress2 tx_value = 0 tx_data = deploy_code if generate_collision: - contract_address = compute_create2_address(TestAddress, 0xDEADBEEF, deploy_code) + contract_address = compute_create2_address(TestAddress2, 0xDEADBEEF, deploy_code) pre[contract_address] = Account(nonce=1) else: tx_target = None diff --git a/tests/verkle/eip4762_verkle_gas_witness/test_sload.py b/tests/verkle/eip4762_verkle_gas_witness/test_sload.py index 774a529e38..46727eaa4b 100644 --- a/tests/verkle/eip4762_verkle_gas_witness/test_sload.py +++ b/tests/verkle/eip4762_verkle_gas_witness/test_sload.py @@ -19,12 +19,13 @@ WitnessCheck, ) from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_types.verkle.types import Hash # TODO(verkle): Update reference spec version REFERENCE_SPEC_GIT_PATH = "EIPS/eip-4762.md" REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" -TestAddress2Storage = {0: 0xAA, 1000: 0xBB} +TestAddress2Storage: dict[int, Hash] = {0: Hash(0xAA), 1000: Hash(0xBB)} # TODO(verkle): update to Osaka when t8n supports the fork. @@ -60,6 +61,7 @@ def test_sload(blockchain_test: BlockchainTestFiller, storage_slot_accesses): # TODO(verkle): update to Osaka when t8n supports the fork. +@pytest.mark.skip("Unskip when geth fixes Touch* witness inclusion with insufficient gas") @pytest.mark.valid_from("Verkle") def test_sload_insufficient_gas(blockchain_test: BlockchainTestFiller, fork: str): """ @@ -69,12 +71,11 @@ def test_sload_insufficient_gas(blockchain_test: BlockchainTestFiller, fork: str for slot in [1000, 1001]: witness_check_extra.add_storage_slot(TestAddress2, slot, TestAddress2Storage.get(slot)) - _sload(blockchain_test, [1000, 1001, 1002, 1003], witness_check_extra, gas_limit=21_024) + _sload(blockchain_test, [1000, 1001, 1002, 1003], witness_check_extra, gas_limit=23_506) def _sload( blockchain_test: BlockchainTestFiller, - fork: str, storage_slot_accesses: list[int], witness_check_extra: WitnessCheck, gas_limit=1_000_000, @@ -111,7 +112,7 @@ def _sload( for address in [TestAddress, TestAddress2, env.fee_recipient]: witness_check.add_account_full( address=address, - account=(None if address == env.fee_recipient else pre[address]), + account=pre.get(address), ) blocks = [