From 887278c41fa5f8694e3b95164241077e32ec873e Mon Sep 17 00:00:00 2001 From: DreamWuGit Date: Fri, 8 Sep 2023 16:53:15 +0800 Subject: [PATCH] implement ErrorOutOfGasCREATE error gadget (#1562) ### Description implement ErrorOutOfGasCREATE error gadget, relies on PR #1558, only can be ready after that pr merged. ### Issue Link closed [#1522](https://github.com/privacy-scaling-explorations/zkevm-circuits/issues/1522) ### Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update This PR contains: - oog create/create2 both use OOG::Create - circuit for oog create - tests for create/tx deploy cases --------- Co-authored-by: Zhang Zhuo --- bus-mapping/src/error.rs | 10 +- bus-mapping/src/evm/opcodes.rs | 12 +- eth-types/src/evm_types.rs | 33 ++ zkevm-circuits/src/evm_circuit/execution.rs | 10 +- .../evm_circuit/execution/error_oog_create.rs | 379 ++++++++++++++++++ zkevm-circuits/src/evm_circuit/step.rs | 6 +- 6 files changed, 435 insertions(+), 15 deletions(-) create mode 100644 zkevm-circuits/src/evm_circuit/execution/error_oog_create.rs diff --git a/bus-mapping/src/error.rs b/bus-mapping/src/error.rs index 8eb668b2fb..4bec3d713b 100644 --- a/bus-mapping/src/error.rs +++ b/bus-mapping/src/error.rs @@ -92,8 +92,8 @@ pub enum OogError { SloadSstore, /// Out of Gas for CALL, CALLCODE, DELEGATECALL and STATICCALL Call, - /// Out of Gas for CREATE2 - Create2, + /// Out of Gas for CREATE and CREATE2 + Create, /// Out of Gas for SELFDESTRUCT SelfDestruct, } @@ -105,9 +105,7 @@ impl From<&OpcodeId> for OogError { OpcodeId::MLOAD | OpcodeId::MSTORE | OpcodeId::MSTORE8 => { OogError::StaticMemoryExpansion } - OpcodeId::CREATE | OpcodeId::RETURN | OpcodeId::REVERT => { - OogError::DynamicMemoryExpansion - } + OpcodeId::RETURN | OpcodeId::REVERT => OogError::DynamicMemoryExpansion, OpcodeId::CALLDATACOPY | OpcodeId::CODECOPY | OpcodeId::EXTCODECOPY @@ -124,7 +122,7 @@ impl From<&OpcodeId> for OogError { OogError::Call } OpcodeId::SLOAD | OpcodeId::SSTORE => OogError::SloadSstore, - OpcodeId::CREATE2 => OogError::Create2, + OpcodeId::CREATE | OpcodeId::CREATE2 => OogError::Create, OpcodeId::SELFDESTRUCT => OogError::SelfDestruct, _ => OogError::Constant, } diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index 5a1e477bc5..3e0a49941c 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -270,12 +270,20 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { } } -fn fn_gen_error_state_associated_ops(error: &ExecError) -> Option { +fn fn_gen_error_state_associated_ops( + geth_step: &GethExecStep, + error: &ExecError, +) -> Option { match error { ExecError::InvalidJump => Some(InvalidJump::gen_associated_ops), ExecError::InvalidOpcode => Some(ErrorSimple::gen_associated_ops), ExecError::OutOfGas(OogError::Call) => Some(OOGCall::gen_associated_ops), ExecError::OutOfGas(OogError::Constant) => Some(ErrorSimple::gen_associated_ops), + ExecError::OutOfGas(OogError::Create) => match geth_step.op { + OpcodeId::CREATE => Some(StackOnlyOpcode::<3, 0, true>::gen_associated_ops), + OpcodeId::CREATE2 => Some(StackOnlyOpcode::<4, 0, true>::gen_associated_ops), + op => unreachable!("OOG Create cannot occur in {op}"), + }, 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), @@ -370,7 +378,7 @@ pub fn gen_associated_ops( // TODO: after more error state handled, refactor all error handling in // fn_gen_error_state_associated_ops method // For exceptions that have been implemented - if let Some(fn_gen_error_ops) = fn_gen_error_state_associated_ops(&exec_error) { + if let Some(fn_gen_error_ops) = fn_gen_error_state_associated_ops(geth_step, &exec_error) { return fn_gen_error_ops(state, geth_steps); } else { // For exceptions that fail to enter next call context, we need diff --git a/eth-types/src/evm_types.rs b/eth-types/src/evm_types.rs index f59c186824..44b1befedf 100644 --- a/eth-types/src/evm_types.rs +++ b/eth-types/src/evm_types.rs @@ -28,6 +28,39 @@ pub const GAS_STIPEND_CALL_WITH_VALUE: u64 = 2300; /// pub const MAX_EXPANDED_MEMORY_ADDRESS: u64 = 0x1FFFFFFFE0; +#[cfg(feature = "shanghai")] +mod gas_create { + // For EIP-3860, there are 2 special gas cost constraints in geth + // [gasCreate2Eip3860](https://github.com/ethereum/go-ethereum/blob/eb83e7c54021573eaceb14236af3a7a8c64f6027/core/vm/gas_table.go#L321) + // (similar for CREATE). + // 1. size <= 49152 (MaxInitCodeSize) + // 2. gasCost = memoryGasCost + (2 + 6) * ((size + 31) / 32) should not + // overflow for Uint64. + // No need to constrain the second condition, since the maximum gas cost + // cannot overflow for Uint64 (36028809887100925 calculated by + // `memorySize = 0x1FFFFFFFE0` and `size = 49152`) if the first condition is + // satisfied. + + /// Maximum init code size to permit in a creation transaction and create instructions. + pub const MAX_INIT_CODE_SIZE: u64 = 2 * super::MAX_CODE_SIZE; + /// Once per word of the init code when creating a contract. + pub const INIT_CODE_WORD_GAS: u64 = 2; + /// Gas per code word for CREATE. + pub const CREATE_GAS_PER_CODE_WORD: u64 = INIT_CODE_WORD_GAS; + /// Gas per code word for CREATE2. + pub const CREATE2_GAS_PER_CODE_WORD: u64 = INIT_CODE_WORD_GAS + super::GasCost::COPY_SHA3.0; +} +#[cfg(not(feature = "shanghai"))] +mod gas_create { + /// Maximum init code size (0x1FFFFFFFE0) if not EIP-3860. + pub use super::MAX_EXPANDED_MEMORY_ADDRESS as MAX_INIT_CODE_SIZE; + /// Gas per code word for CREATE if not EIP-3860. + pub const CREATE_GAS_PER_CODE_WORD: u64 = 0; + /// Gas per code word for CREATE2 if not EIP-3860. + pub const CREATE2_GAS_PER_CODE_WORD: u64 = super::GasCost::COPY_SHA3; +} +pub use gas_create::*; + /// Defines the gas consumption. pub struct GasCost; diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 89e13690e4..4261c76b9a 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -74,6 +74,7 @@ mod error_invalid_opcode; mod error_oog_account_access; mod error_oog_call; mod error_oog_constant; +mod error_oog_create; mod error_oog_dynamic_memory; mod error_oog_exp; mod error_oog_log; @@ -151,6 +152,7 @@ use error_invalid_opcode::ErrorInvalidOpcodeGadget; use error_oog_account_access::ErrorOOGAccountAccessGadget; use error_oog_call::ErrorOOGCallGadget; use error_oog_constant::ErrorOOGConstantGadget; +use error_oog_create::ErrorOOGCreateGadget; use error_oog_dynamic_memory::ErrorOOGDynamicMemoryGadget; use error_oog_exp::ErrorOOGExpGadget; use error_oog_log::ErrorOOGLogGadget; @@ -311,7 +313,7 @@ pub struct ExecutionConfig { error_oog_sha3: Box>, error_oog_account_access: Box>, error_oog_ext_codecopy: Box>, - error_oog_create2: Box>, + error_oog_create: Box>, error_oog_self_destruct: Box>, error_oog_code_store: Box>, @@ -576,7 +578,7 @@ impl ExecutionConfig { error_oog_sha3: configure_gadget!(), error_oog_ext_codecopy: configure_gadget!(), error_oog_exp: configure_gadget!(), - error_oog_create2: configure_gadget!(), + error_oog_create: configure_gadget!(), error_oog_self_destruct: configure_gadget!(), error_oog_code_store: configure_gadget!(), error_invalid_jump: configure_gadget!(), @@ -1316,8 +1318,8 @@ impl ExecutionConfig { ExecutionState::ErrorOutOfGasEXP => { assign_exec_step!(self.error_oog_exp) } - ExecutionState::ErrorOutOfGasCREATE2 => { - assign_exec_step!(self.error_oog_create2) + ExecutionState::ErrorOutOfGasCREATE => { + assign_exec_step!(self.error_oog_create) } ExecutionState::ErrorOutOfGasSELFDESTRUCT => { assign_exec_step!(self.error_oog_self_destruct) diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_create.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_create.rs new file mode 100644 index 0000000000..b22619ae01 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_create.rs @@ -0,0 +1,379 @@ +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + param::{N_BYTES_GAS, N_BYTES_MEMORY_ADDRESS, N_BYTES_MEMORY_WORD_SIZE}, + step::ExecutionState, + util::{ + common_gadget::CommonErrorGadget, + constraint_builder::{ConstrainBuilderCommon, EVMConstraintBuilder}, + math_gadget::{LtGadget, PairSelectGadget}, + memory_gadget::{ + CommonMemoryAddressGadget, MemoryExpandedAddressGadget, MemoryExpansionGadget, + MemoryWordSizeGadget, + }, + or, select, CachedRegion, Cell, WordExpr, + }, + }, + util::{word::Word32Cell, Expr}, + witness::{Block, Call, ExecStep, Transaction}, +}; +use eth_types::{ + evm_types::{ + GasCost, OpcodeId, CREATE2_GAS_PER_CODE_WORD, CREATE_GAS_PER_CODE_WORD, MAX_INIT_CODE_SIZE, + }, + Field, U256, +}; +use halo2_proofs::{circuit::Value, plonk::Error}; + +#[derive(Clone, Debug)] +pub(crate) struct ErrorOOGCreateGadget { + opcode: Cell, + value: Word32Cell, + salt: Word32Cell, + is_create2: PairSelectGadget, + minimum_word_size: MemoryWordSizeGadget, + memory_address: MemoryExpandedAddressGadget, + memory_expansion: MemoryExpansionGadget, + // Init code size is overflow when it is greater than 49152 + // (maximum init code size) if Shanghai, otherwise when it is greater than + // 0x1FFFFFFFE0 (maximum value of offset + size). + // Uint64 overflow is checked in `memory_address` (offset + length). + init_code_size_overflow: LtGadget, + insufficient_gas: LtGadget, + common_error_gadget: CommonErrorGadget, +} + +impl ExecutionGadget for ErrorOOGCreateGadget { + const NAME: &'static str = "ErrorOutOfGasCREATE"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::ErrorOutOfGasCREATE; + + fn configure(cb: &mut EVMConstraintBuilder) -> Self { + let opcode = cb.query_cell(); + + let is_create2 = PairSelectGadget::construct( + cb, + opcode.expr(), + OpcodeId::CREATE2.expr(), + OpcodeId::CREATE.expr(), + ); + + let value = cb.query_word32(); + let salt = cb.query_word32(); + + let memory_address = MemoryExpandedAddressGadget::construct_self(cb); + + cb.stack_pop(value.to_word()); + cb.stack_pop(memory_address.offset_word()); + cb.stack_pop(memory_address.length_word()); + cb.condition(is_create2.expr().0, |cb| cb.stack_pop(salt.to_word())); + + let init_code_size_overflow = + LtGadget::construct(cb, MAX_INIT_CODE_SIZE.expr(), memory_address.length()); + + let minimum_word_size = MemoryWordSizeGadget::construct(cb, memory_address.length()); + let memory_expansion = MemoryExpansionGadget::construct(cb, [memory_address.address()]); + + let code_store_gas_cost = minimum_word_size.expr() + * select::expr( + is_create2.expr().0, + CREATE2_GAS_PER_CODE_WORD.expr(), + CREATE_GAS_PER_CODE_WORD.expr(), + ); + let gas_cost = GasCost::CREATE.expr() + memory_expansion.gas_cost() + code_store_gas_cost; + let insufficient_gas = LtGadget::construct(cb, cb.curr.state.gas_left.expr(), gas_cost); + + cb.require_equal( + "Memory address is overflow, init code size is overflow, or gas left is less than cost", + or::expr([ + memory_address.overflow(), + init_code_size_overflow.expr(), + insufficient_gas.expr(), + ]), + 1.expr(), + ); + + let common_error_gadget = CommonErrorGadget::construct( + cb, + opcode.expr(), + select::expr(is_create2.expr().0, 4.expr(), 3.expr()), + ); + + Self { + opcode, + value, + salt, + is_create2, + minimum_word_size, + memory_address, + memory_expansion, + init_code_size_overflow, + insufficient_gas, + common_error_gadget, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + _tx: &Transaction, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + log::debug!( + "ErrorOutOfGasCREATE: gas_cost = {}, gas_left = {}", + step.gas_cost, + step.gas_left, + ); + + let opcode = step.opcode().unwrap(); + let is_create2 = opcode == OpcodeId::CREATE2; + self.opcode + .assign(region, offset, Value::known(F::from(opcode.as_u64())))?; + self.is_create2.assign( + region, + offset, + F::from(opcode.as_u64()), + F::from(OpcodeId::CREATE2.as_u64()), + F::from(OpcodeId::CREATE.as_u64()), + )?; + + let [value, memory_offset, memory_length] = + [0, 1, 2].map(|i| block.get_rws(step, i).stack_value()); + let salt = if is_create2 { + block.get_rws(step, 3).stack_value() + } else { + U256::zero() + }; + + self.value.assign_u256(region, offset, value)?; + self.salt.assign_u256(region, offset, salt)?; + + let memory_address = + self.memory_address + .assign(region, offset, memory_offset, memory_length)?; + + let init_code_size = + MemoryExpandedAddressGadget::::length_value(memory_offset, memory_length); + let minimum_word_size = self + .minimum_word_size + .assign(region, offset, init_code_size)?; + let memory_expansion_gas = self + .memory_expansion + .assign(region, offset, step.memory_word_size(), [memory_address])? + .1; + + self.init_code_size_overflow.assign( + region, + offset, + F::from(MAX_INIT_CODE_SIZE), + F::from(init_code_size), + )?; + + let code_store_gas_cost = minimum_word_size + * if is_create2 { + CREATE2_GAS_PER_CODE_WORD + } else { + CREATE_GAS_PER_CODE_WORD + }; + self.insufficient_gas.assign( + region, + offset, + F::from(step.gas_left), + F::from(GasCost::CREATE + memory_expansion_gas + code_store_gas_cost), + )?; + + self.common_error_gadget.assign( + region, + offset, + block, + call, + step, + if is_create2 { 6 } else { 5 }, + )?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_util::CircuitTestBuilder; + use eth_types::{bytecode, word, Bytecode, ToWord}; + use mock::{ + eth, + test_ctx::{helpers::account_0_code_account_1_no_code, LoggerConfig}, + TestContext, MOCK_ACCOUNTS, MOCK_BLOCK_GAS_LIMIT, + }; + + struct TestCase { + bytecode: Bytecode, + gas: u64, + } + + impl TestCase { + pub fn new(is_create2: bool, offset: U256, size: U256, gas: u64) -> Self { + let mut bytecode = Bytecode::default(); + if is_create2 { + bytecode.append(&bytecode! {PUSH1(0)}); // salt; + } + bytecode.append(&bytecode! { + PUSH32(size) // size + PUSH32(offset) // offset + PUSH1(0) // value + }); + bytecode.write_op(if is_create2 { + OpcodeId::CREATE2 + } else { + OpcodeId::CREATE + }); + + Self { bytecode, gas } + } + } + + #[test] + fn test_oog_create_simple() { + let mut cases = vec![]; + for is_create2 in [true, false] { + cases.push(TestCase::new( + is_create2, + 0xffffffff_u64.into(), + 0xff.into(), + 0xffff, + )); + + cases.push(TestCase::new(is_create2, U256::zero(), 4.into(), 0x7d08)); + } + + for case in cases.iter() { + test_root(case); + test_internal(case); + } + } + + #[test] + fn test_oog_create_max_expanded_address() { + for is_create2 in [true, false] { + // 0xffffffff1 + 0xffffffff0 = 0x1fffffffe1 + // > MAX_EXPANDED_MEMORY_ADDRESS (0x1fffffffe0) + let case = TestCase::new( + is_create2, + 0xffffffff1_u64.into(), + 0xffffffff0_u64.into(), + MOCK_BLOCK_GAS_LIMIT, + ); + + test_root(&case); + test_internal(&case); + } + } + + #[test] + fn test_oog_create_max_u64_address() { + for is_create2 in [true, false] { + let case = TestCase::new( + is_create2, + u64::MAX.into(), + u64::MAX.into(), + MOCK_BLOCK_GAS_LIMIT, + ); + + test_root(&case); + test_internal(&case); + } + } + + #[test] + fn test_oog_create_max_word_address() { + for is_create2 in [true, false] { + let case = TestCase::new(is_create2, U256::MAX, U256::MAX, MOCK_BLOCK_GAS_LIMIT); + + test_root(&case); + test_internal(&case); + } + } + + #[test] + fn test_oog_create_max_init_code_size() { + for is_create2 in [true, false] { + // For Shanghai, MAX_INIT_CODE_SIZE is 49152, it is constrained by + // `init_code_size_overflow`. + // For not Shanghai, MAX_INIT_CODE_SIZE is 0x1FFFFFFFE0, it is + // constrained by `memory_address.overflow()` + // (and `init_code_size_overflow`). + let case = TestCase::new( + is_create2, + U256::zero(), + (MAX_INIT_CODE_SIZE + 1).into(), + MOCK_BLOCK_GAS_LIMIT, + ); + + test_root(&case); + test_internal(&case); + } + } + + fn test_root(case: &TestCase) { + let ctx = TestContext::<2, 1>::new_with_logger_config( + None, + account_0_code_account_1_no_code(case.bytecode.clone()), + |mut txs, accs| { + txs[0] + .from(accs[1].address) + .to(accs[0].address) + .gas(case.gas.into()); + }, + |block, _tx| block, + LoggerConfig { + enable_memory: true, + ..Default::default() + }, + ) + .unwrap(); + + CircuitTestBuilder::new_from_test_ctx(ctx).run(); + } + + fn test_internal(case: &TestCase) { + let code_a = bytecode! { + PUSH1(0x00) // retLength + PUSH1(0x00) // retOffset + PUSH32(0x00) // argsLength + PUSH32(0x00) // argsOffset + PUSH1(0x00) // value + PUSH32(MOCK_ACCOUNTS[1].to_word()) // addr + PUSH32(case.gas) // gas + CALL + STOP + }; + + let ctx = TestContext::<3, 1>::new_with_logger_config( + None, + |accs| { + accs[0].address(MOCK_ACCOUNTS[0]).code(code_a); + accs[1] + .address(MOCK_ACCOUNTS[1]) + .code(case.bytecode.clone()); + accs[2].address(MOCK_ACCOUNTS[2]).balance(eth(1)); + }, + |mut txs, accs| { + txs[0] + .from(accs[2].address) + .to(accs[0].address) + .gas(word!("0xFFFFF")); + }, + |block, _tx| block, + LoggerConfig { + enable_memory: true, + ..Default::default() + }, + ) + .unwrap(); + + CircuitTestBuilder::new_from_test_ctx(ctx).run(); + } +} diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index 14e297bab3..5f549e25e8 100644 --- a/zkevm-circuits/src/evm_circuit/step.rs +++ b/zkevm-circuits/src/evm_circuit/step.rs @@ -136,7 +136,7 @@ pub enum ExecutionState { ErrorOutOfGasEXTCODECOPY, ErrorOutOfGasCall, ErrorOutOfGasSloadSstore, - ErrorOutOfGasCREATE2, + ErrorOutOfGasCREATE, ErrorOutOfGasSELFDESTRUCT, } @@ -196,7 +196,7 @@ impl From<&ExecError> for ExecutionState { OogError::Sha3 => ExecutionState::ErrorOutOfGasSHA3, OogError::Call => ExecutionState::ErrorOutOfGasCall, OogError::SloadSstore => ExecutionState::ErrorOutOfGasSloadSstore, - OogError::Create2 => ExecutionState::ErrorOutOfGasCREATE2, + OogError::Create => ExecutionState::ErrorOutOfGasCREATE, OogError::SelfDestruct => ExecutionState::ErrorOutOfGasSELFDESTRUCT, }, } @@ -345,7 +345,7 @@ impl ExecutionState { | Self::ErrorOutOfGasEXTCODECOPY | Self::ErrorOutOfGasCall | Self::ErrorOutOfGasSloadSstore - | Self::ErrorOutOfGasCREATE2 + | Self::ErrorOutOfGasCREATE | Self::ErrorOutOfGasSELFDESTRUCT ) }