From e55ba06f1e5cba9602614788a026c59ac41e73a8 Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 20 Apr 2023 20:09:52 +0800 Subject: [PATCH] Implement circuit for `ErrorOutOfGasMemoryCopy` state (#1288) ### Description Spec Markdown PR https://github.com/privacy-scaling-explorations/zkevm-specs/pull/397 #### Summary 1. Merge OOG error `ExtCodeCopy` into `MemoryCopy`. 2. Implement bus-mapping and circuit for this OOG error of `CALLDATACOPY`, `CODECOPY`, `EXTCODECOPY` and `RETURNDATACOPY` opcodes. 3. OOG error executions of `CALLDATACOPY`, `CODECOPY` and `RETURNDATACOPY` are same. 4. `EXTCODECOPY` has extra `1` stack pop, and constant gas costs are different for cold (2600) and warm (100) accounts according to EIP-2929 (could reference [go-etherum gasExtCodeCopyEIP2929 function](https://github.com/ethereum/go-ethereum/blob/master/core/vm/operations_acl.go#L116)). 5. Fix `memory_copier_gas_cost` function to not calculate memory expansion for zero copy size in gas utils of `eth-types`. ### Issue Link Close https://github.com/privacy-scaling-explorations/zkevm-circuits/issues/1266 ### Type of change - [X] New feature (non-breaking change which adds functionality) ### How Has This Been Tested? Add new unit-test cases for this error state. --- bus-mapping/src/error.rs | 14 +- bus-mapping/src/evm/opcodes.rs | 3 + .../src/evm/opcodes/error_oog_memory_copy.rs | 73 ++++ eth-types/src/evm_types/gas_utils.rs | 10 +- zkevm-circuits/src/evm_circuit/execution.rs | 4 +- .../execution/error_oog_memory_copy.rs | 406 ++++++++++++++++++ zkevm-circuits/src/witness/step.rs | 1 - 7 files changed, 499 insertions(+), 12 deletions(-) create mode 100644 bus-mapping/src/evm/opcodes/error_oog_memory_copy.rs create mode 100644 zkevm-circuits/src/evm_circuit/execution/error_oog_memory_copy.rs diff --git a/bus-mapping/src/error.rs b/bus-mapping/src/error.rs index 8fe916ce33..0c9914c1e3 100644 --- a/bus-mapping/src/error.rs +++ b/bus-mapping/src/error.rs @@ -73,8 +73,8 @@ pub enum OogError { /// Out of Gas for CREATE, RETURN, REVERT, which have dynamic memory /// expansion gas cost DynamicMemoryExpansion, - /// Out of Gas for CALLDATACOPY, CODECOPY, RETURNDATACOPY, which copy a - /// specified chunk of memory + /// Out of Gas for CALLDATACOPY, CODECOPY, EXTCODECOPY, RETURNDATACOPY, + /// which copy a specified chunk of memory MemoryCopy, /// Out of Gas for BALANCE, EXTCODESIZE, EXTCODEHASH, which possibly touch /// an extra account @@ -88,8 +88,6 @@ pub enum OogError { Exp, /// Out of Gas for SHA3 Sha3, - /// Out of Gas for EXTCODECOPY - ExtCodeCopy, /// Out of Gas for SLOAD and SSTORE SloadSstore, /// Out of Gas for CALL, CALLCODE, DELEGATECALL and STATICCALL @@ -144,9 +142,10 @@ pub(crate) fn get_step_reported_error(op: &OpcodeId, error: &str) -> ExecError { OpcodeId::CREATE | OpcodeId::RETURN | OpcodeId::REVERT => { OogError::DynamicMemoryExpansion } - OpcodeId::CALLDATACOPY | OpcodeId::CODECOPY | OpcodeId::RETURNDATACOPY => { - OogError::MemoryCopy - } + OpcodeId::CALLDATACOPY + | OpcodeId::CODECOPY + | OpcodeId::EXTCODECOPY + | OpcodeId::RETURNDATACOPY => OogError::MemoryCopy, OpcodeId::BALANCE | OpcodeId::EXTCODESIZE | OpcodeId::EXTCODEHASH => { OogError::AccountAccess } @@ -155,7 +154,6 @@ pub(crate) fn get_step_reported_error(op: &OpcodeId, error: &str) -> ExecError { } OpcodeId::EXP => OogError::Exp, OpcodeId::SHA3 => OogError::Sha3, - OpcodeId::EXTCODECOPY => OogError::ExtCodeCopy, OpcodeId::CALL | OpcodeId::CALLCODE | OpcodeId::DELEGATECALL | OpcodeId::STATICCALL => { OogError::Call } diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index 8e558fb189..076a64ef0b 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -58,6 +58,7 @@ mod error_invalid_jump; mod error_oog_call; mod error_oog_exp; mod error_oog_log; +mod error_oog_memory_copy; mod error_oog_sload_sstore; mod error_return_data_outofbound; mod error_simple; @@ -83,6 +84,7 @@ use error_invalid_jump::InvalidJump; use error_oog_call::OOGCall; use error_oog_exp::OOGExp; use error_oog_log::ErrorOOGLog; +use error_oog_memory_copy::OOGMemoryCopy; use error_oog_sload_sstore::OOGSloadSstore; use error_return_data_outofbound::ErrorReturnDataOutOfBound; use error_simple::ErrorSimple; @@ -275,6 +277,7 @@ fn fn_gen_error_state_associated_ops(error: &ExecError) -> Option Some(ErrorSimple::gen_associated_ops), ExecError::OutOfGas(OogError::Exp) => Some(OOGExp::gen_associated_ops), ExecError::OutOfGas(OogError::Log) => Some(ErrorOOGLog::gen_associated_ops), + ExecError::OutOfGas(OogError::MemoryCopy) => Some(OOGMemoryCopy::gen_associated_ops), ExecError::OutOfGas(OogError::SloadSstore) => Some(OOGSloadSstore::gen_associated_ops), ExecError::StackOverflow => Some(ErrorSimple::gen_associated_ops), ExecError::StackUnderflow => Some(ErrorSimple::gen_associated_ops), diff --git a/bus-mapping/src/evm/opcodes/error_oog_memory_copy.rs b/bus-mapping/src/evm/opcodes/error_oog_memory_copy.rs new file mode 100644 index 0000000000..b334414231 --- /dev/null +++ b/bus-mapping/src/evm/opcodes/error_oog_memory_copy.rs @@ -0,0 +1,73 @@ +use crate::{ + circuit_input_builder::{CircuitInputStateRef, ExecStep}, + error::{ExecError, OogError}, + evm::Opcode, + operation::{CallContextField, TxAccessListAccountOp, RW}, + Error, +}; +use eth_types::{evm_types::OpcodeId, GethExecStep, ToAddress}; + +/// Placeholder structure used to implement [`Opcode`] trait over it +/// corresponding to the +/// [`OogError::MemoryCopy`](crate::error::OogError::MemoryCopy). +#[derive(Clone, Copy, Debug)] +pub(crate) struct OOGMemoryCopy; + +impl Opcode for OOGMemoryCopy { + fn gen_associated_ops( + state: &mut CircuitInputStateRef, + geth_steps: &[GethExecStep], + ) -> Result, Error> { + let geth_step = &geth_steps[0]; + debug_assert!([ + OpcodeId::CALLDATACOPY, + OpcodeId::CODECOPY, + OpcodeId::EXTCODECOPY, + OpcodeId::RETURNDATACOPY + ] + .contains(&geth_step.op)); + + let mut exec_step = state.new_step(geth_step)?; + exec_step.error = Some(ExecError::OutOfGas(OogError::MemoryCopy)); + + let is_extcodecopy = geth_step.op == OpcodeId::EXTCODECOPY; + + // According to EIP-2929, EXTCODECOPY constant gas cost is different for cold + // and warm accounts. + if is_extcodecopy { + state.call_context_read( + &mut exec_step, + state.call()?.call_id, + CallContextField::TxId, + state.tx_ctx.id().into(), + ); + + let external_address = geth_step.stack.last()?.to_address(); + let is_warm = state.sdb.check_account_in_access_list(&external_address); + state.push_op( + &mut exec_step, + RW::READ, + TxAccessListAccountOp { + tx_id: state.tx_ctx.id(), + address: external_address, + is_warm, + is_warm_prev: is_warm, + }, + ); + } + + // Each of CALLDATACOPY, CODECOPY and RETURNDATACOPY has 3 stack read values. + // But EXTCODECOPY has 4. It has an extra stack pop for external address. + let stack_read_num = if is_extcodecopy { 4 } else { 3 }; + for i in 0..stack_read_num { + state.stack_read( + &mut exec_step, + geth_step.stack.nth_last_filled(i), + geth_step.stack.nth_last(i)?, + )?; + } + + state.handle_return(&mut exec_step, geth_steps, true)?; + Ok(vec![exec_step]) + } +} diff --git a/eth-types/src/evm_types/gas_utils.rs b/eth-types/src/evm_types/gas_utils.rs index d1e032d669..8d34a37e20 100644 --- a/eth-types/src/evm_types/gas_utils.rs +++ b/eth-types/src/evm_types/gas_utils.rs @@ -24,8 +24,14 @@ pub fn memory_copier_gas_cost( num_copy_bytes: u64, ) -> u64 { let num_words = (num_copy_bytes + 31) / 32; - num_words * GasCost::COPY.as_u64() - + memory_expansion_gas_cost(curr_memory_word_size, next_memory_word_size) + num_words * GasCost::COPY.as_u64() + + // Note that opcodes with a byte size parameter of 0 will not trigger + // memory expansion, regardless of their offset parameters. + if num_words > 0 { + memory_expansion_gas_cost(curr_memory_word_size, next_memory_word_size) + } else { + 0 + } } /// Calculate EIP 150 gas passed to callee. diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 83ac420dcc..ff44ee7e5a 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -66,6 +66,7 @@ mod error_oog_call; mod error_oog_constant; mod error_oog_exp; mod error_oog_log; +mod error_oog_memory_copy; mod error_oog_sload_sstore; mod error_oog_static_memory; mod error_return_data_oo_bound; @@ -137,6 +138,7 @@ use error_oog_call::ErrorOOGCallGadget; use error_oog_constant::ErrorOOGConstantGadget; use error_oog_exp::ErrorOOGExpGadget; use error_oog_log::ErrorOOGLogGadget; +use error_oog_memory_copy::ErrorOOGMemoryCopyGadget; use error_oog_sload_sstore::ErrorOOGSloadSstoreGadget; use error_return_data_oo_bound::ErrorReturnDataOutOfBoundGadget; use error_stack::ErrorStackGadget; @@ -282,6 +284,7 @@ pub(crate) struct ExecutionConfig { error_oog_call: Box>, error_oog_constant: Box>, error_oog_exp: Box>, + error_oog_memory_copy: Box>, error_oog_sload_sstore: Box>, error_oog_static_memory_gadget: Box>, @@ -290,7 +293,6 @@ pub(crate) struct ExecutionConfig { error_oog_dynamic_memory_gadget: Box>, error_oog_log: Box>, - error_oog_memory_copy: Box>, error_oog_account_access: Box>, error_oog_sha3: Box>, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_memory_copy.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_memory_copy.rs new file mode 100644 index 0000000000..d4f56ba227 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_memory_copy.rs @@ -0,0 +1,406 @@ +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + param::{N_BYTES_ACCOUNT_ADDRESS, N_BYTES_GAS, N_BYTES_MEMORY_WORD_SIZE}, + step::ExecutionState, + util::{ + common_gadget::CommonErrorGadget, + constraint_builder::ConstraintBuilder, + from_bytes, + math_gadget::{IsZeroGadget, LtGadget}, + memory_gadget::{MemoryAddressGadget, MemoryCopierGasGadget, MemoryExpansionGadget}, + select, CachedRegion, Cell, Word, + }, + witness::{Block, Call, ExecStep, Transaction}, + }, + table::CallContextFieldTag, + util::Expr, +}; +use eth_types::{ + evm_types::{GasCost, OpcodeId}, + Field, ToLittleEndian, U256, +}; +use halo2_proofs::{circuit::Value, plonk::Error}; + +/// Gadget to implement the corresponding out of gas errors for +/// [`OpcodeId::CALLDATACOPY`], [`OpcodeId::CODECOPY`], +/// [`OpcodeId::EXTCODECOPY`] and [`OpcodeId::RETURNDATACOPY`]. +#[derive(Clone, Debug)] +pub(crate) struct ErrorOOGMemoryCopyGadget { + opcode: Cell, + /// Check if `EXTCODECOPY` external address is warm + is_warm: Cell, + tx_id: Cell, + /// Extra stack pop for `EXTCODECOPY` + external_address: Word, + /// Source offset + src_offset: Word, + /// Destination offset and size to copy + dst_memory_addr: MemoryAddressGadget, + memory_expansion: MemoryExpansionGadget, + memory_copier_gas: MemoryCopierGasGadget, + insufficient_gas: LtGadget, + is_extcodecopy: IsZeroGadget, + common_error_gadget: CommonErrorGadget, +} + +impl ExecutionGadget for ErrorOOGMemoryCopyGadget { + const NAME: &'static str = "ErrorOutOfGasMemoryCopy"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::ErrorOutOfGasMemoryCopy; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let opcode = cb.query_cell(); + cb.require_in_set( + "ErrorOutOfGasMemoryCopy opcode must be CALLDATACOPY, CODECOPY, EXTCODECOPY or RETURNDATACOPY", + opcode.expr(), + vec![ + OpcodeId::CALLDATACOPY.expr(), + OpcodeId::CODECOPY.expr(), + OpcodeId::EXTCODECOPY.expr(), + OpcodeId::RETURNDATACOPY.expr(), + ], + ); + + let dst_offset = cb.query_cell_phase2(); + let src_offset = cb.query_word_rlc(); + let copy_size = cb.query_word_rlc(); + let external_address = cb.query_word_rlc(); + let is_warm = cb.query_bool(); + let tx_id = cb.query_cell(); + + let is_extcodecopy = + IsZeroGadget::construct(cb, opcode.expr() - OpcodeId::EXTCODECOPY.expr()); + + cb.condition(is_extcodecopy.expr(), |cb| { + cb.call_context_lookup(false.expr(), None, CallContextFieldTag::TxId, tx_id.expr()); + + // Check if EXTCODECOPY external address is warm. + cb.account_access_list_read( + tx_id.expr(), + from_bytes::expr(&external_address.cells[..N_BYTES_ACCOUNT_ADDRESS]), + is_warm.expr(), + ); + + // EXTCODECOPY has an extra stack pop for external address. + cb.stack_pop(external_address.expr()); + }); + + cb.stack_pop(dst_offset.expr()); + cb.stack_pop(src_offset.expr()); + cb.stack_pop(copy_size.expr()); + + let dst_memory_addr = MemoryAddressGadget::construct(cb, dst_offset, copy_size); + let memory_expansion = MemoryExpansionGadget::construct(cb, [dst_memory_addr.address()]); + let memory_copier_gas = MemoryCopierGasGadget::construct( + cb, + dst_memory_addr.length(), + memory_expansion.gas_cost(), + ); + + let constant_gas_cost = select::expr( + is_extcodecopy.expr(), + // According to EIP-2929, EXTCODECOPY constant gas cost is different for cold and warm + // accounts. + select::expr( + is_warm.expr(), + GasCost::WARM_ACCESS.expr(), + GasCost::COLD_ACCOUNT_ACCESS.expr(), + ), + // Constant gas cost is same for CALLDATACOPY, CODECOPY and RETURNDATACOPY. + OpcodeId::CALLDATACOPY.constant_gas_cost().expr(), + ); + + let insufficient_gas = LtGadget::construct( + cb, + cb.curr.state.gas_left.expr(), + constant_gas_cost + memory_copier_gas.gas_cost(), + ); + + cb.require_equal( + "Gas left is less than gas cost", + insufficient_gas.expr(), + 1.expr(), + ); + + let common_error_gadget = CommonErrorGadget::construct( + cb, + opcode.expr(), + // EXTCODECOPY has extra 1 call context lookup (tx_id), 1 account access list + // read (is_warm), and 1 stack pop (external_address). + 5.expr() + 3.expr() * is_extcodecopy.expr(), + ); + + Self { + opcode, + is_warm, + tx_id, + external_address, + src_offset, + dst_memory_addr, + memory_expansion, + memory_copier_gas, + insufficient_gas, + is_extcodecopy, + common_error_gadget, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + transaction: &Transaction, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + let opcode = step.opcode.unwrap(); + let is_extcodecopy = opcode == OpcodeId::EXTCODECOPY; + + log::debug!( + "ErrorOutOfGasMemoryCopy: opcode = {}, gas_left = {}, gas_cost = {}", + opcode, + step.gas_left, + step.gas_cost, + ); + + let (is_warm, external_address) = if is_extcodecopy { + ( + block.rws[step.rw_indices[1]].tx_access_list_value_pair().0, + block.rws[step.rw_indices[2]].stack_value(), + ) + } else { + (false, U256::zero()) + }; + + let rw_offset = if is_extcodecopy { 3 } else { 0 }; + let [dst_offset, src_offset, copy_size] = [rw_offset, rw_offset + 1, rw_offset + 2] + .map(|idx| block.rws[step.rw_indices[idx]].stack_value()); + + self.opcode + .assign(region, offset, Value::known(F::from(opcode.as_u64())))?; + self.is_warm + .assign(region, offset, Value::known(F::from(u64::from(is_warm))))?; + self.tx_id + .assign(region, offset, Value::known(F::from(transaction.id as u64)))?; + self.external_address + .assign(region, offset, Some(external_address.to_le_bytes()))?; + self.src_offset + .assign(region, offset, Some(src_offset.to_le_bytes()))?; + let memory_addr = self + .dst_memory_addr + .assign(region, offset, dst_offset, copy_size)?; + let (_, memory_expansion_cost) = + self.memory_expansion + .assign(region, offset, step.memory_word_size(), [memory_addr])?; + let memory_copier_gas = self.memory_copier_gas.assign( + region, + offset, + copy_size.as_u64(), + memory_expansion_cost, + )?; + let constant_gas_cost = if is_extcodecopy { + if is_warm { + GasCost::WARM_ACCESS + } else { + GasCost::COLD_ACCOUNT_ACCESS + } + } else { + GasCost::FASTEST + }; + self.insufficient_gas.assign_value( + region, + offset, + Value::known(F::from(step.gas_left)), + Value::known(F::from(constant_gas_cost.0 + memory_copier_gas)), + )?; + self.is_extcodecopy.assign( + region, + offset, + F::from(opcode.as_u64()) - F::from(OpcodeId::EXTCODECOPY.as_u64()), + )?; + self.common_error_gadget.assign( + region, + offset, + block, + call, + step, + // EXTCODECOPY has extra 1 call context lookup (tx_id), 1 account access list + // read (is_warm), and 1 stack pop (external_address). + 5 + if is_extcodecopy { 3 } else { 0 }, + )?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + evm_circuit::test::{rand_bytes, rand_word}, + test_util::CircuitTestBuilder, + }; + use eth_types::{ + bytecode, evm_types::gas_utils::memory_copier_gas_cost, Bytecode, ToWord, U256, + }; + use itertools::Itertools; + use mock::{ + eth, test_ctx::helpers::account_0_code_account_1_no_code, TestContext, MOCK_ACCOUNTS, + }; + + const TESTING_COMMON_OPCODES: &[OpcodeId] = &[ + OpcodeId::CALLDATACOPY, + OpcodeId::CODECOPY, + OpcodeId::RETURNDATACOPY, + ]; + + const TESTING_DST_OFFSET_COPY_SIZE_PAIRS: &[(u64, u64)] = + &[(0x20, 0), (0x40, 20), (0x2000, 0x200)]; + + #[test] + fn test_oog_memory_copy_for_common_opcodes() { + for (opcode, (dst_offset, copy_size)) in TESTING_COMMON_OPCODES + .iter() + .cartesian_product(TESTING_DST_OFFSET_COPY_SIZE_PAIRS.iter()) + { + let testing_data = TestingData::new_for_common_opcode(*opcode, *dst_offset, *copy_size); + + test_root(&testing_data); + test_internal(&testing_data); + } + } + + #[test] + fn test_oog_memory_copy_for_extcodecopy() { + for (is_warm, (dst_offset, copy_size)) in [false, true] + .iter() + .cartesian_product(TESTING_DST_OFFSET_COPY_SIZE_PAIRS.iter()) + { + let testing_data = TestingData::new_for_extcodecopy(*is_warm, *dst_offset, *copy_size); + + test_root(&testing_data); + test_internal(&testing_data); + } + } + + struct TestingData { + bytecode: Bytecode, + gas_cost: u64, + } + + impl TestingData { + pub fn new_for_common_opcode(opcode: OpcodeId, dst_offset: u64, copy_size: u64) -> Self { + let bytecode = bytecode! { + PUSH32(copy_size) + PUSH32(rand_word()) + PUSH32(dst_offset) + .write_op(opcode) + }; + + let memory_word_size = (dst_offset + copy_size + 31) / 32; + + let gas_cost = OpcodeId::PUSH32.constant_gas_cost().0 * 3 + + opcode.constant_gas_cost().0 + + memory_copier_gas_cost(0, memory_word_size, copy_size); + + Self { bytecode, gas_cost } + } + + pub fn new_for_extcodecopy(is_warm: bool, dst_offset: u64, copy_size: u64) -> Self { + let external_address = MOCK_ACCOUNTS[4]; + + let mut bytecode = bytecode! { + PUSH32(copy_size) + PUSH32(U256::zero()) + PUSH32(dst_offset) + PUSH32(external_address.to_word()) + EXTCODECOPY + }; + + let memory_word_size = (dst_offset + copy_size + 31) / 32; + + let mut gas_cost = OpcodeId::PUSH32.constant_gas_cost().0 * 4 + + GasCost::COLD_ACCOUNT_ACCESS.0 + + memory_copier_gas_cost(0, memory_word_size, copy_size); + + if is_warm { + bytecode.append(&bytecode! { + PUSH32(copy_size) + PUSH32(rand_word()) + PUSH32(dst_offset) + PUSH32(external_address.to_word()) + EXTCODECOPY + }); + + gas_cost += OpcodeId::PUSH32.constant_gas_cost().0 * 4 + + GasCost::WARM_ACCESS.0 + + memory_copier_gas_cost(memory_word_size, memory_word_size, copy_size); + } + + Self { bytecode, gas_cost } + } + } + + fn test_root(testing_data: &TestingData) { + let ctx = TestContext::<2, 1>::new( + None, + account_0_code_account_1_no_code(testing_data.bytecode.clone()), + |mut txs, accs| { + // Decrease expected gas cost (by 1) to trigger out of gas error. + txs[0] + .from(accs[1].address) + .to(accs[0].address) + .gas((GasCost::TX.0 + testing_data.gas_cost - 1).into()); + }, + |block, _tx| block.number(0xcafe_u64), + ) + .unwrap(); + + CircuitTestBuilder::new_from_test_ctx(ctx).run(); + } + + fn test_internal(testing_data: &TestingData) { + let (addr_a, addr_b) = (MOCK_ACCOUNTS[0], MOCK_ACCOUNTS[1]); + + // code B gets called by code A, so the call is an internal call. + let code_b = testing_data.bytecode.clone(); + let gas_cost_b = testing_data.gas_cost; + + // Code A calls code B. + let code_a = bytecode! { + // populate memory in A's context. + PUSH8(U256::from_big_endian(&rand_bytes(8))) + PUSH1(0x00) // offset + MSTORE + // call ADDR_B. + PUSH1(0x00) // retLength + PUSH1(0x00) // retOffset + PUSH32(0x00) // argsLength + PUSH32(0x20) // argsOffset + PUSH1(0x00) // value + PUSH32(addr_b.to_word()) // addr + // Decrease expected gas cost (by 1) to trigger out of gas error. + PUSH32(gas_cost_b - 1) // gas + CALL + STOP + }; + + let ctx = TestContext::<3, 1>::new( + None, + |accs| { + accs[0].address(addr_b).code(code_b); + accs[1].address(addr_a).code(code_a); + accs[2].address(MOCK_ACCOUNTS[2]).balance(eth(10)); + }, + |mut txs, accs| { + txs[0].from(accs[2].address).to(accs[1].address); + }, + |block, _tx| block, + ) + .unwrap(); + + CircuitTestBuilder::new_from_test_ctx(ctx).run(); + } +} diff --git a/zkevm-circuits/src/witness/step.rs b/zkevm-circuits/src/witness/step.rs index bcc8e3bd37..b4ccc4f0e1 100644 --- a/zkevm-circuits/src/witness/step.rs +++ b/zkevm-circuits/src/witness/step.rs @@ -86,7 +86,6 @@ impl From<&ExecError> for ExecutionState { OogError::Log => ExecutionState::ErrorOutOfGasLOG, OogError::Exp => ExecutionState::ErrorOutOfGasEXP, OogError::Sha3 => ExecutionState::ErrorOutOfGasSHA3, - OogError::ExtCodeCopy => ExecutionState::ErrorOutOfGasEXTCODECOPY, OogError::Call => ExecutionState::ErrorOutOfGasCall, OogError::SloadSstore => ExecutionState::ErrorOutOfGasSloadSstore, OogError::Create2 => ExecutionState::ErrorOutOfGasCREATE2,