diff --git a/src/zkevm_specs/evm/execution/callop.py b/src/zkevm_specs/evm/execution/callop.py index 7569d5b91..964acd228 100644 --- a/src/zkevm_specs/evm/execution/callop.py +++ b/src/zkevm_specs/evm/execution/callop.py @@ -1,13 +1,8 @@ +from zkevm_specs.evm.util.call_gadget import CallGadget +from zkevm_specs.util.param import N_BYTES_GAS from ...util import ( - EMPTY_CODE_HASH, FQ, - GAS_COST_ACCOUNT_COLD_ACCESS, - GAS_COST_CALL_WITH_VALUE, - GAS_COST_NEW_ACCOUNT, - GAS_COST_WARM_ACCESS, GAS_STIPEND_CALL_WITH_VALUE, - N_BYTES_ACCOUNT_ADDRESS, - N_BYTES_GAS, RLC, ) from ..instruction import Instruction, Transition @@ -44,24 +39,7 @@ def callop(instruction: Instruction): # Verify depth is less than 1024 instruction.range_lookup(depth, 1024) - # Lookup values from stack - gas_rlc = instruction.stack_pop() - code_address_rlc = instruction.stack_pop() - # The third stack pop `value` is not present for both DELEGATECALL and - # STATICCALL opcodes. - value = instruction.stack_pop() if is_call + is_callcode == 1 else RLC(0) - cd_offset_rlc = instruction.stack_pop() - cd_length_rlc = instruction.stack_pop() - rd_offset_rlc = instruction.stack_pop() - rd_length_rlc = instruction.stack_pop() - is_success = instruction.stack_push() - - # Verify is_success is a bool - instruction.constrain_bool(is_success) - - # Recomposition of random linear combination to integer - code_address = instruction.rlc_to_fq(code_address_rlc, N_BYTES_ACCOUNT_ADDRESS) - + call = CallGadget(instruction, FQ(1), is_call, is_callcode, is_delegatecall) # For opcode CALLCODE: # - callee_address = caller_address # @@ -69,32 +47,27 @@ def callop(instruction: Instruction): # - callee_address = caller_address # - caller_address = parent_caller_address # - # Variable `code_address` will be used to get code hash. - callee_address = instruction.select(is_callcode + is_delegatecall, caller_address, code_address) + callee_address = instruction.select( + is_callcode + is_delegatecall, caller_address, call.callee_address + ) caller_address = instruction.select(is_delegatecall, parent_caller_address, caller_address) - gas = instruction.rlc_to_fq(gas_rlc, N_BYTES_GAS) - gas_is_u64 = instruction.is_zero(instruction.sum(gas_rlc.le_bytes[N_BYTES_GAS:])) - cd_offset, cd_length = instruction.memory_offset_and_length(cd_offset_rlc, cd_length_rlc) - rd_offset, rd_length = instruction.memory_offset_and_length(rd_offset_rlc, rd_length_rlc) - - # Verify memory expansion - next_memory_size, memory_expansion_gas_cost = instruction.memory_expansion_dynamic_length( - cd_offset, - cd_length, - rd_offset, - rd_length, + # Add `callee_address` to access list + is_warm_access = instruction.add_account_to_access_list( + tx_id, call.callee_address, reversion_info ) - # Add `code_address` to access list - is_warm_access = instruction.add_account_to_access_list(tx_id, code_address, reversion_info) + # Check not is_static if call has value + has_value = call.has_value + instruction.constrain_zero(has_value * is_static) # Propagate rw_counter_end_of_reversion and is_persistent callee_reversion_info = instruction.reversion_info(call_id=callee_call_id) instruction.constrain_equal( - callee_reversion_info.is_persistent, reversion_info.is_persistent * is_success.expr() + callee_reversion_info.is_persistent, + reversion_info.is_persistent * call.is_success, ) - is_reverted_by_caller = is_success.expr() == FQ(1) and reversion_info.is_persistent == FQ(0) + is_reverted_by_caller = call.is_success == FQ(1) and reversion_info.is_persistent == FQ(0) if is_reverted_by_caller: # Propagate rw_counter_end_of_reversion when callee succeeds but one of callers revert at some point. # Note that we subtract it with current caller's reversible_write_counter as callee's endpoint, where caller's @@ -104,16 +77,12 @@ def callop(instruction: Instruction): reversion_info.rw_counter_of_reversion(), ) - # Check not is_static if call has value - has_value = 1 - instruction.is_zero(value) - instruction.constrain_zero(has_value * is_static) - if is_call == 1: # For CALL opcode, verify transfer, and get caller balance before # transfer to constrain it should be greater than or equal to stack # `value`. (_, caller_balance), _ = instruction.transfer( - caller_address, callee_address, value, callee_reversion_info + caller_address, callee_address, call.value, callee_reversion_info ) elif is_callcode == 1: # For CALLCODE opcode, get caller balance to constrain it should be @@ -124,43 +93,24 @@ def callop(instruction: Instruction): # or equal to stack `value`. if is_call + is_callcode == 1: value_lt_caller_balance, value_eq_caller_balance = instruction.compare_word( - value, caller_balance + call.value, caller_balance ) instruction.constrain_zero(1 - value_lt_caller_balance - value_eq_caller_balance) - 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: - is_empty_code_hash = instruction.is_equal( - callee_code_hash, instruction.rlc_encode(EMPTY_CODE_HASH, 32) - ) - else: # callee_exists == 0 - is_empty_code_hash = FQ(1) - # Verify gas cost. - gas_cost = ( - instruction.select( - is_warm_access, FQ(GAS_COST_WARM_ACCESS), FQ(GAS_COST_ACCOUNT_COLD_ACCESS) - ) - + has_value - * ( - GAS_COST_CALL_WITH_VALUE - # Only CALL opcode could invoke transfer to make empty account into non-empty. - + is_call * (1 - callee_exists) * GAS_COST_NEW_ACCOUNT - ) - + memory_expansion_gas_cost + gas_cost = call.gas_cost( + instruction, + is_warm_access, + is_call, # Only CALL opcode could invoke transfer to make empty account into non-empty. ) - # Apply EIP 150. # Note that sufficient gas_left is checked implicitly by constant_divmod. gas_available = instruction.curr.gas_left - gas_cost one_64th_gas, _ = instruction.constant_divmod(gas_available, FQ(64), N_BYTES_GAS) all_but_one_64th_gas = gas_available - one_64th_gas callee_gas_left = instruction.select( - gas_is_u64, - instruction.min(all_but_one_64th_gas, gas, N_BYTES_GAS), + call.is_u64_gas, + instruction.min(all_but_one_64th_gas, call.gas, N_BYTES_GAS), all_but_one_64th_gas, ) @@ -171,9 +121,10 @@ def callop(instruction: Instruction): is_precompile, FQ(instruction.next.execution_state in precompile_execution_states()) ) - if is_empty_code_hash == FQ(1) and is_precompile == FQ(0): + no_callee_code = call.is_empty_code_hash + call.callee_not_exists + if no_callee_code == FQ(1) and is_precompile == FQ(0): # Make sure call is successful - instruction.constrain_equal(is_success, FQ(1)) + instruction.constrain_equal(call.is_success, FQ(1)) # Empty return_data for (field_tag, expected_value) in [ @@ -198,7 +149,7 @@ def callop(instruction: Instruction): program_counter=Transition.delta(1), stack_pointer=Transition.delta(stack_pointer_delta), gas_left=Transition.delta(has_value * GAS_STIPEND_CALL_WITH_VALUE - gas_cost), - memory_size=Transition.to(next_memory_size), + memory_size=Transition.to(call.next_memory_size), reversible_write_counter=Transition.delta(3), # Always stay same call_id=Transition.same(), @@ -219,7 +170,7 @@ def callop(instruction: Instruction): instruction.curr.stack_pointer + stack_pointer_delta, ), (CallContextFieldTag.GasLeft, instruction.curr.gas_left - gas_cost - callee_gas_left), - (CallContextFieldTag.MemorySize, next_memory_size), + (CallContextFieldTag.MemorySize, call.next_memory_size), ( CallContextFieldTag.ReversibleWriteCounter, instruction.curr.reversible_write_counter + 1, @@ -238,22 +189,22 @@ def callop(instruction: Instruction): (CallContextFieldTag.Depth, depth.expr() + 1), (CallContextFieldTag.CallerAddress, caller_address.expr()), (CallContextFieldTag.CalleeAddress, callee_address.expr()), - (CallContextFieldTag.CallDataOffset, cd_offset), - (CallContextFieldTag.CallDataLength, cd_length), - (CallContextFieldTag.ReturnDataOffset, rd_offset), - (CallContextFieldTag.ReturnDataLength, rd_length), + (CallContextFieldTag.CallDataOffset, call.cd_offset), + (CallContextFieldTag.CallDataLength, call.cd_length), + (CallContextFieldTag.ReturnDataOffset, call.rd_offset), + (CallContextFieldTag.ReturnDataLength, call.rd_length), ( CallContextFieldTag.Value, - instruction.select(is_delegatecall, parent_call_value.expr(), value.expr()), + instruction.select(is_delegatecall, parent_call_value.expr(), call.value.expr()), ), - (CallContextFieldTag.IsSuccess, is_success.expr()), + (CallContextFieldTag.IsSuccess, call.is_success), (CallContextFieldTag.IsStatic, is_static.expr()), (CallContextFieldTag.LastCalleeId, FQ(0)), (CallContextFieldTag.LastCalleeReturnDataOffset, FQ(0)), (CallContextFieldTag.LastCalleeReturnDataLength, FQ(0)), (CallContextFieldTag.IsRoot, FQ(False)), (CallContextFieldTag.IsCreate, FQ(False)), - (CallContextFieldTag.CodeHash, callee_code_hash.expr()), + (CallContextFieldTag.CodeHash, call.callee_code_hash), ]: instruction.constrain_equal( instruction.call_context_lookup(field_tag, call_id=callee_call_id), @@ -268,7 +219,7 @@ def callop(instruction: Instruction): call_id=Transition.to(callee_call_id), is_root=Transition.to(False), is_create=Transition.to(False), - code_hash=Transition.to(callee_code_hash), + code_hash=Transition.to(call.callee_code_hash), gas_left=Transition.to(callee_gas_left), reversible_write_counter=Transition.to(2), log_id=Transition.same(), diff --git a/src/zkevm_specs/evm/execution/oog_call.py b/src/zkevm_specs/evm/execution/oog_call.py index 57ffa26b7..9eb2d18d0 100644 --- a/src/zkevm_specs/evm/execution/oog_call.py +++ b/src/zkevm_specs/evm/execution/oog_call.py @@ -1,14 +1,7 @@ -from ...util import ( - FQ, - N_BYTES_ACCOUNT_ADDRESS, - EMPTY_CODE_HASH, - GAS_COST_WARM_ACCESS, - GAS_COST_ACCOUNT_COLD_ACCESS, - GAS_COST_CALL_WITH_VALUE, - GAS_COST_NEW_ACCOUNT, -) +from zkevm_specs.evm.util.call_gadget import CallGadget +from ...util import FQ from ..instruction import Instruction, Transition -from ..table import CallContextFieldTag, AccountFieldTag +from ..table import CallContextFieldTag from ..execution_state import ExecutionState from ...util import N_BYTES_GAS from ..opcode import Opcode @@ -21,56 +14,17 @@ def oog_call(instruction: Instruction): instruction.constrain_equal(opcode, Opcode.CALL) tx_id = instruction.call_context_lookup(CallContextFieldTag.TxId) - instruction.call_context_lookup(CallContextFieldTag.IsStatic) - # Lookup values from stack - instruction.stack_pop() - callee_address_rlc = instruction.stack_pop() - value = instruction.stack_pop() - cd_offset_rlc = instruction.stack_pop() - cd_length_rlc = instruction.stack_pop() - rd_offset_rlc = instruction.stack_pop() - rd_length_rlc = instruction.stack_pop() - is_success = instruction.stack_push() - instruction.constrain_zero(is_success) - cd_offset, cd_length = instruction.memory_offset_and_length(cd_offset_rlc, cd_length_rlc) - rd_offset, rd_length = instruction.memory_offset_and_length(rd_offset_rlc, rd_length_rlc) - - # Verify memory expansion - next_memory_size, memory_expansion_gas_cost = instruction.memory_expansion_dynamic_length( - cd_offset, - cd_length, - rd_offset, - rd_length, - ) - - has_value = 1 - instruction.is_zero(value) - callee_address = instruction.rlc_to_fq(callee_address_rlc, N_BYTES_ACCOUNT_ADDRESS) + # init CallGadget to handle stack vars. + call = CallGadget(instruction, FQ(0), FQ(1), FQ(0), FQ(0)) # TODO: handle PrecompiledContract oog cases # Add callee to access list - is_warm_access = instruction.read_account_to_access_list(tx_id, callee_address) - - # lookup balance of callee - callee_balance = instruction.account_read(callee_address, AccountFieldTag.Balance) - # Verify gas cost - callee_nonce = instruction.account_read(callee_address, AccountFieldTag.Nonce) - callee_code_hash = instruction.account_read(callee_address, AccountFieldTag.CodeHash) + is_warm_access = instruction.read_account_to_access_list(tx_id, call.callee_address) - is_empty_code_hash = instruction.is_equal( - callee_code_hash, instruction.rlc_encode(EMPTY_CODE_HASH, 32) - ) - is_account_empty = ( - instruction.is_zero(callee_nonce) * instruction.is_zero(callee_balance) * is_empty_code_hash - ) - gas_cost = ( - instruction.select( - is_warm_access, FQ(GAS_COST_WARM_ACCESS), FQ(GAS_COST_ACCOUNT_COLD_ACCESS) - ) - + has_value * (GAS_COST_CALL_WITH_VALUE + is_account_empty * GAS_COST_NEW_ACCOUNT) - + memory_expansion_gas_cost - ) + # verify gas cost + gas_cost = call.gas_cost(instruction, is_warm_access) # verify gas is insufficient gas_not_enough, _ = instruction.compare(instruction.curr.gas_left, gas_cost, N_BYTES_GAS) @@ -89,14 +43,14 @@ def oog_call(instruction: Instruction): if instruction.curr.is_root: # Do step state transition instruction.constrain_step_state_transition( - rw_counter=Transition.delta(15), + rw_counter=Transition.delta(12), call_id=Transition.same(), ) else: # when it is internal call, need to restore caller's state as finishing this call. # Restore caller state to next StepState instruction.step_state_transition_to_restored_context( - rw_counter_delta=15, + rw_counter_delta=12, return_data_offset=FQ(0), return_data_length=FQ(0), gas_left=instruction.curr.gas_left, diff --git a/src/zkevm_specs/evm/util/call_gadget.py b/src/zkevm_specs/evm/util/call_gadget.py new file mode 100644 index 000000000..831dbdb8a --- /dev/null +++ b/src/zkevm_specs/evm/util/call_gadget.py @@ -0,0 +1,114 @@ +from zkevm_specs.evm.table import AccountFieldTag +from zkevm_specs.util.arithmetic import RLC +from zkevm_specs.util.hash import EMPTY_CODE_HASH +from zkevm_specs.util.param import ( + GAS_COST_ACCOUNT_COLD_ACCESS, + GAS_COST_CALL_WITH_VALUE, + GAS_COST_NEW_ACCOUNT, + GAS_COST_WARM_ACCESS, +) +from ...util import ( + FQ, + N_BYTES_ACCOUNT_ADDRESS, + N_BYTES_GAS, + RLC, +) +from ..instruction import Instruction + + +class CallGadget: + IS_SUCCESS_CALL: FQ + + gas: FQ + callee_address: FQ + value: RLC + cd_offset: FQ + cd_length: FQ + rd_offset: FQ + rd_length: FQ + is_success: FQ + + is_u64_gas: FQ + next_memory_size: FQ + memory_expansion_gas_cost: FQ + + has_value: FQ + callee_code_hash: FQ + is_empty_code_hash: FQ + callee_not_exists: FQ + + def __init__( + self, + instruction: Instruction, + is_success_call: FQ, + is_call: FQ, + is_callcode: FQ, + is_delegatecall: FQ, + ): + self.IS_SUCCESS_CALL = is_success_call + + # Lookup values from stack + self.gas_rlc = instruction.stack_pop() + callee_address_rlc = instruction.stack_pop() + # For non-OOG case, + # the third stack pop `value` is not present for both DELEGATECALL and + # STATICCALL opcodes. + self.value = instruction.stack_pop() if is_call + is_callcode == FQ(1) else RLC(0) + cd_offset_rlc = instruction.stack_pop() + cd_length_rlc = instruction.stack_pop() + rd_offset_rlc = instruction.stack_pop() + rd_length_rlc = instruction.stack_pop() + self.is_success = instruction.stack_push().expr() + + if self.IS_SUCCESS_CALL == FQ(1): + # Verify is_success is a bool + instruction.constrain_bool(self.is_success) + self.gas = instruction.rlc_to_fq(self.gas_rlc, N_BYTES_GAS) + self.is_u64_gas = instruction.is_zero( + instruction.sum(self.gas_rlc.le_bytes[N_BYTES_GAS:]) + ) + else: + instruction.constrain_zero(self.is_success) + self.has_value = FQ(0) if is_delegatecall == FQ(1) else 1 - instruction.is_zero(self.value) + + self.callee_address = instruction.rlc_to_fq(callee_address_rlc, N_BYTES_ACCOUNT_ADDRESS) + self.cd_offset, self.cd_length = instruction.memory_offset_and_length( + cd_offset_rlc, cd_length_rlc + ) + self.rd_offset, self.rd_length = instruction.memory_offset_and_length( + rd_offset_rlc, rd_length_rlc + ) + # Verify memory expansion + ( + self.next_memory_size, + self.memory_expansion_gas_cost, + ) = instruction.memory_expansion_dynamic_length( + self.cd_offset, + self.cd_length, + self.rd_offset, + self.rd_length, + ) + + # Check callee account existence with code_hash != 0 + self.callee_code_hash = instruction.account_read( + self.callee_address, AccountFieldTag.CodeHash + ).expr() + self.is_empty_code_hash = instruction.is_equal( + self.callee_code_hash, instruction.rlc_encode(EMPTY_CODE_HASH, 32) + ) + self.callee_not_exists = instruction.is_zero(self.callee_code_hash) + + def gas_cost( + self, + instruction: Instruction, + is_warm_access: FQ, + is_call: FQ = FQ(1), + ) -> FQ: + return ( + instruction.select( + is_warm_access, FQ(GAS_COST_WARM_ACCESS), FQ(GAS_COST_ACCOUNT_COLD_ACCESS) + ) + + self.has_value + * (GAS_COST_CALL_WITH_VALUE + is_call * self.callee_not_exists * GAS_COST_NEW_ACCOUNT) + + self.memory_expansion_gas_cost + ) diff --git a/tests/evm/test_callop.py b/tests/evm/test_callop.py index 04f6705b3..98032e3a8 100644 --- a/tests/evm/test_callop.py +++ b/tests/evm/test_callop.py @@ -192,8 +192,6 @@ def test_callop( is_delegatecall = 1 if opcode == Opcode.DELEGATECALL else 0 is_staticcall = 1 if opcode == Opcode.STATICCALL else 0 - 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. value = stack.value if is_call + is_callcode == 1 else 0 @@ -310,12 +308,14 @@ def test_callop( rw_dictionary \ .stack_read(1, 1018, RLC(stack.gas, randomness)) \ .stack_read(1, 1019, RLC(callee.address, randomness)) + rw_dictionary \ .stack_read(1, 1020, RLC(stack.cd_offset, randomness)) \ .stack_read(1, 1021, RLC(stack.cd_length, randomness)) \ .stack_read(1, 1022, RLC(stack.rd_offset, randomness)) \ .stack_read(1, 1023, RLC(stack.rd_length, randomness)) \ .stack_write(1, 1023, RLC(is_success, randomness)) \ + .account_read(callee.address, AccountFieldTag.CodeHash, callee_bytecode_hash) \ .tx_access_list_account_write(1, callee.address, True, is_warm_access, rw_counter_of_reversion=None if caller_ctx.is_persistent else caller_ctx.rw_counter_end_of_reversion - caller_ctx.reversible_write_counter) \ .call_context_read(call_id, CallContextFieldTag.RwCounterEndOfReversion, callee_rw_counter_end_of_reversion) \ .call_context_read(call_id, CallContextFieldTag.IsPersistent, callee_is_persistent) @@ -327,9 +327,6 @@ def test_callop( # For opcode DELEGATECALL: # - callee = caller # - caller = parent_caller - # - # Variable `code_address` will be used for further code hash read. - code_address = callee.address if is_callcode == 1: callee = caller elif is_delegatecall == 1: @@ -352,8 +349,6 @@ def test_callop( rw_dictionary \ .account_read(caller.address, AccountFieldTag.Balance, RLC(caller.balance, randomness)) - rw_dictionary.account_read(code_address, AccountFieldTag.CodeHash, callee_bytecode_hash) - if is_empty_code_hash: rw_dictionary \ .call_context_write(1, CallContextFieldTag.LastCalleeId, 0) \ diff --git a/tests/evm/test_oog_call.py b/tests/evm/test_oog_call.py index 11ca6eb9d..063a0295e 100644 --- a/tests/evm/test_oog_call.py +++ b/tests/evm/test_oog_call.py @@ -87,8 +87,6 @@ def test_root_call( is_warm_access: bool, ): randomness = rand_fq() - - callee_balance = RLC(callee.balance + stack.value, randomness) caller_bytecode = ( Bytecode() .call( @@ -111,7 +109,6 @@ def test_root_call( rw_dictionary = ( RWDictionary(24) .call_context_read(1, CallContextFieldTag.TxId, 1) - .call_context_read(1, CallContextFieldTag.IsStatic, False) .stack_read(1, 1017, RLC(stack.gas, randomness)) .stack_read(1, 1018, RLC(callee.address, randomness)) .stack_read(1, 1019, RLC(stack.value, randomness)) @@ -120,10 +117,8 @@ def test_root_call( .stack_read(1, 1022, RLC(stack.rd_offset, randomness)) .stack_read(1, 1023, RLC(stack.rd_length, randomness)) .stack_write(1, 1023, RLC(is_success, randomness)) - .tx_access_list_account_read(1, callee.address, is_warm_access) - .account_read(callee.address, AccountFieldTag.Balance, callee_balance) - .account_read(callee.address, AccountFieldTag.Nonce, RLC(callee.nonce, randomness)) .account_read(callee.address, AccountFieldTag.CodeHash, callee_bytecode_hash) + .tx_access_list_account_read(1, callee.address, is_warm_access) .call_context_read(1, CallContextFieldTag.IsSuccess, 0) ) # fmt: on @@ -196,10 +191,36 @@ def test_oog_call_not_root(caller_ctx: CallerContext, callee: Account): stack = Stack(gas=100, cd_offset=64, cd_length=320, rd_offset=0, rd_length=32) is_warm_access = False - caller_rw_counter_end_of_reversion = 2 - - callee_balance = 200 - callee_balance = RLC(callee_balance + stack.value, randomness) + rw_dictionary = ( + RWDictionary(24) + .call_context_read(2, CallContextFieldTag.TxId, 1) + .stack_read(2, 1017, RLC(stack.gas, randomness)) + .stack_read(2, 1018, RLC(callee.address, randomness)) + .stack_read(2, 1019, RLC(stack.value, randomness)) + .stack_read(2, 1020, RLC(stack.cd_offset, randomness)) + .stack_read(2, 1021, RLC(stack.cd_length, randomness)) + .stack_read(2, 1022, RLC(stack.rd_offset, randomness)) + .stack_read(2, 1023, RLC(stack.rd_length, randomness)) + .stack_write(2, 1023, RLC(False, randomness)) + .account_read(callee.address, AccountFieldTag.CodeHash, callee_bytecode_hash) + .tx_access_list_account_read(1, callee.address, is_warm_access) + .call_context_read(2, CallContextFieldTag.IsSuccess, 0) + # restore context operations + .call_context_read(2, CallContextFieldTag.CallerId, 1) + .call_context_read(1, CallContextFieldTag.IsRoot, caller_ctx.is_root) + .call_context_read(1, CallContextFieldTag.IsCreate, caller_ctx.is_create) + .call_context_read(1, CallContextFieldTag.CodeHash, caller_bytecode_hash) + .call_context_read(1, CallContextFieldTag.ProgramCounter, caller_ctx.program_counter) + .call_context_read(1, CallContextFieldTag.StackPointer, caller_ctx.stack_pointer) + .call_context_read(1, CallContextFieldTag.GasLeft, caller_ctx.gas_left) + .call_context_read(1, CallContextFieldTag.MemorySize, caller_ctx.memory_size) + .call_context_read( + 1, CallContextFieldTag.ReversibleWriteCounter, caller_ctx.reversible_write_counter + ) + .call_context_write(1, CallContextFieldTag.LastCalleeId, 2) + .call_context_write(1, CallContextFieldTag.LastCalleeReturnDataOffset, 0) + .call_context_write(1, CallContextFieldTag.LastCalleeReturnDataLength, 0) + ) tables = Tables( block_table=set(Block().table_assignments(randomness)), @@ -210,40 +231,7 @@ def test_oog_call_not_root(caller_ctx: CallerContext, callee: Account): callee.code.table_assignments(randomness), ) ), - rw_table=set( - # fmt: off - RWDictionary(24) - .call_context_read(2, CallContextFieldTag.TxId, 1) - .call_context_read(2, CallContextFieldTag.IsStatic, False) - .stack_read(2, 1017, RLC(stack.gas, randomness)) - .stack_read(2, 1018, RLC(callee.address, randomness)) - .stack_read(2, 1019, RLC(stack.value, randomness)) - .stack_read(2, 1020, RLC(stack.cd_offset, randomness)) - .stack_read(2, 1021, RLC(stack.cd_length, randomness)) - .stack_read(2, 1022, RLC(stack.rd_offset, randomness)) - .stack_read(2, 1023, RLC(stack.rd_length, randomness)) - .stack_write(2, 1023, RLC(False, randomness)) - .tx_access_list_account_read(1, callee.address, is_warm_access) - .account_read(callee.address, AccountFieldTag.Balance, callee_balance) - .account_read(callee.address, AccountFieldTag.Nonce, RLC(callee.nonce, randomness)) - .account_read(callee.address, AccountFieldTag.CodeHash, callee_bytecode_hash) - # restore context operations - .call_context_read(2, CallContextFieldTag.IsSuccess, 0) - .call_context_read(2, CallContextFieldTag.CallerId, 1) - .call_context_read(1, CallContextFieldTag.IsRoot, caller_ctx.is_root) - .call_context_read(1, CallContextFieldTag.IsCreate, caller_ctx.is_create) - .call_context_read(1, CallContextFieldTag.CodeHash, caller_bytecode_hash) - .call_context_read(1, CallContextFieldTag.ProgramCounter, caller_ctx.program_counter) - .call_context_read(1, CallContextFieldTag.StackPointer, caller_ctx.stack_pointer) - .call_context_read(1, CallContextFieldTag.GasLeft, caller_ctx.gas_left) - .call_context_read(1, CallContextFieldTag.MemorySize, caller_ctx.memory_size) - .call_context_read(1, CallContextFieldTag.ReversibleWriteCounter, caller_ctx.reversible_write_counter) - .call_context_write(1, CallContextFieldTag.LastCalleeId, 2) - .call_context_write(1, CallContextFieldTag.LastCalleeReturnDataOffset, 0) - .call_context_write(1, CallContextFieldTag.LastCalleeReturnDataLength, 0) - .rws - # fmt: on - ), + rw_table=set(rw_dictionary.rws), ) verify_steps( @@ -264,7 +252,7 @@ def test_oog_call_not_root(caller_ctx: CallerContext, callee: Account): ), StepState( execution_state=ExecutionState.STOP, - rw_counter=24 + 27, + rw_counter=rw_dictionary.rw_counter, call_id=1, is_root=caller_ctx.is_root, is_create=caller_ctx.is_create,