From 87ca93ab3e47bcfaa4888c081c48b51291ddbfc3 Mon Sep 17 00:00:00 2001 From: Eduard S Date: Thu, 5 Jan 2023 18:08:38 +0100 Subject: [PATCH] feat: update account non-existence handling Change the approach to handle non-existing accounts by 1. Using `code_hash = 0` as a non-existing account state 2. Doing accont non-existence MPT proofs from the State Circuit on the where an account update is CodeHash and goes 0->0. Resolve https://github.com/privacy-scaling-explorations/zkevm-specs/issues/259 --- specs/evm-proof.md | 45 ++++++++++++++++++ specs/opcode/31BALANCE.md | 8 ++-- specs/opcode/3bEXTCODESIZE.md | 8 ++-- specs/opcode/3cEXTCODECOPY.md | 4 +- specs/opcode/3fEXTCODEHASH.md | 8 ++-- specs/tables.md | 1 - src/zkevm_specs/evm/execution/balance.py | 11 ++--- src/zkevm_specs/evm/execution/callop.py | 8 ++-- src/zkevm_specs/evm/execution/extcodecopy.py | 8 ++-- src/zkevm_specs/evm/execution/extcodehash.py | 15 ++---- src/zkevm_specs/evm/execution/extcodesize.py | 7 ++- src/zkevm_specs/evm/main.py | 11 +++-- src/zkevm_specs/state.py | 13 +++++- tests/evm/test_balance.py | 31 ++++++------- tests/evm/test_callop.py | 17 +++---- tests/evm/test_extcodecopy.py | 48 +++++++++----------- tests/evm/test_extcodehash.py | 28 +++++------- tests/evm/test_extcodesize.py | 31 ++++++------- 18 files changed, 167 insertions(+), 135 deletions(-) diff --git a/specs/evm-proof.md b/specs/evm-proof.md index 29634138e..e7f58810b 100644 --- a/specs/evm-proof.md +++ b/specs/evm-proof.md @@ -42,3 +42,48 @@ We call the list of random read-write access records `BusMapping` because it act The repeated chip has 2 main states, one is call initialization, another is bytecode execution. **TODO** + +## Account non-existence + +The following opcodes contain special cases for non-existing accounts: +- BALANCE +- EXTCODEHASH +- EXTCODECOPY +- EXTCODESIZE +- CALL +- CALLCODE +- DELEGATECALL +- STATICCALL + +The rest of the opcodes only deal with accounts that are known to exist (by previous conditions). + +We encode the state of existence of an account with its value of the +`code_hash` in the `rw_table` (managed by the State Circuit): `code_hash = 0` +means that the account doesn't exist, and `code_hash != 0` means that the +account exists. We can guarantee this fact with the following properties: +- Every time an account is created, the `code_hash` is set from zero to a + non-zero value. + - By the read consistency guaranteed by the State Circuit, we know that if a + `code_hash` is non-zero, the account must exist (because it has been created + previously) +- A `code_hash` read with value 0 is translated to a non-existence account + proof in the MPT (via the state circuit). + - From this we know that if a `code_hash` is read as zero, the account + doesn't exist. +- There are no other valid transitions of `code_hash`. In summary, the valid + transitions are: `0->0`, and `0->H` (where H != 0). Note that we're not + considering account destruction for now. + +Since only `code_hash` encodes account existence, we must be careful to never +read (lookup) another account property unless we have checked that the account +exists. This is because the RwTable in the State Circuit has all the entries +sorted by [Tag, FieldTag], so `CodeHash`, `Nonce` and `Balance` are treated +independently, which means that a lookup to `Nonce` or `Balance` could suceed +on a non-existing account if the account is created afterwards. For this +reason we must guarantee that: +- A `Nonce` and `Balance` lookup to an account must only be performed if we + have previously verified that the account exists by reading its `CodeHash` + and checking it to be non-zero. This check can be done in the same step (for + opcodes that can deal with non-existing accounts), or in a previous step (as + a precondition, for opcodes that work with the caller account). + diff --git a/specs/opcode/31BALANCE.md b/specs/opcode/31BALANCE.md index 05311532a..67fdefd11 100644 --- a/specs/opcode/31BALANCE.md +++ b/specs/opcode/31BALANCE.md @@ -23,20 +23,20 @@ stack instead. 1. opId = 0x31 2. State transition: - - gc + 7 (1 stack read, 1 stack write, 3 call context reads, 1 account reads, + - gc + 7 (1 stack read, 1 stack write, 3 call context reads, 1/2 account reads, 1 transaction access list write) - stack_pointer + 0 (one pop and one push) - pc + 1 - gas: - the accessed `address` is warm: GAS_COST_WARM_ACCESS - the accessed `address` is cold: GAS_COST_ACCOUNT_COLD_ACCESS -3. Lookups: 7 busmapping lookups +3. Lookups: 7/8 busmapping lookups - `address` is at top of the stack. - 3 reads from call context for `tx_id`, `rw_counter_end_of_reversion`, and `is_persistent`. - `address` is added to the transaction access list if not already present. - - If witness value `exists == 1`, lookup account `balance`. Otherwise lookup - account non-existing proof. + - 1 account `code_hash` lookup to determine account existence. + - If account exists, lookup account `balance`. - The BALANCE result is at the new top of the stack. 4. Additional Constraints - value `is_warm` matches the gas cost for this opcode. diff --git a/specs/opcode/3bEXTCODESIZE.md b/specs/opcode/3bEXTCODESIZE.md index e5433afa7..13dd588fd 100644 --- a/specs/opcode/3bEXTCODESIZE.md +++ b/specs/opcode/3bEXTCODESIZE.md @@ -8,8 +8,8 @@ The `EXTCODESIZE` opcode gets code size of the given account. The `EXTCODESIZE` opcode pops `address` (20 bytes of data) off the stack and pushes the code size of the corresponding account onto the stack. If the given -account doesn't exist (by checking non existing flag), then it will push 0 onto -the stack instead. +account doesn't exist (by checking that `code_hash == 0`), then it will push 0 +onto the stack instead. ## Circuit behaviour @@ -35,8 +35,8 @@ the stack instead. - 3 from call context for `tx_id`, `rw_counter_end_of_reversion`, and `is_persistent`. - `address` is added to the transaction access list if not already present. - - If witness value `exists == 1`, lookup account `code_hash`, then get - `code_size`. Otherwise only lookup the account non-existing proof. + - Lookup account `code_hash`, then get `code_size` if `code_hash != 0`, + otherwise skip the `code_size` lookup. - The EXTCODESIZE result is at the new top of the stack. 4. Additional Constraints - value `is_warm` matches the gas cost for this opcode. diff --git a/specs/opcode/3cEXTCODECOPY.md b/specs/opcode/3cEXTCODECOPY.md index 1fada3626..221a11fce 100644 --- a/specs/opcode/3cEXTCODECOPY.md +++ b/specs/opcode/3cEXTCODECOPY.md @@ -25,8 +25,8 @@ The `EXTCODECOPY` circuit constrains the values popped from stack, call context/ - `memory_offset` is at the second position of the stack - `code_offset` is at the third position of the stack - `size` is at the fourth position of the stack - - `code_hash` from the address if witness value `exists == 1`. Otherwise only lookup the account non-existing proof. - - `code_size` from the bytecode table + - `code_hash` from the address (will be 0 when the account doesn't exist). + - `code_size` from the bytecode table if account exists (`code_hash != 0`) - `tx_id`, `rw_counter_end_of_reversion`, `is_persistent` from call context - `address` is added to the transaction access list if not already present diff --git a/specs/opcode/3fEXTCODEHASH.md b/specs/opcode/3fEXTCODEHASH.md index 321b0239d..50e487221 100644 --- a/specs/opcode/3fEXTCODEHASH.md +++ b/specs/opcode/3fEXTCODEHASH.md @@ -4,8 +4,9 @@ The `EXTCODEHASH` opcode pops `address` off the stack and pushes the code hash of the corresponding account onto the stack. If the corresponding account -doesn't exist (by checking non existing flag), then it will push 0 onto the -stack instead. +doesn't exist then it will push 0 onto the stack instead. Since we use +`code_hash = 0` to encode non-existing accounts, we can use the lookup result +directly. ## Constraints @@ -23,8 +24,7 @@ stack instead. - 3 from call context for `tx_id`, `rw_counter_end_of_reversion`, and `is_persistent`. - `address` is added to the transaction access list if not already present. - - If witness value `exists == 1`, lookup account `code_hash`. Otherwise - lookup account non-existing proof. + - lookup account `code_hash`. - The EXTCODEHASH result is at the new top of the stack. 4. Additional Constraints - value `is_warm` matches the gas cost for this opcode. diff --git a/specs/tables.md b/specs/tables.md index 64f80e2ec..62dca7743 100644 --- a/specs/tables.md +++ b/specs/tables.md @@ -79,7 +79,6 @@ NOTE: `kN` means `keyN` | $counter | $isWrite | Account | | $address | Nonce | | $value | $committedValue | $root | | $counter | $isWrite | Account | | $address | Balance | | $value | $committedValue | $root | | $counter | $isWrite | Account | | $address | CodeHash | | $value | $committedValue | $root | -| $counter | $isWrite | Account | | $address | NonExisting | | 0 | 0 | $root | | $counter | true | AccountDestructed | | $address | | | $value | 0 | $root | | | | | | | | | | | | | | | *CallContext constant* | | | *CallContextFieldTag* (ro) | | | | | diff --git a/src/zkevm_specs/evm/execution/balance.py b/src/zkevm_specs/evm/execution/balance.py index 4280a82a4..1f7ef9588 100644 --- a/src/zkevm_specs/evm/execution/balance.py +++ b/src/zkevm_specs/evm/execution/balance.py @@ -13,11 +13,10 @@ def balance(instruction: Instruction): tx_id = instruction.call_context_lookup(CallContextFieldTag.TxId) is_warm = instruction.add_account_to_access_list(tx_id, address, instruction.reversion_info()) - # Load account `exists` value from auxilary witness data. - exists = instruction.curr.aux_data - - if exists == 0: - instruction.account_read(address, AccountFieldTag.NonExisting) + # Check account existence with code_hash != 0 + exists = FQ(1) - instruction.is_zero( + instruction.account_read(address, AccountFieldTag.CodeHash) + ) balance = instruction.account_read(address, AccountFieldTag.Balance) if exists == 1 else RLC(0) @@ -28,7 +27,7 @@ def balance(instruction: Instruction): instruction.step_state_transition_in_same_context( opcode, - rw_counter=Transition.delta(7), + rw_counter=Transition.delta(7 + exists.n), program_counter=Transition.delta(1), stack_pointer=Transition.same(), dynamic_gas_cost=instruction.select(is_warm, FQ(0), FQ(EXTRA_GAS_COST_ACCOUNT_COLD_ACCESS)), diff --git a/src/zkevm_specs/evm/execution/callop.py b/src/zkevm_specs/evm/execution/callop.py index 149435b16..648060eb8 100644 --- a/src/zkevm_specs/evm/execution/callop.py +++ b/src/zkevm_specs/evm/execution/callop.py @@ -128,17 +128,15 @@ def callop(instruction: Instruction): ) instruction.constrain_zero(1 - value_lt_caller_balance - value_eq_caller_balance) - # Load callee account `exists` value from auxilary witness data. - callee_exists = instruction.curr.aux_data + callee_code_hash = instruction.account_read(code_address, AccountFieldTag.CodeHash) + # Check calle account existence with code_hash != 0 + callee_exists = FQ(1) - instruction.is_zero(callee_code_hash) if callee_exists == 1: - # Get callee code hash. - callee_code_hash = instruction.account_read(code_address, AccountFieldTag.CodeHash) is_empty_code_hash = instruction.is_equal( callee_code_hash, instruction.rlc_encode(EMPTY_CODE_HASH, 32) ) else: # callee_exists == 0 - instruction.account_read(code_address, AccountFieldTag.NonExisting) is_empty_code_hash = FQ(1) # Verify gas cost. diff --git a/src/zkevm_specs/evm/execution/extcodecopy.py b/src/zkevm_specs/evm/execution/extcodecopy.py index 368102ab3..8c704c6ed 100644 --- a/src/zkevm_specs/evm/execution/extcodecopy.py +++ b/src/zkevm_specs/evm/execution/extcodecopy.py @@ -19,14 +19,12 @@ def extcodecopy(instruction: Instruction): tx_id = instruction.call_context_lookup(CallContextFieldTag.TxId) is_warm = instruction.add_account_to_access_list(tx_id, address, instruction.reversion_info()) - # Load account `exists` value from auxilary witness data. - exists = instruction.curr.aux_data + code_hash = instruction.account_read(address, AccountFieldTag.CodeHash) + # Check account existence with code_hash != 0 + exists = FQ(1) - instruction.is_zero(code_hash) if exists == 1: - code_hash = instruction.account_read(address, AccountFieldTag.CodeHash) code_size = instruction.bytecode_length(code_hash.expr()) else: - instruction.account_read(address, AccountFieldTag.NonExisting) - code_hash = RLC(0) code_size = RLC(0) next_memory_size, memory_expansion_gas_cost = instruction.memory_expansion_dynamic_length( diff --git a/src/zkevm_specs/evm/execution/extcodehash.py b/src/zkevm_specs/evm/execution/extcodehash.py index 4bfb85ceb..33dd38edf 100644 --- a/src/zkevm_specs/evm/execution/extcodehash.py +++ b/src/zkevm_specs/evm/execution/extcodehash.py @@ -1,4 +1,4 @@ -from ...util import EXTRA_GAS_COST_ACCOUNT_COLD_ACCESS, FQ, N_BYTES_ACCOUNT_ADDRESS, RLC +from ...util import EXTRA_GAS_COST_ACCOUNT_COLD_ACCESS, FQ, N_BYTES_ACCOUNT_ADDRESS from ..instruction import Instruction, Transition from ..opcode import Opcode from ..table import AccountFieldTag, CallContextFieldTag @@ -13,18 +13,11 @@ def extcodehash(instruction: Instruction): tx_id = instruction.call_context_lookup(CallContextFieldTag.TxId) is_warm = instruction.add_account_to_access_list(tx_id, address, instruction.reversion_info()) - # Load account `exists` value from auxilary witness data. - exists = instruction.curr.aux_data - - if exists == 0: - instruction.account_read(address, AccountFieldTag.NonExisting) - - code_hash = ( - instruction.account_read(address, AccountFieldTag.CodeHash) if exists == 1 else RLC(0) - ) + # We already define code_hash to be 0 when the account doesn't exist. + code_hash = instruction.account_read(address, AccountFieldTag.CodeHash) instruction.constrain_equal( - instruction.select(exists, code_hash.expr(), FQ(0)), + code_hash.expr(), instruction.stack_push(), ) diff --git a/src/zkevm_specs/evm/execution/extcodesize.py b/src/zkevm_specs/evm/execution/extcodesize.py index d67619fcf..1c7f8273a 100644 --- a/src/zkevm_specs/evm/execution/extcodesize.py +++ b/src/zkevm_specs/evm/execution/extcodesize.py @@ -19,14 +19,13 @@ def extcodesize(instruction: Instruction): tx_id = instruction.call_context_lookup(CallContextFieldTag.TxId) is_warm = instruction.add_account_to_access_list(tx_id, address, instruction.reversion_info()) - # Load account `exists` value from auxilary witness data. - exists = instruction.curr.aux_data + code_hash = instruction.account_read(address, AccountFieldTag.CodeHash) + # Check account existence with code_hash != 0 + exists = FQ(1) - instruction.is_zero(code_hash) if exists == 1: - code_hash = instruction.account_read(address, AccountFieldTag.CodeHash) code_size = instruction.bytecode_length(code_hash) else: # exists == 0 - instruction.account_read(address, AccountFieldTag.NonExisting) code_size = RLC(0) instruction.constrain_equal( diff --git a/src/zkevm_specs/evm/main.py b/src/zkevm_specs/evm/main.py index 2caf8ec19..8c6cee4e4 100644 --- a/src/zkevm_specs/evm/main.py +++ b/src/zkevm_specs/evm/main.py @@ -22,7 +22,7 @@ def verify_steps( if end_with_last_step: steps.append(DUMMY_STEP_STATE) - ok = True + exception = None for idx, (curr, next) in enumerate(zip(steps, steps[1:])): try: verify_step( @@ -36,8 +36,13 @@ def verify_steps( ) ) except AssertionError as e: - ok = False - assert ok == success + exception = e + if success: + if exception: + raise exception + assert exception is None + else: + assert exception is not None def verify_step(instruction: Instruction): diff --git a/src/zkevm_specs/state.py b/src/zkevm_specs/state.py index cf161232e..cbf392cf8 100644 --- a/src/zkevm_specs/state.py +++ b/src/zkevm_specs/state.py @@ -257,6 +257,8 @@ def check_storage(row: Row, row_prev: Row, row_next: Row, tables: Tables): # 4.0. Unused keys are 0 assert row.field_tag() == 0 + # value = 0 means the leaf doesn't exist. 0->0 transition requires a + # non-existing proof. is_non_exist = FQ(row.value.expr() == FQ(0)) * FQ(row.committed_value.expr() == FQ(0)) # 4.1. MPT lookup for last access to (address, storage_key) @@ -304,11 +306,20 @@ def check_account(row: Row, row_prev: Row, row_next: Row, tables: Tables): assert row.id() == 0 assert row.storage_key() == 0 + # We use code_hash = 0 as non-existing account state. code_hash: 0->0 + # transition requires a non-existing proof. + is_non_exist = ( + FQ(row.value.expr() == FQ(0)) + * FQ(row.committed_value.expr() == FQ(0)) + * FQ(field_tag == FQ(AccountFieldTag.CodeHash)) + ) + # 6.2. MPT storage lookup for last access to (address, field_tag) if not all_keys_eq(row, row_next): tables.mpt_lookup( get_addr(row), - FQ(proof_type), + is_non_exist * FQ(MPTProofType.NonExistingAccountProof) + + (1 - is_non_exist) * FQ(proof_type), row.storage_key(), row.value, row.committed_value, diff --git a/tests/evm/test_balance.py b/tests/evm/test_balance.py index f0e32da01..923d82b0a 100644 --- a/tests/evm/test_balance.py +++ b/tests/evm/test_balance.py @@ -14,32 +14,33 @@ from zkevm_specs.util import ( EXTRA_GAS_COST_ACCOUNT_COLD_ACCESS, GAS_COST_WARM_ACCESS, + FQ, + EMPTY_CODE_HASH, RLC, U160, U256, rand_address, - rand_bytes, rand_fq, rand_range, rand_word, ) TESTING_DATA = [ - (0x30000, 0, 0, True, True), - (0x30000, 0, 0, False, True), - (0x30000, 200, 1, True, True), - (0x30000, 200, 1, False, True), + (0x30000, 0, False, True, True), + (0x30000, 0, False, False, True), + (0x30000, 200, True, True, True), + (0x30000, 200, True, False, True), ( rand_address(), rand_word(), - rand_range(2), + rand_range(2) == 0, rand_range(2) == 0, True, # persistent call ), ( rand_address(), rand_word(), - rand_range(2), + rand_range(2) == 0, rand_range(2) == 0, False, # reverted call ), @@ -47,10 +48,10 @@ @pytest.mark.parametrize("address, balance, exists, is_warm, is_persistent", TESTING_DATA) -def test_balance(address: U160, balance: U256, exists: int, is_warm: bool, is_persistent: bool): +def test_balance(address: U160, balance: U256, exists: bool, is_warm: bool, is_persistent: bool): randomness = rand_fq() - result = balance if exists == 1 else 0 + result = balance if exists else 0 tx_id = 1 call_id = 1 @@ -75,12 +76,11 @@ def test_balance(address: U160, balance: U256, exists: int, is_warm: bool, is_pe rw_counter_of_reversion=rw_counter_end_of_reversion - reversible_write_counter, ) ) - if exists == 1: + rw_dictionary.account_read( + address, AccountFieldTag.CodeHash, RLC(EMPTY_CODE_HASH if exists else 0, randomness) + ) + if exists: rw_dictionary.account_read(address, AccountFieldTag.Balance, RLC(balance, randomness)) - else: - rw_dictionary.account_read( - address, AccountFieldTag.NonExisting, RLC(1 - exists, randomness) - ) rw_table = set(rw_dictionary.stack_write(call_id, 1023, RLC(result, randomness)).rws) @@ -107,11 +107,10 @@ def test_balance(address: U160, balance: U256, exists: int, is_warm: bool, is_pe program_counter=0, stack_pointer=1023, gas_left=GAS_COST_WARM_ACCESS + (not is_warm) * EXTRA_GAS_COST_ACCOUNT_COLD_ACCESS, - aux_data=exists, ), StepState( execution_state=ExecutionState.STOP if is_persistent else ExecutionState.REVERT, - rw_counter=8, + rw_counter=8 + (1 if exists else 0), call_id=1, is_root=True, is_create=False, diff --git a/tests/evm/test_callop.py b/tests/evm/test_callop.py index c22a0eeff..04f6705b3 100644 --- a/tests/evm/test_callop.py +++ b/tests/evm/test_callop.py @@ -192,7 +192,7 @@ def test_callop( is_delegatecall = 1 if opcode == Opcode.DELEGATECALL else 0 is_staticcall = 1 if opcode == Opcode.STATICCALL else 0 - callee_exists = 0 if callee.is_empty() else 1 + callee_exists = not callee.is_empty() # Set `is_static == 1` for both DELEGATECALL and STATICCALL opcodes, or when # `stack.value == 0` for both CALL and CALLCODE opcodes. @@ -258,8 +258,11 @@ def test_callop( callee_bytecode = callee.code callee_bytecode_hash = callee_bytecode.hash() - is_empty_code_hash = callee_bytecode_hash == EMPTY_CODE_HASH - callee_bytecode_hash = RLC(callee_bytecode_hash, randomness) + if not callee.is_empty(): + is_empty_code_hash = callee_bytecode_hash == EMPTY_CODE_HASH + else: + is_empty_code_hash = True + callee_bytecode_hash = RLC(callee_bytecode_hash if not callee.is_empty() else 0, randomness) is_success = False if callee is CALLEE_WITH_REVERT_BYTECODE else True is_reverted_by_caller = not caller_ctx.is_persistent and is_success @@ -349,12 +352,7 @@ def test_callop( rw_dictionary \ .account_read(caller.address, AccountFieldTag.Balance, RLC(caller.balance, randomness)) - if callee_exists == 1: - rw_dictionary \ - .account_read(code_address, AccountFieldTag.CodeHash, callee_bytecode_hash) - else: - rw_dictionary \ - .account_read(code_address, AccountFieldTag.NonExisting, RLC(1, randomness)) + rw_dictionary.account_read(code_address, AccountFieldTag.CodeHash, callee_bytecode_hash) if is_empty_code_hash: rw_dictionary \ @@ -416,7 +414,6 @@ def test_callop( gas_left=caller_ctx.gas_left, memory_size=caller_ctx.memory_size, reversible_write_counter=caller_ctx.reversible_write_counter, - aux_data=callee_exists, ), ( StepState( diff --git a/tests/evm/test_extcodecopy.py b/tests/evm/test_extcodecopy.py index 83bcf8651..f1f5ee0fc 100644 --- a/tests/evm/test_extcodecopy.py +++ b/tests/evm/test_extcodecopy.py @@ -31,29 +31,29 @@ TESTING_DATA = ( # empty code - (bytes(), True, True, 1, 0x30000, 0x00, 0x00, 54), # warm account - (bytes(), False, True, 1, 0x30000, 0x00, 0x00, 54), # cold account + (bytes(), True, True, True, 0x30000, 0x00, 0x00, 54), # warm account + (bytes(), False, True, True, 0x30000, 0x00, 0x00, 54), # cold account # non-empty code - (bytes([10, 40]), True, True, 1, 0x30000, 0x00, 0x00, 54), # warm account - (bytes([10, 10]), False, True, 1, 0x30000, 0x00, 0x00, 54), # cold account + (bytes([10, 40]), True, True, True, 0x30000, 0x00, 0x00, 54), # warm account + (bytes([10, 10]), False, True, True, 0x30000, 0x00, 0x00, 54), # cold account # code length > 256 - (rand_bytes(256), True, True, 1, 0x30000, 0x00, 0x00, 54), # warm account - (rand_bytes(256), False, True, 1, 0x30000, 0x00, 0x00, 54), # cold account + (rand_bytes(256), True, True, True, 0x30000, 0x00, 0x00, 54), # warm account + (rand_bytes(256), False, True, True, 0x30000, 0x00, 0x00, 54), # cold account # out of bound cases - (rand_bytes(64), True, True, 1, 0x30000, 0x20, 0x00, 260), # warm account - (rand_bytes(64), False, True, 1, 0x30000, 0x20, 0x00, 260), # cold account + (rand_bytes(64), True, True, True, 0x30000, 0x20, 0x00, 260), # warm account + (rand_bytes(64), False, True, True, 0x30000, 0x20, 0x00, 260), # cold account # non-existing account - (bytes(), True, True, 0, 0x30000, 0x00, 0x00, 54), # warm account - (bytes(), False, True, 0, 0x30000, 0x00, 0x00, 54), # cold account + (bytes(), True, True, False, 0x30000, 0x00, 0x00, 54), # warm account + (bytes(), False, True, False, 0x30000, 0x00, 0x00, 54), # cold account # non-existing account & code length > 256 - (rand_bytes(256), True, True, 0, 0x30000, 0x00, 0x00, 54), # warm account - (rand_bytes(256), False, True, 0, 0x30000, 0x00, 0x00, 54), # cold account + (rand_bytes(256), True, True, False, 0x30000, 0x00, 0x00, 54), # warm account + (rand_bytes(256), False, True, False, 0x30000, 0x00, 0x00, 54), # cold account # non-existing account & non-empty code - (bytes([10, 40]), True, True, 0, 0x30000, 0x00, 0x00, 54), # warm account - (bytes([10, 10]), False, True, 0, 0x30000, 0x00, 0x00, 54), # cold account + (bytes([10, 40]), True, True, False, 0x30000, 0x00, 0x00, 54), # warm account + (bytes([10, 10]), False, True, False, 0x30000, 0x00, 0x00, 54), # cold account # non-existing account & out of bound cases - (rand_bytes(64), True, True, 0, 0x30000, 0x20, 0x00, 260), # warm account - (rand_bytes(64), False, True, 0, 0x30000, 0x20, 0x00, 260), # cold account + (rand_bytes(64), True, True, False, 0x30000, 0x20, 0x00, 260), # warm account + (rand_bytes(64), False, True, False, 0x30000, 0x20, 0x00, 260), # cold account ) @@ -64,14 +64,14 @@ def test_extcodecopy( code: bytes, is_warm: bool, is_persistent: bool, - exists: int, + exists: bool, address: U160, src_addr: U64, dst_addr: U64, length: U64, ): randomness = rand_fq() - code = code if exists == 1 else bytes() + code = code if exists else bytes() code_hash = int.from_bytes(keccak256(code), "big") next_memory_word_size = memory_word_size(dst_addr + length) @@ -109,12 +109,9 @@ def test_extcodecopy( rw_counter_of_reversion=rw_counter_end_of_reversion - reversible_write_counter, ) ) - if exists == 1: - rw_dictionary.account_read(address, AccountFieldTag.CodeHash, RLC(code_hash, randomness)) - else: - rw_dictionary.account_read( - address, AccountFieldTag.NonExisting, RLC(1 - exists, randomness) - ) + rw_dictionary.account_read( + address, AccountFieldTag.CodeHash, RLC(code_hash if exists else 0, randomness) + ) bytecode = Bytecode().extcodecopy() bytecode_hash = RLC(bytecode.hash(), randomness) @@ -129,7 +126,6 @@ def test_extcodecopy( program_counter=0, stack_pointer=1020, gas_left=gas_cost_extcodecopy, - aux_data=exists, ) ] @@ -142,7 +138,7 @@ def test_extcodecopy( for i in range(len(Bytecode(code).code)) ] ) - result = RLC(code_hash, randomness).rlc_value if exists == 1 else RLC(0, randomness).rlc_value + result = RLC(code_hash if exists else 0, randomness).rlc_value copy_circuit = CopyCircuit().copy( randomness, rw_dictionary, diff --git a/tests/evm/test_extcodehash.py b/tests/evm/test_extcodehash.py index afd167df6..56b1fbc13 100644 --- a/tests/evm/test_extcodehash.py +++ b/tests/evm/test_extcodehash.py @@ -27,22 +27,22 @@ ) TESTING_DATA = [ - (0x30000, bytes(), 0, True, True), # warm empty account - (0x30000, bytes(), 0, False, True), # cold empty account - (0x30000, bytes([10, 40]), 1, True, True), # warm non-empty account - (0x30000, bytes([10, 10]), 1, False, True), # cold non-empty account - (0x30000, bytes(), 1, False, True), # non-empty account with empty code + (0x30000, bytes(), False, True, True), # warm empty account + (0x30000, bytes(), False, False, True), # cold empty account + (0x30000, bytes([10, 40]), True, True, True), # warm non-empty account + (0x30000, bytes([10, 10]), True, False, True), # cold non-empty account + (0x30000, bytes(), True, False, True), # non-empty account with empty code ( rand_address(), rand_bytes(100), - rand_range(2), + rand_range(2) == 0, rand_range(2) == 0, True, # persistent call ), ( rand_address(), rand_bytes(100), - rand_range(2), + rand_range(2) == 0, rand_range(2) == 0, False, # reverted call ), @@ -50,11 +50,11 @@ @pytest.mark.parametrize("address, code, exists, is_warm, is_persistent", TESTING_DATA) -def test_extcodehash(address: U160, code: bytes, exists: int, is_warm: bool, is_persistent: bool): +def test_extcodehash(address: U160, code: bytes, exists: bool, is_warm: bool, is_persistent: bool): randomness = rand_fq() code_hash = int.from_bytes(keccak256(code), "big") - result = code_hash if exists == 1 else 0 + result = code_hash if exists else 0 tx_id = 1 call_id = 1 @@ -79,12 +79,9 @@ def test_extcodehash(address: U160, code: bytes, exists: int, is_warm: bool, is_ rw_counter_of_reversion=rw_counter_end_of_reversion - reversible_write_counter, ) ) - if exists == 1: - rw_dictionary.account_read(address, AccountFieldTag.CodeHash, RLC(code_hash, randomness)) - else: - rw_dictionary.account_read( - address, AccountFieldTag.NonExisting, RLC(1 - exists, randomness) - ) + rw_dictionary.account_read( + address, AccountFieldTag.CodeHash, RLC(code_hash if exists else 0, randomness) + ) rw_table = set(rw_dictionary.stack_write(call_id, 1023, RLC(result, randomness)).rws) @@ -111,7 +108,6 @@ def test_extcodehash(address: U160, code: bytes, exists: int, is_warm: bool, is_ program_counter=0, stack_pointer=1023, gas_left=GAS_COST_WARM_ACCESS + (not is_warm) * EXTRA_GAS_COST_ACCOUNT_COLD_ACCESS, - aux_data=exists, ), StepState( execution_state=ExecutionState.STOP if is_persistent else ExecutionState.REVERT, diff --git a/tests/evm/test_extcodesize.py b/tests/evm/test_extcodesize.py index 62e151908..09c0943ea 100644 --- a/tests/evm/test_extcodesize.py +++ b/tests/evm/test_extcodesize.py @@ -27,34 +27,34 @@ ) TESTING_DATA = [ - (0x30000, bytes(), 0, True, True), # warm empty account - (0x30000, bytes(), 0, False, True), # cold empty account - (0x30000, bytes([10, 40]), 1, True, True), # warm non-empty account - (0x30000, bytes([10, 10, 40]), 1, False, True), # cold non-empty account - (0x30000, bytes(), 1, False, True), # non-empty account with empty code + (0x30000, bytes(), False, True, True), # warm empty account + (0x30000, bytes(), False, False, True), # cold empty account + (0x30000, bytes([10, 40]), True, True, True), # warm non-empty account + (0x30000, bytes([10, 10, 40]), True, False, True), # cold non-empty account + (0x30000, bytes(), True, False, True), # non-empty account with empty code ( rand_address(), rand_bytes(100), - rand_range(2), - rand_range(2), + rand_range(2) == 0, + rand_range(2) == 0, True, # persistent call ), ( rand_address(), rand_bytes(100), - rand_range(2), - rand_range(2), + rand_range(2) == 0, + rand_range(2) == 0, False, # reverted call ), ] @pytest.mark.parametrize("address, code, exists, is_warm, is_persistent", TESTING_DATA) -def test_extcodesize(address: U160, code: bytes, exists: int, is_warm: bool, is_persistent: bool): +def test_extcodesize(address: U160, code: bytes, exists: bool, is_warm: bool, is_persistent: bool): randomness = rand_fq() code_hash = int.from_bytes(keccak256(code), "big") - code_size = len(code) if exists == 1 else 0 + code_size = len(code) if exists else 0 tx_id = 1 call_id = 1 @@ -78,12 +78,9 @@ def test_extcodesize(address: U160, code: bytes, exists: int, is_warm: bool, is_ rw_counter_of_reversion=rw_counter_end_of_reversion - reversible_write_counter, ) ) - if exists == 1: - rw_dictionary.account_read(address, AccountFieldTag.CodeHash, RLC(code_hash, randomness)) - else: - rw_dictionary.account_read( - address, AccountFieldTag.NonExisting, RLC(1 - exists, randomness) - ) + rw_dictionary.account_read( + address, AccountFieldTag.CodeHash, RLC(code_hash if exists else 0, randomness) + ) rw_table = set(rw_dictionary.stack_write(call_id, 1023, RLC(code_size, randomness)).rws)