From f74796293b0bd36030e131079d9642edabc34efb Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Fri, 14 Apr 2023 14:21:41 +0100 Subject: [PATCH 1/4] feat: copy circuit maintains accumulator value and value RLC for memory to bytecode --- mock/src/test_ctx.rs | 2 +- zkevm-circuits/src/copy_circuit.rs | 123 +++++++++++------- .../evm_circuit/execution/return_revert.rs | 23 ++-- zkevm-circuits/src/evm_circuit/util.rs | 13 ++ zkevm-circuits/src/table.rs | 32 ++--- 5 files changed, 121 insertions(+), 72 deletions(-) diff --git a/mock/src/test_ctx.rs b/mock/src/test_ctx.rs index 5da3907901..67eb88b439 100644 --- a/mock/src/test_ctx.rs +++ b/mock/src/test_ctx.rs @@ -76,7 +76,7 @@ pub use external_tracer::LoggerConfig; /// // Now we can start generating the traces and items we need to inspect /// // the behaviour of the generated env. /// ``` -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TestContext { /// chain id pub chain_id: Word, diff --git a/zkevm-circuits/src/copy_circuit.rs b/zkevm-circuits/src/copy_circuit.rs index 57a83fded2..d7bc433088 100644 --- a/zkevm-circuits/src/copy_circuit.rs +++ b/zkevm-circuits/src/copy_circuit.rs @@ -47,6 +47,8 @@ pub struct CopyCircuitConfig { pub is_last: Column, /// The value copied in this copy step. pub value: Column, + /// Random linear combination accumulator value. + pub value_acc: Column, /// Whether the row is padding. pub is_pad: Column, /// In case of a bytecode tag, this denotes whether or not the copied byte @@ -105,6 +107,7 @@ impl SubCircuitConfig for CopyCircuitConfig { let q_step = meta.complex_selector(); let is_last = meta.advice_column(); let value = meta.advice_column_in(SecondPhase); + let value_acc = meta.advice_column_in(SecondPhase); let is_code = meta.advice_column(); let is_pad = meta.advice_column(); let is_first = copy_table.is_first; @@ -222,23 +225,48 @@ impl SubCircuitConfig for CopyCircuitConfig { rw_diff, ); }); - cb.condition( - and::expr([ - meta.query_advice(is_last, Rotation::cur()), - tag.value_equals(CopyDataType::RlcAcc, Rotation::cur())(meta), - ]), - |cb| { - cb.require_equal( - "value == rlc_acc at the last row for RlcAcc", - meta.query_advice(value, Rotation::cur()), - meta.query_advice(rlc_acc, Rotation::cur()), - ); - }, - ); cb.gate(meta.query_fixed(q_enable, Rotation::cur())) }); + meta.create_gate( + "Last Step (check value accumulator) Memory => Bytecode", + |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "value_acc == rlc_acc on the last row", + meta.query_advice(value_acc, Rotation::next()), + meta.query_advice(rlc_acc, Rotation::next()), + ); + + cb.gate(and::expr([ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_last, Rotation::next()), + and::expr([ + tag.value_equals(CopyDataType::Memory, Rotation::cur())(meta), + tag.value_equals(CopyDataType::Bytecode, Rotation::next())(meta), + ]), + ])) + }, + ); + + meta.create_gate("Last Step (check value accumulator) RlcAcc", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "value_acc == rlc_acc on the last row", + meta.query_advice(value_acc, Rotation::next()), + meta.query_advice(rlc_acc, Rotation::next()), + ); + + cb.gate(and::expr([ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_last, Rotation::next()), + tag.value_equals(CopyDataType::RlcAcc, Rotation::next())(meta), + ])) + }); + meta.create_gate("verify step (q_step == 1)", |meta| { let mut cb = BaseConstraintBuilder::default(); @@ -262,25 +290,30 @@ impl SubCircuitConfig for CopyCircuitConfig { ); }, ); + cb.require_equal( + "write value == read value", + meta.query_advice(value, Rotation::cur()), + meta.query_advice(value, Rotation::next()), + ); + cb.require_equal( + "value_acc is same for read-write rows", + meta.query_advice(value_acc, Rotation::cur()), + meta.query_advice(value_acc, Rotation::next()), + ); cb.condition( - not::expr(tag.value_equals(CopyDataType::RlcAcc, Rotation::next())( - meta, - )), + and::expr([ + not::expr(meta.query_advice(is_last, Rotation::next())), + not::expr(meta.query_advice(is_pad, Rotation::cur())), + ]), |cb| { cb.require_equal( - "write value == read value (if not rlc acc)", - meta.query_advice(value, Rotation::cur()), - meta.query_advice(value, Rotation::next()), + "value_acc(2) == value_acc(0) * r + value(2)", + meta.query_advice(value_acc, Rotation(2)), + meta.query_advice(value_acc, Rotation::cur()) * challenges.keccak_input() + + meta.query_advice(value, Rotation(2)), ); }, ); - cb.condition(meta.query_advice(is_first, Rotation::cur()), |cb| { - cb.require_equal( - "write value == read value (is_first == 1)", - meta.query_advice(value, Rotation::cur()), - meta.query_advice(value, Rotation::next()), - ); - }); cb.require_zero( "value == 0 when is_pad == 1 for read", and::expr([ @@ -298,25 +331,9 @@ impl SubCircuitConfig for CopyCircuitConfig { meta.query_advice(is_pad, Rotation::next()), ); - cb.gate(meta.query_selector(q_step)) - }); - - meta.create_gate("verify_step (q_step == 0)", |meta| { - let mut cb = BaseConstraintBuilder::default(); - - cb.require_equal( - "rows[2].value == rows[0].value * r + rows[1].value", - meta.query_advice(value, Rotation(2)), - meta.query_advice(value, Rotation::cur()) * challenges.keccak_input() - + meta.query_advice(value, Rotation::next()), - ); - cb.gate(and::expr([ meta.query_fixed(q_enable, Rotation::cur()), - not::expr(meta.query_selector(q_step)), - not::expr(meta.query_advice(is_last, Rotation::cur())), - tag.value_equals(CopyDataType::RlcAcc, Rotation::cur())(meta), - not::expr(meta.query_advice(is_pad, Rotation::cur())), + meta.query_selector(q_step), ])) }); @@ -402,6 +419,7 @@ impl SubCircuitConfig for CopyCircuitConfig { q_step, is_last, value, + value_acc, is_pad, is_code, q_enable, @@ -463,9 +481,15 @@ impl CopyCircuitConfig { )?; // is_last, value, is_pad, is_code - for (column, &(value, label)) in [self.is_last, self.value, self.is_pad, self.is_code] - .iter() - .zip_eq(circuit_row) + for (column, &(value, label)) in [ + self.is_last, + self.value, + self.value_acc, + self.is_pad, + self.is_code, + ] + .iter() + .zip_eq(circuit_row) { region.assign_advice( || format!("{} at row: {}", label, *offset), @@ -616,6 +640,13 @@ impl CopyCircuitConfig { *offset, || Value::known(F::zero()), )?; + // value_acc + region.assign_advice( + || format!("assign value_acc {}", *offset), + self.value_acc, + *offset, + || Value::known(F::zero()), + )?; // rlc_acc region.assign_advice( || format!("assign rlc_acc {}", *offset), diff --git a/zkevm-circuits/src/evm_circuit/execution/return_revert.rs b/zkevm-circuits/src/evm_circuit/execution/return_revert.rs index d417a3590f..8d118ed06f 100644 --- a/zkevm-circuits/src/evm_circuit/execution/return_revert.rs +++ b/zkevm-circuits/src/evm_circuit/execution/return_revert.rs @@ -27,6 +27,7 @@ pub(crate) struct ReturnRevertGadget { opcode: Cell, range: MemoryAddressGadget, + init_code_rlc: Cell, is_success: Cell, restore_context: RestoreContextGadget, @@ -94,11 +95,12 @@ impl ExecutionGadget for ReturnRevertGadget { let is_contract_deployment = is_create.clone() * is_success.expr() * not::expr(copy_rw_increase_is_zero.expr()); - let (caller_id, address, reversion_info, code_hash) = + let (caller_id, address, reversion_info, code_hash, init_code_rlc) = cb.condition(is_contract_deployment.clone(), |cb| { // We don't need to place any additional constraints on code_hash because the // copy circuit enforces that it is the hash of the bytes in the copy lookup. let code_hash = cb.query_cell_phase2(); + let init_code_rlc = cb.query_cell_phase2(); cb.copy_table_lookup( cb.curr.state.call_id.expr(), CopyDataType::Memory.expr(), @@ -108,7 +110,7 @@ impl ExecutionGadget for ReturnRevertGadget { range.address(), 0.expr(), range.length(), - 0.expr(), + init_code_rlc.expr(), copy_rw_increase.expr(), ); @@ -127,7 +129,7 @@ impl ExecutionGadget for ReturnRevertGadget { Some(&mut reversion_info), ); - (caller_id, address, reversion_info, code_hash) + (caller_id, address, reversion_info, code_hash, init_code_rlc) }); // Case B in the specs. @@ -218,6 +220,7 @@ impl ExecutionGadget for ReturnRevertGadget { Self { opcode, range, + init_code_rlc, is_success, copy_length, copy_rw_increase, @@ -276,6 +279,11 @@ impl ExecutionGadget for ReturnRevertGadget { let values: Vec<_> = (3..3 + length.as_usize()) .map(|i| block.rws[step.rw_indices[i]].memory_value()) .collect(); + self.init_code_rlc.assign( + region, + offset, + region.keccak_rlc(&values.iter().rev().cloned().collect::>()), + )?; let mut code_hash = CodeDB::hash(&values).to_fixed_bytes(); code_hash.reverse(); self.code_hash.assign( @@ -617,10 +625,8 @@ mod test { PUSH1(0) // dest offset RETURNDATACOPY }); - - let block: GethData = TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode.clone()) - .unwrap() - .into(); + let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode.clone()).unwrap(); + let block: GethData = test_ctx.clone().into(); // collect return opcode, retrieve next step, assure both contract create // successfully @@ -651,7 +657,6 @@ mod test { .iter() .for_each(|size| assert_eq!(size, &Word::zero())); - let text_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode).unwrap(); - CircuitTestBuilder::new_from_test_ctx(text_ctx).run(); + CircuitTestBuilder::new_from_test_ctx(test_ctx).run(); } } diff --git a/zkevm-circuits/src/evm_circuit/util.rs b/zkevm-circuits/src/evm_circuit/util.rs index 6749480939..4936bfc924 100644 --- a/zkevm-circuits/src/evm_circuit/util.rs +++ b/zkevm-circuits/src/evm_circuit/util.rs @@ -195,10 +195,23 @@ impl<'r, 'b, F: FieldExt> CachedRegion<'r, 'b, F> { .evm_word() .map(|r| rlc::value(&n.to_le_bytes(), r)) } + + pub fn keccak_rlc(&self, le_bytes: &[u8]) -> Value { + self.challenges + .keccak_input() + .map(|r| rlc::value(le_bytes, r)) + } + pub fn empty_code_hash_rlc(&self) -> Value { self.word_rlc(CodeDB::empty_code_hash().to_word()) } + pub fn code_hash(&self, n: U256) -> Value { + self.challenges + .evm_word() + .map(|r| rlc::value(&n.to_le_bytes(), r)) + } + /// Constrains a cell to have a constant value. /// /// Returns an error if the cell is in a column where equality has not been diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index c1eddcbb83..4dec7a36b6 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -1044,7 +1044,7 @@ pub struct CopyTable { } type CopyTableRow = [(Value, &'static str); 8]; -type CopyCircuitRow = [(Value, &'static str); 4]; +type CopyCircuitRow = [(Value, &'static str); 5]; impl CopyTable { /// Construct a new CopyTable @@ -1069,7 +1069,7 @@ impl CopyTable { ) -> Vec<(CopyDataType, CopyTableRow, CopyCircuitRow)> { let mut assignments = Vec::new(); // rlc_acc - let rlc_acc = if copy_event.dst_type == CopyDataType::RlcAcc { + let rlc_acc = { let values = copy_event .bytes .iter() @@ -1078,8 +1078,6 @@ impl CopyTable { challenges .keccak_input() .map(|keccak_input| rlc::value(values.iter().rev(), keccak_input)) - } else { - Value::known(F::zero()) }; let mut value_acc = Value::known(F::zero()); for (step_idx, (is_read_step, copy_step)) in copy_event @@ -1154,17 +1152,11 @@ impl CopyTable { // bytes_left let bytes_left = u64::try_from(copy_event.bytes.len() * 2 - step_idx).unwrap() / 2; // value - let value = if copy_event.dst_type == CopyDataType::RlcAcc { - if is_read_step { - Value::known(F::from(copy_step.value as u64)) - } else { - value_acc = value_acc * challenges.keccak_input() - + Value::known(F::from(copy_step.value as u64)); - value_acc - } - } else { - Value::known(F::from(copy_step.value as u64)) - }; + let value = Value::known(F::from(copy_step.value as u64)); + // value_acc + if is_read_step { + value_acc = value_acc * challenges.keccak_input() + value; + } // is_pad let is_pad = Value::known(F::from( is_read_step && copy_step_addr >= copy_event.src_addr_end, @@ -1184,7 +1176,14 @@ impl CopyTable { "src_addr_end", ), (Value::known(F::from(bytes_left)), "bytes_left"), - (rlc_acc, "rlc_acc"), + ( + match (copy_event.src_type, copy_event.dst_type) { + (CopyDataType::Memory, CopyDataType::Bytecode) => rlc_acc, + (_, CopyDataType::RlcAcc) => rlc_acc, + _ => Value::known(F::zero()), + }, + "rlc_acc", + ), ( Value::known(F::from(copy_event.rw_counter(step_idx))), "rw_counter", @@ -1197,6 +1196,7 @@ impl CopyTable { [ (is_last, "is_last"), (value, "value"), + (value_acc, "value_acc"), (is_pad, "is_pad"), (is_code, "is_code"), ], From 65f72e4ded5aa907da0a4da5b33f9f8bd6e91549 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Fri, 14 Apr 2023 14:47:49 +0100 Subject: [PATCH 2/4] feat: insuff balance and nonce overflow related errors --- .../circuit_input_builder/input_state_ref.rs | 50 ++++++++++++++++++- .../src/circuit_input_builder/tracer_tests.rs | 6 ++- bus-mapping/src/error.rs | 28 +++++++++-- bus-mapping/src/evm/opcodes.rs | 23 +++++++-- zkevm-circuits/src/witness/step.rs | 14 ++++-- 5 files changed, 108 insertions(+), 13 deletions(-) diff --git a/bus-mapping/src/circuit_input_builder/input_state_ref.rs b/bus-mapping/src/circuit_input_builder/input_state_ref.rs index 9258676eab..125d5937f6 100644 --- a/bus-mapping/src/circuit_input_builder/input_state_ref.rs +++ b/bus-mapping/src/circuit_input_builder/input_state_ref.rs @@ -6,7 +6,7 @@ use super::{ TransactionContext, }; use crate::{ - error::{get_step_reported_error, ExecError}, + error::{get_step_reported_error, ExecError, InsufficientBalanceError, NonceUintOverflowError}, exec_trace::OperationRef, operation::{ AccountField, AccountOp, CallContextField, CallContextOp, MemoryOp, Op, OpEnum, Operation, @@ -434,6 +434,24 @@ impl<'a> CircuitInputStateRef<'a> { Ok(()) } + /// Add address to access list for the current transaction. + pub fn tx_access_list_write( + &mut self, + step: &mut ExecStep, + address: Address, + ) -> Result<(), Error> { + let is_warm = self.sdb.check_account_in_access_list(&address); + self.push_op_reversible( + step, + TxAccessListAccountOp { + tx_id: self.tx_ctx.id(), + address, + is_warm: true, + is_warm_prev: is_warm, + }, + ) + } + /// Push a write type [`TxAccessListAccountOp`] into the /// [`OperationContainer`](crate::operation::OperationContainer) with the /// next [`RWCounter`](crate::operation::RWCounter), and then @@ -688,6 +706,18 @@ impl<'a> CircuitInputStateRef<'a> { )) } + pub(crate) fn reversion_info_read(&mut self, step: &mut ExecStep, call: &Call) { + for (field, value) in [ + ( + CallContextField::RwCounterEndOfReversion, + call.rw_counter_end_of_reversion.to_word(), + ), + (CallContextField::IsPersistent, call.is_persistent.to_word()), + ] { + self.call_context_read(step, call.call_id, field, value); + } + } + /// Check if address is a precompiled or not. pub fn is_precompiled(&self, address: &Address) -> bool { address.0[0..19] == [0u8; 19] && (1..=9).contains(&address.0[19]) @@ -1299,7 +1329,23 @@ impl<'a> CircuitInputStateRef<'a> { return Err(Error::AccountNotFound(sender)); } if account.balance < value { - return Ok(Some(ExecError::InsufficientBalance)); + return Ok(Some(ExecError::InsufficientBalance(match step.op { + OpcodeId::CALL | OpcodeId::CALLCODE => InsufficientBalanceError::Call, + OpcodeId::CREATE => InsufficientBalanceError::Create, + OpcodeId::CREATE2 => InsufficientBalanceError::Create2, + op => { + unreachable!("insufficient balance error unexpected for opcode: {:?}", op) + } + }))); + } + + // Nonce Uint overflow + if account.nonce >= u64::MAX.into() { + return Ok(Some(ExecError::NonceUintOverflow(match step.op { + OpcodeId::CREATE => NonceUintOverflowError::Create, + OpcodeId::CREATE2 => NonceUintOverflowError::Create2, + op => unreachable!("Nonce Uint overflow error unexpected for opcode: {:?}", op), + }))); } // Address collision diff --git a/bus-mapping/src/circuit_input_builder/tracer_tests.rs b/bus-mapping/src/circuit_input_builder/tracer_tests.rs index 39dc4fbeb0..d89453934f 100644 --- a/bus-mapping/src/circuit_input_builder/tracer_tests.rs +++ b/bus-mapping/src/circuit_input_builder/tracer_tests.rs @@ -1,7 +1,7 @@ use super::*; use crate::{ circuit_input_builder::access::gen_state_access_trace, - error::{ExecError, OogError}, + error::{ExecError, InsufficientBalanceError, OogError}, geth_errors::{ GETH_ERR_GAS_UINT_OVERFLOW, GETH_ERR_OUT_OF_GAS, GETH_ERR_STACK_OVERFLOW, GETH_ERR_STACK_UNDERFLOW, @@ -288,7 +288,9 @@ fn tracer_err_insufficient_balance() { let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( builder.state_ref().get_step_err(step, next_step).unwrap(), - Some(ExecError::InsufficientBalance) + Some(ExecError::InsufficientBalance( + InsufficientBalanceError::Call + )) ); } diff --git a/bus-mapping/src/error.rs b/bus-mapping/src/error.rs index 8fe916ce33..b35edfc1fc 100644 --- a/bus-mapping/src/error.rs +++ b/bus-mapping/src/error.rs @@ -100,6 +100,26 @@ pub enum OogError { SelfDestruct, } +/// Insufficient balance errors by opcode/state. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum InsufficientBalanceError { + /// Insufficient balance during CALL/CALLCODE opcode. + Call, + /// Insufficient balance during CREATE opcode. + Create, + /// Insufficient balance during CREATE2 opcode. + Create2, +} + +/// Nonce uint overflow errors by opcode/state. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum NonceUintOverflowError { + /// Nonce uint overflow during CREATE opcode. + Create, + /// Nonce uint overflow during CREATE2 opcode. + Create2, +} + /// EVM Execution Error #[derive(Clone, Debug, PartialEq, Eq)] pub enum ExecError { @@ -116,9 +136,9 @@ pub enum ExecError { WriteProtection, /// For CALL, CALLCODE, DELEGATECALL, STATICCALL Depth, - /// For CALL, CALLCODE - InsufficientBalance, - /// For CREATE, CREATE2 + /// For CALL, CALLCODE, CREATE, CREATE2 + InsufficientBalance(InsufficientBalanceError), + /// For CREATE2 ContractAddressCollision, /// contract must not begin with 0xef due to EIP #3541 EVM Object Format /// (EOF) @@ -131,6 +151,8 @@ pub enum ExecError { CodeStoreOutOfGas, /// For RETURN in a CREATE, CREATE2 MaxCodeSizeExceeded, + /// For CREATE, CREATE2 + NonceUintOverflow(NonceUintOverflowError), } // TODO: Move to impl block. diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index 8e558fb189..70e1327ffe 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -1,7 +1,7 @@ //! Definition of each opcode of the EVM. use crate::{ circuit_input_builder::{CircuitInputStateRef, ExecStep}, - error::{ExecError, OogError}, + error::{ExecError, InsufficientBalanceError, NonceUintOverflowError, OogError}, evm::OpcodeId, operation::{ AccountField, AccountOp, CallContextField, TxAccessListAccountOp, TxReceiptField, @@ -278,8 +278,25 @@ fn fn_gen_error_state_associated_ops(error: &ExecError) -> Option Some(OOGSloadSstore::gen_associated_ops), ExecError::StackOverflow => Some(ErrorSimple::gen_associated_ops), ExecError::StackUnderflow => Some(ErrorSimple::gen_associated_ops), - // call & callcode can encounter InsufficientBalance error, Use pop-7 generic CallOpcode - ExecError::InsufficientBalance => Some(CallOpcode::<7>::gen_associated_ops), + ExecError::InsufficientBalance(InsufficientBalanceError::Call) => { + Some(CallOpcode::<7>::gen_associated_ops) + } + // create & create2 can encounter insufficient balance. + ExecError::InsufficientBalance(InsufficientBalanceError::Create) => { + Some(DummyCreate::::gen_associated_ops) + } + ExecError::InsufficientBalance(InsufficientBalanceError::Create2) => { + Some(DummyCreate::::gen_associated_ops) + } + // only create2 may cause ContractAddressCollision error, so use DummyCreate::. + ExecError::ContractAddressCollision => Some(DummyCreate::::gen_associated_ops), + // create & create2 can encounter nonce uint overflow. + ExecError::NonceUintOverflow(NonceUintOverflowError::Create) => { + Some(DummyCreate::::gen_associated_ops) + } + ExecError::NonceUintOverflow(NonceUintOverflowError::Create2) => { + Some(DummyCreate::::gen_associated_ops) + } ExecError::WriteProtection => Some(ErrorWriteProtection::gen_associated_ops), ExecError::ReturnDataOutOfBounds => Some(ErrorReturnDataOutOfBound::gen_associated_ops), diff --git a/zkevm-circuits/src/witness/step.rs b/zkevm-circuits/src/witness/step.rs index bcc8e3bd37..afa66d34e1 100644 --- a/zkevm-circuits/src/witness/step.rs +++ b/zkevm-circuits/src/witness/step.rs @@ -1,6 +1,6 @@ use bus_mapping::{ circuit_input_builder, - error::{ExecError, OogError}, + error::{ExecError, InsufficientBalanceError, NonceUintOverflowError, OogError}, evm::OpcodeId, operation, }; @@ -65,8 +65,16 @@ impl From<&ExecError> for ExecutionState { ExecError::StackOverflow | ExecError::StackUnderflow => ExecutionState::ErrorStack, ExecError::WriteProtection => ExecutionState::ErrorWriteProtection, ExecError::Depth => ExecutionState::ErrorDepth, - ExecError::InsufficientBalance => ExecutionState::ErrorInsufficientBalance, - ExecError::ContractAddressCollision => ExecutionState::ErrorContractAddressCollision, + ExecError::InsufficientBalance(insuff_balance_err) => match insuff_balance_err { + InsufficientBalanceError::Call => ExecutionState::CALL_OP, + InsufficientBalanceError::Create => ExecutionState::CREATE, + InsufficientBalanceError::Create2 => ExecutionState::CREATE2, + }, + ExecError::ContractAddressCollision => ExecutionState::CREATE2, + ExecError::NonceUintOverflow(nonce_overflow_err) => match nonce_overflow_err { + NonceUintOverflowError::Create => ExecutionState::CREATE, + NonceUintOverflowError::Create2 => ExecutionState::CREATE2, + }, ExecError::InvalidCreationCode => ExecutionState::ErrorInvalidCreationCode, ExecError::InvalidJump => ExecutionState::ErrorInvalidJump, ExecError::ReturnDataOutOfBounds => ExecutionState::ErrorReturnDataOutOfBound, From 699163689ad1fe00246d3c2d4867a478bf841f36 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Fri, 14 Apr 2023 15:11:12 +0100 Subject: [PATCH 3/4] feat: bus-mapping additions to support CREATE/CREATE2 --- bus-mapping/src/circuit_input_builder.rs | 15 +- bus-mapping/src/circuit_input_builder/call.rs | 7 + bus-mapping/src/evm/opcodes.rs | 24 +- bus-mapping/src/evm/opcodes/create.rs | 415 ++++++++++++++---- bus-mapping/src/state_db.rs | 6 + 5 files changed, 355 insertions(+), 112 deletions(-) diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 91696dead0..7b5179caae 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -368,10 +368,17 @@ pub fn get_create_init_code<'a, 'b>( call_ctx: &'a CallContext, step: &'b GethExecStep, ) -> Result<&'a [u8], Error> { - let offset = step.stack.nth_last(1)?; - let length = step.stack.nth_last(2)?; - Ok(&call_ctx.memory.0 - [offset.low_u64() as usize..(offset.low_u64() + length.low_u64()) as usize]) + let offset = step.stack.nth_last(1)?.low_u64() as usize; + let length = step.stack.nth_last(2)?.as_usize(); + + let mem_len = call_ctx.memory.0.len(); + if offset >= mem_len { + return Ok(&[]); + } + + let offset_end = offset.checked_add(length).unwrap_or(mem_len); + + Ok(&call_ctx.memory.0[offset..offset_end]) } /// Retrieve the memory offset and length of call. diff --git a/bus-mapping/src/circuit_input_builder/call.rs b/bus-mapping/src/circuit_input_builder/call.rs index 8f4098134c..0a6119777e 100644 --- a/bus-mapping/src/circuit_input_builder/call.rs +++ b/bus-mapping/src/circuit_input_builder/call.rs @@ -128,6 +128,13 @@ pub struct CallContext { pub return_data: Vec, } +impl CallContext { + /// Memory size in words, rounded up + pub fn memory_word_size(&self) -> u64 { + u64::try_from(self.memory.len()).expect("failed to convert usize to u64") / 32 + } +} + /// A reversion group is the collection of calls and the operations which are /// [`Operation::reversible`](crate::operation::Operation::reversible) that /// happened in them, that will be reverted at once when the call that initiated diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index 70e1327ffe..81d4d45577 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -77,7 +77,7 @@ use callop::CallOpcode; use callvalue::Callvalue; use codecopy::Codecopy; use codesize::Codesize; -use create::DummyCreate; +use create::Create; use dup::Dup; use error_invalid_jump::InvalidJump; use error_oog_call::OOGCall; @@ -247,19 +247,13 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { OpcodeId::LOG4 => Log::gen_associated_ops, OpcodeId::CALL | OpcodeId::CALLCODE => CallOpcode::<7>::gen_associated_ops, OpcodeId::DELEGATECALL | OpcodeId::STATICCALL => CallOpcode::<6>::gen_associated_ops, + OpcodeId::CREATE => Create::::gen_associated_ops, + OpcodeId::CREATE2 => Create::::gen_associated_ops, OpcodeId::RETURN | OpcodeId::REVERT => ReturnRevert::gen_associated_ops, OpcodeId::SELFDESTRUCT => { evm_unimplemented!("Using dummy gen_selfdestruct_ops for opcode SELFDESTRUCT"); DummySelfDestruct::gen_associated_ops } - OpcodeId::CREATE => { - evm_unimplemented!("Using dummy gen_create_ops for opcode {:?}", opcode_id); - DummyCreate::::gen_associated_ops - } - OpcodeId::CREATE2 => { - evm_unimplemented!("Using dummy gen_create_ops for opcode {:?}", opcode_id); - DummyCreate::::gen_associated_ops - } _ => { evm_unimplemented!("Using dummy gen_associated_ops for opcode {:?}", opcode_id); Dummy::gen_associated_ops @@ -283,19 +277,19 @@ fn fn_gen_error_state_associated_ops(error: &ExecError) -> Option { - Some(DummyCreate::::gen_associated_ops) + Some(Create::::gen_associated_ops) } ExecError::InsufficientBalance(InsufficientBalanceError::Create2) => { - Some(DummyCreate::::gen_associated_ops) + Some(Create::::gen_associated_ops) } - // only create2 may cause ContractAddressCollision error, so use DummyCreate::. - ExecError::ContractAddressCollision => Some(DummyCreate::::gen_associated_ops), + // only create2 may cause ContractAddressCollision error, so use Create::. + ExecError::ContractAddressCollision => Some(Create::::gen_associated_ops), // create & create2 can encounter nonce uint overflow. ExecError::NonceUintOverflow(NonceUintOverflowError::Create) => { - Some(DummyCreate::::gen_associated_ops) + Some(Create::::gen_associated_ops) } ExecError::NonceUintOverflow(NonceUintOverflowError::Create2) => { - Some(DummyCreate::::gen_associated_ops) + Some(Create::::gen_associated_ops) } ExecError::WriteProtection => Some(ErrorWriteProtection::gen_associated_ops), ExecError::ReturnDataOutOfBounds => Some(ErrorReturnDataOutOfBound::gen_associated_ops), diff --git a/bus-mapping/src/evm/opcodes/create.rs b/bus-mapping/src/evm/opcodes/create.rs index d40fa678d3..a1f742babd 100644 --- a/bus-mapping/src/evm/opcodes/create.rs +++ b/bus-mapping/src/evm/opcodes/create.rs @@ -1,36 +1,40 @@ use crate::{ - circuit_input_builder::{CircuitInputStateRef, ExecStep}, - evm::Opcode, - operation::{AccountField, AccountOp, CallContextField, TxAccessListAccountOp}, + circuit_input_builder::{ + CircuitInputStateRef, CopyDataType, CopyEvent, ExecStep, NumberOrHash, + }, + error::ExecError, + evm::{Opcode, OpcodeId}, + operation::{AccountField, AccountOp, CallContextField, MemoryOp, RW}, state_db::CodeDB, Error, }; -use eth_types::{evm_types::gas_utils::memory_expansion_gas_cost, GethExecStep, ToWord, Word}; +use eth_types::{Bytecode, GethExecStep, ToBigEndian, ToWord, Word, H160, H256}; +use ethers_core::utils::{get_create2_address, keccak256, rlp}; #[derive(Debug, Copy, Clone)] -pub struct DummyCreate; +pub struct Create; -impl Opcode for DummyCreate { +impl Opcode for Create { fn gen_associated_ops( state: &mut CircuitInputStateRef, geth_steps: &[GethExecStep], ) -> Result, Error> { - // TODO: replace dummy create here let geth_step = &geth_steps[0]; + let mut exec_step = state.new_step(geth_step)?; - let offset = geth_step.stack.nth_last(1)?.as_usize(); + // Get low Uint64 of offset. + let offset = geth_step.stack.nth_last(1)?.low_u64() as usize; let length = geth_step.stack.nth_last(2)?.as_usize(); - let curr_memory_word_size = (state.call_ctx()?.memory.len() as u64) / 32; if length != 0 { state .call_ctx_mut()? .memory .extend_at_least(offset + length); } - let next_memory_word_size = (state.call_ctx()?.memory.len() as u64) / 32; + let next_memory_word_size = state.call_ctx()?.memory_word_size(); - let mut exec_step = state.new_step(geth_step)?; + let callee = state.parse_call(geth_step)?; let n_pop = if IS_CREATE2 { 4 } else { 3 }; for i in 0..n_pop { @@ -47,92 +51,149 @@ impl Opcode for DummyCreate { state.create_address()? }; - // TODO: rename this to initialization call? - let call = state.parse_call(geth_step)?; + let callee_account = &state.sdb.get_account(&address).1.clone(); + let callee_exists = !callee_account.is_empty(); + state.stack_write( &mut exec_step, geth_step.stack.nth_last_filled(n_pop - 1), - if call.is_success { + if callee.is_success { address.to_word() } else { Word::zero() }, )?; + let (initialization_code, code_hash) = if length > 0 { + handle_copy(state, &mut exec_step, state.call()?.call_id, offset, length)? + } else { + (vec![], CodeDB::empty_code_hash()) + }; + let tx_id = state.tx_ctx.id(); - let current_call = state.call()?.clone(); - - // Quote from [EIP-2929](https://eips.ethereum.org/EIPS/eip-2929) - // > When a CREATE or CREATE2 opcode is called, - // > immediately (i.e. before checks are done to determine - // > whether or not the address is unclaimed) - // > add the address being created to accessed_addresses, - // > but gas costs of CREATE and CREATE2 are unchanged - let is_warm = state.sdb.check_account_in_access_list(&address); - state.push_op_reversible( + let caller = state.call()?.clone(); + + state.call_context_read( &mut exec_step, - TxAccessListAccountOp { - tx_id: state.tx_ctx.id(), - address, - is_warm: true, - is_warm_prev: is_warm, - }, - )?; + caller.call_id, + CallContextField::TxId, + tx_id.to_word(), + ); + state.reversion_info_read(&mut exec_step, &caller); + state.tx_access_list_write(&mut exec_step, address)?; - // Increase caller's nonce - let nonce_prev = state.sdb.get_nonce(&call.caller_address); - state.push_op_reversible( + let depth = caller.depth; + state.call_context_read( &mut exec_step, - AccountOp { - address: call.caller_address, - field: AccountField::Nonce, - value: (nonce_prev + 1).into(), - value_prev: nonce_prev.into(), - }, - )?; + caller.call_id, + CallContextField::Depth, + depth.to_word(), + ); - // Add callee into access list - let is_warm = state.sdb.check_account_in_access_list(&call.address); - state.push_op_reversible( + state.call_context_read( &mut exec_step, - TxAccessListAccountOp { - tx_id, - address: call.address, - is_warm: true, - is_warm_prev: is_warm, - }, - )?; + caller.call_id, + CallContextField::CalleeAddress, + caller.address.to_word(), + ); - state.push_call(call.clone()); + let caller_balance = state.sdb.get_balance(&callee.caller_address); + state.account_read( + &mut exec_step, + callee.caller_address, + AccountField::Balance, + caller_balance, + ); - state.transfer( + let caller_nonce = state.sdb.get_nonce(&caller.address); + state.account_read( &mut exec_step, - call.caller_address, - call.address, - true, - true, - call.value, - )?; + callee.caller_address, + AccountField::Nonce, + caller_nonce.into(), + ); + + // Check if an error of ErrDepth, ErrInsufficientBalance or + // ErrNonceUintOverflow occurred. + let is_precheck_ok = + depth < 1025 && caller_balance >= callee.value && caller_nonce < u64::MAX; + + if is_precheck_ok { + // Increase caller's nonce + state.push_op_reversible( + &mut exec_step, + AccountOp { + address: caller.address, + field: AccountField::Nonce, + value: (caller_nonce + 1).into(), + value_prev: caller_nonce.into(), + }, + )?; + } + + // TODO: look into when this can be pushed. Could it be done in parse call? + state.push_call(callee.clone()); - // Increase callee's nonce - let nonce_prev = state.sdb.get_nonce(&call.address); - debug_assert!(nonce_prev == 0); - state.push_op_reversible( + for (field, value) in [ + ( + CallContextField::RwCounterEndOfReversion, + callee.rw_counter_end_of_reversion.to_word(), + ), + ( + CallContextField::IsPersistent, + callee.is_persistent.to_word(), + ), + ] { + state.call_context_write(&mut exec_step, callee.call_id, field, value); + } + + // if address created before, nonce is not zero + + // this could be good place for checking callee_exists = true, since above + // operation happens in evm create() method before checking + // ErrContractAddressCollision + let code_hash_previous = if callee_exists { + if is_precheck_ok { + // only create2 possibly cause address collision error. + assert_eq!(geth_step.op, OpcodeId::CREATE2); + exec_step.error = Some(ExecError::ContractAddressCollision); + } + callee_account.code_hash + } else { + H256::zero() + }; + + // use CodeHash rw not zero to check address already exists + state.account_read( &mut exec_step, - AccountOp { - address: call.address, - field: AccountField::Nonce, - value: 1.into(), - value_prev: 0.into(), - }, - )?; + address, + AccountField::CodeHash, + code_hash_previous.to_word(), + ); - let memory_expansion_gas_cost = - memory_expansion_gas_cost(curr_memory_word_size, next_memory_word_size); + if is_precheck_ok && !callee_exists { + state.transfer( + &mut exec_step, + callee.caller_address, + callee.address, + true, + true, + callee.value, + )?; + state.push_op_reversible( + &mut exec_step, + AccountOp { + address: callee.address, + field: AccountField::Nonce, + value: 1.into(), + value_prev: 0.into(), + }, + )?; + } - // EIP-150: all but one 64th of the caller's gas is sent to the callee. - let caller_gas_left = - (geth_step.gas.0 - geth_step.gas_cost.0 - memory_expansion_gas_cost) / 64; + // Per EIP-150, all but one 64th of the caller's gas is sent to the + // initialization call. + let caller_gas_left = (geth_step.gas.0 - geth_step.gas_cost.0) / 64; for (field, value) in [ ( @@ -147,40 +208,208 @@ impl Opcode for DummyCreate { (CallContextField::MemorySize, next_memory_word_size.into()), ( CallContextField::ReversibleWriteCounter, - // +3 is because we do some transfers after pushing the call. can be just push the - // call later? - (exec_step.reversible_write_counter + 3).into(), + (exec_step.reversible_write_counter + 2).into(), ), ] { - state.call_context_write(&mut exec_step, current_call.call_id, field, value); + state.call_context_write(&mut exec_step, caller.call_id, field, value); + } + + if !is_precheck_ok { + for (field, value) in [ + (CallContextField::LastCalleeId, 0.into()), + (CallContextField::LastCalleeReturnDataOffset, 0.into()), + (CallContextField::LastCalleeReturnDataLength, 0.into()), + ] { + state.call_context_write(&mut exec_step, caller.call_id, field, value); + } + state.handle_return(&mut exec_step, geth_steps, false)?; + return Ok(vec![exec_step]); } for (field, value) in [ - (CallContextField::CallerId, current_call.call_id.into()), - (CallContextField::IsSuccess, call.is_success.to_word()), - (CallContextField::IsPersistent, call.is_persistent.to_word()), + (CallContextField::CallerId, caller.call_id.into()), + (CallContextField::IsSuccess, callee.is_success.to_word()), + ( + CallContextField::IsPersistent, + callee.is_persistent.to_word(), + ), (CallContextField::TxId, state.tx_ctx.id().into()), ( CallContextField::CallerAddress, - current_call.address.to_word(), + callee.caller_address.to_word(), ), - (CallContextField::CalleeAddress, call.address.to_word()), + (CallContextField::CalleeAddress, callee.address.to_word()), ( CallContextField::RwCounterEndOfReversion, - call.rw_counter_end_of_reversion.to_word(), + callee.rw_counter_end_of_reversion.to_word(), ), - (CallContextField::IsPersistent, call.is_persistent.to_word()), + (CallContextField::Depth, callee.depth.to_word()), + (CallContextField::IsRoot, false.to_word()), + (CallContextField::IsStatic, false.to_word()), + (CallContextField::IsCreate, true.to_word()), + (CallContextField::CodeHash, code_hash.to_word()), + (CallContextField::Value, callee.value), ] { - state.call_context_write(&mut exec_step, call.call_id, field, value); + state.call_context_write(&mut exec_step, callee.call_id, field, value); } - if call.code_hash == CodeDB::empty_code_hash() { - // 1. Create with empty initcode. - state.handle_return(&mut exec_step, geth_steps, false)?; - Ok(vec![exec_step]) + let keccak_input = if IS_CREATE2 { + let salt = geth_step.stack.nth_last(3)?; + assert_eq!( + address, + get_create2_address( + caller.address, + salt.to_be_bytes().to_vec(), + initialization_code.clone(), + ) + ); + std::iter::once(0xffu8) + .chain(caller.address.to_fixed_bytes()) + .chain(salt.to_be_bytes()) + .chain(code_hash.to_fixed_bytes()) + .collect::>() } else { - // 2. Create with non-empty initcode. - Ok(vec![exec_step]) + let mut stream = rlp::RlpStream::new(); + stream.begin_list(2); + stream.append(&caller.address); + stream.append(&Word::from(caller_nonce)); + stream.out().to_vec() + }; + + assert_eq!( + address, + H160(keccak256(&keccak_input)[12..].try_into().unwrap()) + ); + + state.block.sha3_inputs.push(keccak_input); + state.block.sha3_inputs.push(initialization_code); + + if length == 0 || callee_exists { + for (field, value) in [ + (CallContextField::LastCalleeId, 0.into()), + (CallContextField::LastCalleeReturnDataOffset, 0.into()), + (CallContextField::LastCalleeReturnDataLength, 0.into()), + ] { + state.call_context_write(&mut exec_step, caller.call_id, field, value); + } + state.handle_return(&mut exec_step, geth_steps, false)?; } + + Ok(vec![exec_step]) + } +} + +fn handle_copy( + state: &mut CircuitInputStateRef, + step: &mut ExecStep, + callee_id: usize, + offset: usize, + length: usize, +) -> Result<(Vec, H256), Error> { + let initialization_bytes = state.call_ctx()?.memory.0[offset..offset + length].to_vec(); + let code_hash = CodeDB::hash(&initialization_bytes); + let bytes: Vec<_> = Bytecode::from(initialization_bytes.clone()) + .code + .iter() + .map(|element| (element.value, element.is_code)) + .collect(); + + let rw_counter_start = state.block_ctx.rwc; + for (i, (byte, _)) in bytes.iter().enumerate() { + // this could be a memory read, if this happens before we push the new call? + state.push_op( + step, + RW::READ, + MemoryOp::new(callee_id, (offset + i).into(), *byte), + ); + } + + state.push_copy( + step, + CopyEvent { + rw_counter_start, + src_type: CopyDataType::Memory, + src_id: NumberOrHash::Number(callee_id), + src_addr: offset.try_into().unwrap(), + src_addr_end: (offset + length).try_into().unwrap(), + dst_type: CopyDataType::Bytecode, + dst_id: NumberOrHash::Hash(code_hash), + dst_addr: 0, + log_id: None, + bytes, + }, + ); + + Ok((initialization_bytes, code_hash)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{circuit_input_builder::ExecState, mock::BlockData, operation::RW}; + use eth_types::{bytecode, evm_types::OpcodeId, geth_types::GethData, word}; + use mock::{test_ctx::helpers::account_0_code_account_1_no_code, TestContext}; + + #[test] + fn test_create_address_collision_error() { + let code = bytecode! { + PUSH21(word!("6B6020600060003760206000F3600052600C6014F3")) + PUSH1(0) + MSTORE + + PUSH1 (0xef) // salt + PUSH1 (0x15) // size + PUSH1 (0xB) // offset + PUSH1 (0) // value + CREATE2 + + PUSH1 (0xef) // salt + PUSH1 (0x15) // size + PUSH1 (0xB) // offset + PUSH1 (0) // value + CREATE2 + + PUSH1 (0x20) // retLength + PUSH1 (0x20) // retOffset + PUSH1 (0x20) // argsLength + PUSH1 (0x00) // argsOffset + PUSH1 (0x00) // value + DUP6 // addr from above CREATE2 + PUSH2 (0xFFFF) // gas + CALL + STOP + }; + + // Get the execution steps from the external tracer + let block: GethData = TestContext::<2, 1>::new( + None, + account_0_code_account_1_no_code(code), + |mut txs, accs| { + txs[0].from(accs[1].address).to(accs[0].address); + }, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap() + .into(); + + let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + + let tx_id = 1; + let transaction = &builder.block.txs()[tx_id - 1]; + let step = transaction + .steps() + .iter() + .filter(|step| step.exec_state == ExecState::Op(OpcodeId::CREATE2)) + .last() + .unwrap(); + + assert_eq!(step.error, Some(ExecError::ContractAddressCollision)); + + let container = builder.block.container.clone(); + let operation = &container.stack[step.bus_mapping_instance[0].as_usize()]; + assert_eq!(operation.rw(), RW::READ); } } diff --git a/bus-mapping/src/state_db.rs b/bus-mapping/src/state_db.rs index 33a563a424..6e98306ef1 100644 --- a/bus-mapping/src/state_db.rs +++ b/bus-mapping/src/state_db.rs @@ -190,6 +190,12 @@ impl StateDB { account.nonce.as_u64() } + /// Get balance of account with the given address. + pub fn get_balance(&self, addr: &Address) -> Word { + let (_, account) = self.get_account(addr); + account.balance + } + /// Increase nonce of account with `addr` and return the previous value. pub fn increase_nonce(&mut self, addr: &Address) -> u64 { let (_, account) = self.get_account_mut(addr); From 2d2bfc6ccf179ade1a8d063f9586b93e5283a557 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Fri, 14 Apr 2023 15:11:34 +0100 Subject: [PATCH 4/4] feat: CREATE/CREATE2 opcode gadget and tests --- zkevm-circuits/src/evm_circuit/execution.rs | 6 +- .../src/evm_circuit/execution/callop.rs | 1 + .../src/evm_circuit/execution/create.rs | 977 ++++++++++++++++++ .../src/evm_circuit/util/common_gadget.rs | 27 +- .../src/evm_circuit/util/math_gadget/rlp.rs | 41 +- .../src/evm_circuit/util/memory_gadget.rs | 8 + zkevm-circuits/src/witness/step.rs | 4 +- 7 files changed, 1046 insertions(+), 18 deletions(-) create mode 100644 zkevm-circuits/src/evm_circuit/execution/create.rs diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 83ac420dcc..bc23e37c7f 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -56,6 +56,7 @@ mod chainid; mod codecopy; mod codesize; mod comparator; +mod create; mod dummy; mod dup; mod end_block; @@ -127,6 +128,7 @@ use chainid::ChainIdGadget; use codecopy::CodeCopyGadget; use codesize::CodesizeGadget; use comparator::ComparatorGadget; +use create::CreateGadget; use dummy::DummyGadget; use dup::DupGadget; use end_block::EndBlockGadget; @@ -265,8 +267,8 @@ pub(crate) struct ExecutionConfig { shl_shr_gadget: Box>, returndatasize_gadget: Box>, returndatacopy_gadget: Box>, - create_gadget: Box>, - create2_gadget: Box>, + create_gadget: Box>, + create2_gadget: Box>, selfdestruct_gadget: Box>, signed_comparator_gadget: Box>, signextend_gadget: Box>, diff --git a/zkevm-circuits/src/evm_circuit/execution/callop.rs b/zkevm-circuits/src/evm_circuit/execution/callop.rs index d8058324e2..f986b7c0e0 100644 --- a/zkevm-circuits/src/evm_circuit/execution/callop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/callop.rs @@ -176,6 +176,7 @@ impl ExecutionGadget for CallOpGadget { caller_address.expr(), callee_address.expr(), not::expr(call_gadget.callee_not_exists.expr()), + 0.expr(), call_gadget.value.clone(), &mut callee_reversion_info, ) diff --git a/zkevm-circuits/src/evm_circuit/execution/create.rs b/zkevm-circuits/src/evm_circuit/execution/create.rs new file mode 100644 index 0000000000..011a8c02ba --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/create.rs @@ -0,0 +1,977 @@ +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + param::{ + N_BYTES_ACCOUNT_ADDRESS, N_BYTES_GAS, N_BYTES_MEMORY_ADDRESS, N_BYTES_MEMORY_WORD_SIZE, + N_BYTES_U64, N_BYTES_WORD, + }, + step::ExecutionState, + util::{ + common_gadget::TransferGadget, + constraint_builder::{ + ConstraintBuilder, ReversionInfo, StepStateTransition, + Transition::{Delta, To}, + }, + math_gadget::{ + ConstantDivisionGadget, ContractCreateGadget, IsZeroGadget, LtGadget, LtWordGadget, + }, + memory_gadget::{MemoryAddressGadget, MemoryExpansionGadget}, + not, select, CachedRegion, Cell, Word, + }, + witness::{Block, Call, ExecStep, Transaction}, + }, + table::{AccountFieldTag, CallContextFieldTag}, + util::Expr, +}; +use bus_mapping::{circuit_input_builder::CopyDataType, evm::OpcodeId, state_db::CodeDB}; +use eth_types::{evm_types::GasCost, Field, ToBigEndian, ToLittleEndian, ToScalar, U256}; +use ethers_core::utils::keccak256; +use gadgets::util::{and, expr_from_bytes}; +use halo2_proofs::{circuit::Value, plonk::Error}; + +use std::iter::once; + +/// Gadget for CREATE and CREATE2 opcodes +#[derive(Clone, Debug)] +pub(crate) struct CreateGadget { + opcode: Cell, + value: Word, + tx_id: Cell, + reversion_info: ReversionInfo, + was_warm: Cell, + depth: Cell, + callee_reversion_info: ReversionInfo, + callee_is_success: Cell, + transfer: TransferGadget, + init_code: MemoryAddressGadget, + init_code_word_size: ConstantDivisionGadget, + init_code_rlc: Cell, + memory_expansion: MemoryExpansionGadget, + gas_left: ConstantDivisionGadget, + create: ContractCreateGadget, + caller_balance: Word, + is_depth_in_range: LtGadget, + is_insufficient_balance: LtWordGadget, + is_nonce_in_range: LtGadget, + keccak_output: Word, + // prevous code hash befor creating + code_hash_previous: Cell, + // if code_hash_previous is zero, then no collision + not_address_collision: IsZeroGadget, +} + +impl ExecutionGadget + for CreateGadget +{ + const NAME: &'static str = "CREATE"; + + const EXECUTION_STATE: ExecutionState = S; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + // Use rw_counter of the step which triggers next call as its call_id. + let callee_call_id = cb.curr.state.rw_counter.clone(); + let code_hash_previous = cb.query_cell(); + let opcode = cb.query_cell(); + cb.opcode_lookup(opcode.expr(), 1.expr()); + + cb.require_equal( + "Opcode is CREATE or CREATE2", + opcode.expr(), + select::expr( + IS_CREATE2.expr(), + OpcodeId::CREATE2.expr(), + OpcodeId::CREATE.expr(), + ), + ); + + let value = cb.query_word_rlc(); + + let init_code_memory_offset = cb.query_cell_phase2(); + let init_code_length = cb.query_word_rlc(); + let init_code = + MemoryAddressGadget::construct(cb, init_code_memory_offset, init_code_length); + + let keccak_output = cb.query_word_rlc(); + let new_address_rlc = cb.word_rlc::( + keccak_output + .cells + .iter() + .take(N_BYTES_ACCOUNT_ADDRESS) + .map(Expr::expr) + .collect::>() + .try_into() + .unwrap(), + ); + let new_address = expr_from_bytes(&keccak_output.cells[..N_BYTES_ACCOUNT_ADDRESS]); + let callee_is_success = cb.query_bool(); + + let create = ContractCreateGadget::construct(cb); + + cb.stack_pop(value.expr()); + cb.stack_pop(init_code.offset_rlc()); + cb.stack_pop(init_code.length_rlc()); + cb.condition(IS_CREATE2.expr(), |cb| { + cb.stack_pop(create.salt_word_rlc(cb)); + }); + + cb.stack_push(callee_is_success.expr() * new_address_rlc); + + let init_code_rlc = cb.condition(init_code.has_length(), |cb| { + // the init code is being copied from memory to bytecode, so a copy table lookup to + // verify that the associated fields for the copy event. + let init_code_rlc = cb.query_cell_phase2(); + cb.copy_table_lookup( + cb.curr.state.call_id.expr(), + CopyDataType::Memory.expr(), + create.code_hash_word_rlc(cb), + CopyDataType::Bytecode.expr(), + init_code.offset(), + init_code.address(), + 0.expr(), + init_code.length(), + init_code_rlc.expr(), + init_code.length(), + ); + init_code_rlc + }); + cb.condition(not::expr(init_code.has_length()), |cb| { + cb.require_equal( + "code hash of empty bytes", + create.code_hash_word_rlc(cb), + cb.empty_code_hash_rlc(), + ); + }); + + let tx_id = cb.call_context(None, CallContextFieldTag::TxId); + let mut reversion_info = cb.reversion_info_read(None); + let was_warm = cb.query_bool(); + cb.account_access_list_write( + tx_id.expr(), + new_address.clone(), + 1.expr(), + was_warm.expr(), + Some(&mut reversion_info), + ); + + let depth = cb.call_context(None, CallContextFieldTag::Depth); + let is_depth_in_range = LtGadget::construct(cb, depth.expr(), 1025.expr()); + + cb.call_context_lookup( + 0.expr(), + None, + CallContextFieldTag::CalleeAddress, + create.caller_address(), + ); + + let caller_balance = cb.query_word_rlc(); + cb.account_read( + create.caller_address(), + AccountFieldTag::Balance, + caller_balance.expr(), + ); + let is_insufficient_balance = LtWordGadget::construct(cb, &caller_balance, &value); + + let caller_nonce = create.caller_nonce(); + cb.account_read( + create.caller_address(), + AccountFieldTag::Nonce, + caller_nonce.expr(), + ); + let is_nonce_in_range = LtGadget::construct(cb, caller_nonce.expr(), u64::MAX.expr()); + + cb.condition(is_insufficient_balance.expr(), |cb| { + cb.require_equal( + "Depth must be in range if caller balance is insufficient", + is_depth_in_range.expr(), + 1.expr(), + ); + }); + + cb.condition(not::expr(is_nonce_in_range.expr()), |cb| { + cb.require_equal( + "Depth must be in range and caller balance must be sufficient if nonce is overflow", + and::expr([ + is_depth_in_range.expr(), + not::expr(is_insufficient_balance.expr()), + ]), + 1.expr(), + ); + }); + + let is_precheck_ok = and::expr([ + is_depth_in_range.expr(), + not::expr(is_insufficient_balance.expr()), + is_nonce_in_range.expr(), + ]); + + cb.condition(is_precheck_ok.expr(), |cb| { + cb.account_write( + create.caller_address(), + AccountFieldTag::Nonce, + caller_nonce.expr() + 1.expr(), + caller_nonce, + Some(&mut reversion_info), + ); + }); + + cb.condition( + and::expr([is_precheck_ok.expr(), init_code.has_length()]), + |cb| { + cb.keccak_table_lookup( + init_code_rlc.expr(), + init_code.length(), + create.code_hash_word_rlc(cb), + ); + }, + ); + + let mut callee_reversion_info = cb.reversion_info_write(Some(callee_call_id.expr())); + cb.require_equal( + "callee_is_persistent == is_persistent ⋅ is_success", + callee_reversion_info.is_persistent(), + reversion_info.is_persistent() * callee_is_success.expr(), + ); + cb.condition(callee_is_success.expr() * (1.expr() - reversion_info.is_persistent()), |cb| { + cb.require_equal( + "callee_rw_counter_end_of_reversion == rw_counter_end_of_reversion - (reversible_write_counter + 1)", + callee_reversion_info.rw_counter_end_of_reversion(), + reversion_info.rw_counter_of_reversion(1.expr()), + ); + }); + + // check for address collision case by code hash previous + cb.account_read( + new_address.clone(), + AccountFieldTag::CodeHash, + code_hash_previous.expr(), + ); + + let not_address_collision = IsZeroGadget::construct(cb, code_hash_previous.expr()); + cb.condition(not::expr(not_address_collision.expr()), |cb| { + cb.require_equal( + "op code is create2 for address collision", + opcode.expr(), + OpcodeId::CREATE2.expr(), + ); + }); + + // conditional transfer for address collision case + let transfer = cb.condition( + and::expr([is_precheck_ok.expr(), not_address_collision.expr()]), + |cb| { + let tansfer_gadget = TransferGadget::construct( + cb, + create.caller_address(), + new_address.clone(), + 0.expr(), + 1.expr(), + value.clone(), + &mut callee_reversion_info, + ); + cb.account_write( + new_address.clone(), + AccountFieldTag::Nonce, + 1.expr(), + 0.expr(), + Some(&mut callee_reversion_info), + ); + + tansfer_gadget + }, + ); + + let memory_expansion = MemoryExpansionGadget::construct(cb, [init_code.address()]); + + let init_code_word_size = ConstantDivisionGadget::construct( + cb, + init_code.length() + (N_BYTES_WORD - 1).expr(), + N_BYTES_WORD as u64, + ); + let keccak_gas_cost = + GasCost::COPY_SHA3.expr() * IS_CREATE2.expr() * init_code_word_size.quotient(); + + let gas_cost = GasCost::CREATE.expr() + memory_expansion.gas_cost() + keccak_gas_cost; + let gas_remaining = cb.curr.state.gas_left.expr() - gas_cost.clone(); + let gas_left = ConstantDivisionGadget::construct(cb, gas_remaining.clone(), 64); + let callee_gas_left = gas_remaining - gas_left.quotient(); + for (field_tag, value) in [ + ( + CallContextFieldTag::ProgramCounter, + cb.curr.state.program_counter.expr() + 1.expr(), + ), + ( + CallContextFieldTag::StackPointer, + cb.curr.state.stack_pointer.expr() + 2.expr() + IS_CREATE2.expr(), + ), + (CallContextFieldTag::GasLeft, gas_left.quotient()), + ( + CallContextFieldTag::MemorySize, + memory_expansion.next_memory_word_size(), + ), + ( + CallContextFieldTag::ReversibleWriteCounter, + cb.curr.state.reversible_write_counter.expr() + 2.expr(), + ), + ] { + cb.call_context_lookup(true.expr(), None, field_tag, value); + } + + // Handle the case where an error of ErrDepth, ErrInsufficientBalance or + // ErrNonceUintOverflow occurred. + cb.condition(not::expr(is_precheck_ok.expr()), |cb| { + // Save caller's call state + for field_tag in [ + CallContextFieldTag::LastCalleeId, + CallContextFieldTag::LastCalleeReturnDataOffset, + CallContextFieldTag::LastCalleeReturnDataLength, + ] { + cb.call_context_lookup(true.expr(), None, field_tag, 0.expr()); + } + + cb.require_step_state_transition(StepStateTransition { + rw_counter: Delta(cb.rw_counter_offset()), + program_counter: Delta(1.expr()), + stack_pointer: Delta(2.expr() + IS_CREATE2.expr()), + memory_word_size: To(memory_expansion.next_memory_word_size()), + // - (Reversible) Write TxAccessListAccount (Contract Address) + reversible_write_counter: Delta(1.expr()), + gas_left: Delta(-gas_cost.expr()), + ..StepStateTransition::default() + }); + }); + + // Proceed to handle the case where precheck is OK. + cb.condition(is_precheck_ok, |cb| { + for (field_tag, value) in [ + (CallContextFieldTag::CallerId, cb.curr.state.call_id.expr()), + (CallContextFieldTag::IsSuccess, callee_is_success.expr()), + ( + CallContextFieldTag::IsPersistent, + callee_reversion_info.is_persistent(), + ), + (CallContextFieldTag::TxId, tx_id.expr()), + (CallContextFieldTag::CallerAddress, create.caller_address()), + (CallContextFieldTag::CalleeAddress, new_address), + ( + CallContextFieldTag::RwCounterEndOfReversion, + callee_reversion_info.rw_counter_end_of_reversion(), + ), + (CallContextFieldTag::Depth, depth.expr() + 1.expr()), + (CallContextFieldTag::IsRoot, false.expr()), + (CallContextFieldTag::IsStatic, false.expr()), + (CallContextFieldTag::IsCreate, true.expr()), + (CallContextFieldTag::CodeHash, create.code_hash_word_rlc(cb)), + (CallContextFieldTag::Value, value.expr()), + ] { + cb.call_context_lookup(true.expr(), Some(callee_call_id.expr()), field_tag, value); + } + + // keccak table lookup to verify contract address. + cb.keccak_table_lookup( + create.input_rlc(cb), + create.input_length(), + keccak_output.expr(), + ); + + // handle state transition if non-empty init code and no collision. + cb.condition( + init_code.has_length() * not_address_collision.expr(), + |cb| { + cb.require_step_state_transition(StepStateTransition { + rw_counter: Delta(cb.rw_counter_offset()), + call_id: To(callee_call_id.expr()), + is_root: To(false.expr()), + is_create: To(true.expr()), + code_hash: To(create.code_hash_word_rlc(cb)), + gas_left: To(callee_gas_left), + reversible_write_counter: To(1.expr() + transfer.reversible_w_delta()), + ..StepStateTransition::new_context() + }) + }, + ); + + // handle state transition if empty init code and no collision. + cb.condition( + not::expr(init_code.has_length()) * not_address_collision.expr(), + |cb| { + for field_tag in [ + CallContextFieldTag::LastCalleeId, + CallContextFieldTag::LastCalleeReturnDataOffset, + CallContextFieldTag::LastCalleeReturnDataLength, + ] { + cb.call_context_lookup(true.expr(), None, field_tag, 0.expr()); + } + cb.require_step_state_transition(StepStateTransition { + rw_counter: Delta(cb.rw_counter_offset()), + program_counter: Delta(1.expr()), + stack_pointer: Delta(2.expr() + IS_CREATE2.expr()), + gas_left: Delta(-gas_cost.expr()), + reversible_write_counter: Delta(3.expr() + transfer.reversible_w_delta()), + ..Default::default() + }) + }, + ); + + // handle address collision. + cb.condition(not::expr(not_address_collision.expr()), |cb| { + for field_tag in [ + CallContextFieldTag::LastCalleeId, + CallContextFieldTag::LastCalleeReturnDataOffset, + CallContextFieldTag::LastCalleeReturnDataLength, + ] { + cb.call_context_lookup(true.expr(), None, field_tag, 0.expr()); + } + + cb.require_step_state_transition(StepStateTransition { + rw_counter: Delta(cb.rw_counter_offset()), + program_counter: Delta(1.expr()), + stack_pointer: Delta(3.expr()), + gas_left: To(gas_left.quotient()), + reversible_write_counter: Delta(2.expr()), + ..Default::default() + }) + }); + }); + + Self { + opcode, + reversion_info, + tx_id, + was_warm, + value, + depth, + callee_reversion_info, + transfer, + init_code, + init_code_rlc, + memory_expansion, + gas_left, + callee_is_success, + init_code_word_size, + create, + caller_balance, + is_depth_in_range, + is_insufficient_balance, + is_nonce_in_range, + keccak_output, + code_hash_previous, + not_address_collision, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + tx: &Transaction, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + let opcode = step.opcode.unwrap(); + let is_create2 = opcode == OpcodeId::CREATE2; + self.opcode + .assign(region, offset, Value::known(F::from(opcode.as_u64())))?; + + let [value, init_code_start, init_code_length] = [0, 1, 2] + .map(|i| step.rw_indices[i]) + .map(|idx| block.rws[idx].stack_value()); + self.value + .assign(region, offset, Some(value.to_le_bytes()))?; + let salt = if is_create2 { + block.rws[step.rw_indices[3]].stack_value() + } else { + U256::zero() + }; + + let values: Vec<_> = (4 + usize::from(is_create2) + ..4 + usize::from(is_create2) + init_code_length.as_usize()) + .map(|i| block.rws[step.rw_indices[i]].memory_value()) + .collect(); + let copy_rw_increase = init_code_length.as_usize(); + let code_hash = CodeDB::hash(&values); + + let init_code_address = + self.init_code + .assign(region, offset, init_code_start, init_code_length)?; + self.init_code_rlc.assign( + region, + offset, + region.keccak_rlc(&values.iter().rev().cloned().collect::>()), + )?; + + self.tx_id + .assign(region, offset, Value::known(tx.id.to_scalar().unwrap()))?; + self.depth.assign( + region, + offset, + Value::known(call.depth.to_scalar().unwrap()), + )?; + + self.reversion_info.assign( + region, + offset, + call.rw_counter_end_of_reversion, + call.is_persistent, + )?; + + let tx_access_rw = + block.rws[step.rw_indices[7 + usize::from(is_create2) + copy_rw_increase]]; + self.was_warm.assign( + region, + offset, + Value::known( + tx_access_rw + .tx_access_list_value_pair() + .1 + .to_scalar() + .unwrap(), + ), + )?; + + let caller_balance = block.rws + [step.rw_indices[10 + usize::from(is_create2) + copy_rw_increase]] + .account_value_pair() + .1; + + let caller_nonce = block.rws + [step.rw_indices[11 + usize::from(is_create2) + copy_rw_increase]] + .account_value_pair() + .1 + .low_u64(); + + let is_precheck_ok = + if call.depth < 1025 && caller_balance >= value && caller_nonce < u64::MAX { + 1 + } else { + 0 + }; + + let [callee_rw_counter_end_of_reversion, callee_is_persistent] = [12, 13].map(|i| { + let rw = block.rws + [step.rw_indices[i + usize::from(is_create2) + copy_rw_increase + is_precheck_ok]]; + rw.call_context_value() + }); + + self.callee_reversion_info.assign( + region, + offset, + callee_rw_counter_end_of_reversion + .low_u64() + .try_into() + .unwrap(), + callee_is_persistent.low_u64() != 0, + )?; + + // retrieve code_hash for creating address + let code_hash_previous = block.rws + [step.rw_indices[14 + usize::from(is_create2) + copy_rw_increase + is_precheck_ok]] + .account_value_pair(); + let code_hash_previous_rlc = region.code_hash(code_hash_previous.0); + self.code_hash_previous + .assign(region, offset, code_hash_previous_rlc)?; + self.not_address_collision + .assign_value(region, offset, code_hash_previous_rlc)?; + let is_address_collision = !code_hash_previous.0.is_zero(); + + let mut rw_offset = 0; + if is_precheck_ok == 1 && !is_address_collision { + let [caller_balance_pair, callee_balance_pair] = if !value.is_zero() { + rw_offset += 2; + [16, 17].map(|i| { + block.rws[step.rw_indices + [i + usize::from(is_create2) + copy_rw_increase + is_precheck_ok]] + .account_value_pair() + }) + } else { + [(0.into(), 0.into()), (0.into(), 0.into())] + }; + + self.transfer.assign( + region, + offset, + caller_balance_pair, + callee_balance_pair, + value, + )?; + } + + let (_next_memory_word_size, memory_expansion_gas_cost) = self.memory_expansion.assign( + region, + offset, + step.memory_word_size(), + [init_code_address], + )?; + + let (init_code_word_size, _remainder) = self.init_code_word_size.assign( + region, + offset, + (31u64 + init_code_length.as_u64()).into(), + )?; + + let gas_left = step.gas_left + - GasCost::CREATE.as_u64() + - memory_expansion_gas_cost + - if is_create2 { + u64::try_from(init_code_word_size).unwrap() * GasCost::COPY_SHA3.as_u64() + } else { + 0 + }; + self.gas_left.assign(region, offset, gas_left.into())?; + + self.callee_is_success.assign( + region, + offset, + Value::known(if is_precheck_ok == 0 || is_address_collision { + F::zero() + } else { + block.rws[step.rw_indices + [23 + rw_offset + usize::from(is_create2) + copy_rw_increase + is_precheck_ok]] + .call_context_value() + .to_scalar() + .unwrap() + }), + )?; + + let keccak_input: Vec = if is_create2 { + once(0xffu8) + .chain(call.callee_address.to_fixed_bytes()) + .chain(salt.to_be_bytes()) + .chain(code_hash.to_fixed_bytes()) + .collect() + } else { + let mut stream = ethers_core::utils::rlp::RlpStream::new(); + stream.begin_list(2); + stream.append(&call.callee_address); + stream.append(&U256::from(caller_nonce)); + stream.out().to_vec() + }; + let mut keccak_output = keccak256(keccak_input); + keccak_output.reverse(); + + self.keccak_output + .assign(region, offset, Some(keccak_output))?; + + self.create.assign( + region, + offset, + call.callee_address, + caller_nonce, + Some(U256::from(code_hash.to_fixed_bytes())), + Some(salt), + )?; + self.caller_balance + .assign(region, offset, Some(caller_balance.to_le_bytes()))?; + self.is_insufficient_balance + .assign(region, offset, caller_balance, value)?; + + self.is_depth_in_range + .assign(region, offset, F::from(call.depth as u64), F::from(1025))?; + self.is_nonce_in_range + .assign(region, offset, F::from(caller_nonce), F::from(u64::MAX))?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::test_util::CircuitTestBuilder; + use eth_types::{ + address, bytecode, evm_types::OpcodeId, geth_types::Account, word, Address, Bytecode, Word, + }; + use itertools::Itertools; + use lazy_static::lazy_static; + use mock::{eth, TestContext}; + + const CALLEE_ADDRESS: Address = Address::repeat_byte(0xff); + lazy_static! { + static ref CALLER_ADDRESS: Address = address!("0x00bbccddee000000000000000000000000002400"); + } + + fn run_test_circuits(ctx: TestContext<2, 1>) { + CircuitTestBuilder::new_from_test_ctx(ctx).run(); + } + + // RETURN or REVERT with data of [0x60; 5] + fn initialization_bytecode(is_success: bool) -> Bytecode { + let memory_bytes = [0x60; 10]; + let memory_address = 0; + let memory_value = Word::from_big_endian(&memory_bytes); + let mut code = bytecode! { + PUSH10(memory_value) + PUSH1(memory_address) + MSTORE + PUSH2(5) + PUSH2(32u64 - u64::try_from(memory_bytes.len()).unwrap()) + }; + code.write_op(if is_success { + OpcodeId::RETURN + } else { + OpcodeId::REVERT + }); + code + } + + fn creater_bytecode( + initialization_bytecode: Bytecode, + value: Word, + is_create2: bool, + is_persistent: bool, + ) -> Bytecode { + let initialization_bytes = initialization_bytecode.code(); + let mut code = bytecode! { + PUSH32(Word::from_big_endian(&initialization_bytes)) + PUSH1(0) + MSTORE + }; + if is_create2 { + code.append(&bytecode! {PUSH1(45)}); // salt; + } + code.append(&bytecode! { + PUSH1(initialization_bytes.len()) // size + PUSH1(32 - initialization_bytes.len()) // length + PUSH2(value) // value + }); + code.write_op(if is_create2 { + OpcodeId::CREATE2 + } else { + OpcodeId::CREATE + }); + if !is_persistent { + code.append(&bytecode! { + PUSH1(0) + PUSH1(0) + REVERT + }); + } + code + } + + fn creater_bytecode_address_collision(initialization_bytecode: Bytecode) -> Bytecode { + let initialization_bytes = initialization_bytecode.code(); + let mut code = bytecode! { + PUSH32(Word::from_big_endian(&initialization_bytes)) + PUSH1(0) + MSTORE + }; + + code.append(&bytecode! {PUSH1(45)}); // salt; + code.append(&bytecode! { + PUSH1(initialization_bytes.len()) // size + PUSH1(32 - initialization_bytes.len()) // length + PUSH2(23414) // value + }); + code.write_op(OpcodeId::CREATE2); + + // construct address collision by create2 twice + code.append(&bytecode! {PUSH1(45)}); // salt; + + code.append(&bytecode! { + PUSH1(initialization_bytes.len()) // size + PUSH1(32 - initialization_bytes.len()) // length + PUSH2(23414) // value + }); + code.write_op(OpcodeId::CREATE2); + code.append(&bytecode! { + PUSH1(0) + PUSH1(0) + REVERT + }); + + code + } + + fn test_context(caller: Account) -> TestContext<2, 1> { + TestContext::new( + None, + |accs| { + accs[0] + .address(address!("0x000000000000000000000000000000000000cafe")) + .balance(eth(10)); + accs[1].account(&caller); + }, + |mut txs, accs| { + txs[0] + .from(accs[0].address) + .to(accs[1].address) + .gas(word!("0x2386F26FC10000")); + }, + |block, _| block, + ) + .unwrap() + } + + #[test] + fn test_create() { + for ((is_success, is_create2), is_persistent) in [true, false] + .iter() + .cartesian_product(&[true, false]) + .cartesian_product(&[true, false]) + { + let init_code = initialization_bytecode(*is_success); + let root_code = creater_bytecode(init_code, 23414.into(), *is_create2, *is_persistent); + let caller = Account { + address: *CALLER_ADDRESS, + code: root_code.into(), + nonce: Word::one(), + balance: eth(10), + ..Default::default() + }; + run_test_circuits(test_context(caller)); + } + } + + #[test] + fn test_create_rlp_nonce() { + for nonce in [0, 1, 127, 128, 255, 256, 0x10000, u64::MAX - 1] { + let caller = Account { + address: *CALLER_ADDRESS, + code: creater_bytecode(initialization_bytecode(true), 23414.into(), false, true) + .into(), + nonce: nonce.into(), + balance: eth(10), + ..Default::default() + }; + run_test_circuits(test_context(caller)) + } + } + + #[test] + fn test_create_empty_init_code() { + for is_create2 in [true, false] { + let caller = Account { + address: *CALLER_ADDRESS, + code: creater_bytecode(vec![].into(), 23414.into(), is_create2, true).into(), + nonce: 10.into(), + balance: eth(10), + ..Default::default() + }; + run_test_circuits(test_context(caller)); + } + } + + #[test] + fn test_create_overflow_offset_and_zero_size() { + for is_create2 in [true, false] { + let mut bytecode = bytecode! { + PUSH1(0) // size + PUSH32(Word::MAX) // offset + PUSH2(23414) // value + }; + bytecode.write_op(if is_create2 { + OpcodeId::CREATE2 + } else { + OpcodeId::CREATE + }); + let caller = Account { + address: *CALLER_ADDRESS, + code: bytecode.into(), + nonce: 10.into(), + balance: eth(10), + ..Default::default() + }; + run_test_circuits(test_context(caller)); + } + } + + #[test] + fn test_create_address_collision_error() { + let initialization_code = initialization_bytecode(false); + let root_code = creater_bytecode_address_collision(initialization_code); + let caller = Account { + address: *CALLER_ADDRESS, + code: root_code.into(), + nonce: Word::one(), + balance: eth(10), + ..Default::default() + }; + run_test_circuits(test_context(caller)); + } + + // Ignore this test case. It could run successfully but slow for CI. + #[ignore] + #[test] + fn test_create_error_depth() { + let code = bytecode! { + PUSH1(0x20) + PUSH1(0x0) + PUSH1(0x0) + CODECOPY + PUSH1(0x20) + PUSH1(0x0) + PUSH1(0x0) + CREATE + } + .into(); + let caller = Account { + address: *CALLER_ADDRESS, + code, + nonce: Word::one(), + balance: eth(10), + ..Default::default() + }; + run_test_circuits(test_context(caller)); + } + + #[test] + fn test_create_insufficient_balance() { + let value = 23414.into(); + for is_create2 in [true, false] { + let caller = Account { + address: mock::MOCK_ACCOUNTS[0], + nonce: 1.into(), + code: creater_bytecode(initialization_bytecode(false), value, is_create2, true) + .into(), + balance: value - 1, + ..Default::default() + }; + run_test_circuits(test_context(caller)); + } + } + + #[test] + fn test_create_nonce_uint_overflow() { + // TRICKY: + // Caller nonce could not be set to `u64::MAX` directly. Since geth + // [preCheck](https://github.com/ethereum/go-ethereum/blob/4a9fa31450d3cdcea84735b68cd5a0a8450473f8/core/state_transition.go#L262) + // function has a check for nonce uint overflow. So there are two nested + // CREATE (or CREATE2) in bytecode, which could increase nonce from + // `u64::MAX - 1` to `u64::MAX` in the internal loop. + let bytecodes = [ + bytecode! { + PUSH1(2) // size + PUSH1(0) // offset + PUSH1(1) // value + CREATE + PUSH1(2) // size + PUSH1(2) // offset + PUSH1(1) // value + CREATE + }, + bytecode! { + PUSH1(0) // salt + PUSH1(2) // size + PUSH1(0) // offset + PUSH1(1) // value + CREATE2 + PUSH1(0) // salt + PUSH1(2) // size + PUSH1(2) // offset + PUSH1(1) // value + CREATE2 + }, + ]; + + bytecodes.into_iter().for_each(|code| { + let caller = Account { + address: *CALLER_ADDRESS, + code: code.into(), + nonce: (u64::MAX - 1).into(), + balance: eth(10), + ..Default::default() + }; + run_test_circuits(test_context(caller)); + }); + } +} diff --git a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs index 2914907b9a..3a5ae293df 100644 --- a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs @@ -378,7 +378,7 @@ impl TransferWithGasFeeGadget { // If receiver doesn't exist, create it cb.condition( or::expr([ - not::expr(value_is_zero.expr()) * not::expr(receiver_exists.clone()), + not::expr(value_is_zero.expr()) * not::expr(receiver_exists.expr()), must_create.clone(), ]), |cb| { @@ -423,7 +423,7 @@ impl TransferWithGasFeeGadget { 1.expr() + // +1 Write Account (receiver) CodeHash (account creation via code_hash update) or::expr([ - not::expr(self.value_is_zero.expr()) * not::expr(self.receiver_exists.clone()), + not::expr(self.value_is_zero.expr()) * not::expr(self.receiver_exists.expr()), self.must_create.clone()] ) * 1.expr() + // +1 Write Account (sender) Balance @@ -435,7 +435,7 @@ impl TransferWithGasFeeGadget { // NOTE: Write Account (sender) Balance (Not Reversible tx fee) // +1 Write Account (receiver) CodeHash (account creation via code_hash update) or::expr([ - not::expr(self.value_is_zero.expr()) * not::expr(self.receiver_exists.clone()), + not::expr(self.value_is_zero.expr()) * not::expr(self.receiver_exists.expr()), self.must_create.clone()] ) * 1.expr() + // +1 Write Account (sender) Balance @@ -489,6 +489,8 @@ impl TransferWithGasFeeGadget { pub(crate) struct TransferGadget { sender: UpdateBalanceGadget, receiver: UpdateBalanceGadget, + must_create: Expression, + receiver_exists: Expression, pub(crate) value_is_zero: IsZeroGadget, } @@ -498,13 +500,17 @@ impl TransferGadget { sender_address: Expression, receiver_address: Expression, receiver_exists: Expression, + must_create: Expression, value: Word, reversion_info: &mut ReversionInfo, ) -> Self { let value_is_zero = IsZeroGadget::construct(cb, value.expr()); // If receiver doesn't exist, create it cb.condition( - not::expr(value_is_zero.expr()) * not::expr(receiver_exists), + or::expr([ + not::expr(value_is_zero.expr()) * not::expr(receiver_exists.expr()), + must_create.clone(), + ]), |cb| { cb.account_write( receiver_address.clone(), @@ -533,8 +539,10 @@ impl TransferGadget { }); Self { + must_create, sender, receiver, + receiver_exists, value_is_zero, } } @@ -547,6 +555,17 @@ impl TransferGadget { &self.receiver } + pub(crate) fn reversible_w_delta(&self) -> Expression { + // +1 Write Account (receiver) CodeHash (account creation via code_hash update) + or::expr([ + not::expr(self.value_is_zero.expr()) * not::expr(self.receiver_exists.clone()), + self.must_create.clone()] + ) * 1.expr() + + // +1 Write Account (sender) Balance + // +1 Write Account (receiver) Balance + not::expr(self.value_is_zero.expr()) * 2.expr() + } + pub(crate) fn assign( &self, region: &mut CachedRegion<'_, '_, F>, diff --git a/zkevm-circuits/src/evm_circuit/util/math_gadget/rlp.rs b/zkevm-circuits/src/evm_circuit/util/math_gadget/rlp.rs index d3932caccc..8adcf7598e 100644 --- a/zkevm-circuits/src/evm_circuit/util/math_gadget/rlp.rs +++ b/zkevm-circuits/src/evm_circuit/util/math_gadget/rlp.rs @@ -191,7 +191,7 @@ pub struct ContractCreateGadget { /// appropriate RLC wherever needed. code_hash: [Cell; N_BYTES_WORD], /// Random salt for CREATE2. - salt: RandomLinearCombination, + salt: [Cell; N_BYTES_WORD], } impl ContractCreateGadget { @@ -200,7 +200,7 @@ impl ContractCreateGadget { let caller_address = cb.query_keccak_rlc(); let nonce = RlpU64Gadget::construct(cb); let code_hash = array_init::array_init(|_| cb.query_byte()); - let salt = cb.query_keccak_rlc(); + let salt = array_init::array_init(|_| cb.query_byte()); Self { caller_address, @@ -227,11 +227,6 @@ impl ContractCreateGadget { self.nonce.assign(region, offset, caller_nonce)?; - self.salt.assign( - region, - offset, - Some(salt.map(|v| v.to_le_bytes()).unwrap_or_default()), - )?; for (c, v) in self .code_hash .iter() @@ -239,6 +234,13 @@ impl ContractCreateGadget { { c.assign(region, offset, Value::known(F::from(v as u64)))?; } + for (c, v) in self + .salt + .iter() + .zip(salt.map(|v| v.to_le_bytes()).unwrap_or_default()) + { + c.assign(region, offset, Value::known(F::from(v as u64)))?; + } Ok(()) } @@ -277,9 +279,28 @@ impl ContractCreateGadget { ) } + /// Salt EVM word RLC. + pub(crate) fn salt_word_rlc(&self, cb: &ConstraintBuilder) -> Expression { + cb.word_rlc::( + self.salt + .iter() + .map(Expr::expr) + .collect::>() + .try_into() + .unwrap(), + ) + } + /// Salt keccak RLC. - pub(crate) fn salt_keccak_rlc(&self) -> Expression { - self.salt.expr() + pub(crate) fn salt_keccak_rlc(&self, cb: &ConstraintBuilder) -> Expression { + cb.keccak_rlc::( + self.salt + .iter() + .map(Expr::expr) + .collect::>() + .try_into() + .unwrap(), + ) } /// Caller address' RLC value. @@ -323,7 +344,7 @@ impl ContractCreateGadget { let challenge_power_84 = challenge_power_64.clone() * challenge_power_20; (0xff.expr() * challenge_power_84) + (self.caller_address_rlc() * challenge_power_64) - + (self.salt_keccak_rlc() * challenge_power_32) + + (self.salt_keccak_rlc(cb) * challenge_power_32) + self.code_hash_keccak_rlc(cb) } else { // RLC(RLP([caller_address, caller_nonce])) diff --git a/zkevm-circuits/src/evm_circuit/util/memory_gadget.rs b/zkevm-circuits/src/evm_circuit/util/memory_gadget.rs index f637a3c072..4f06cc926f 100644 --- a/zkevm-circuits/src/evm_circuit/util/memory_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/memory_gadget.rs @@ -151,10 +151,18 @@ impl MemoryAddressGadget { self.has_length() * from_bytes::expr(&self.memory_offset_bytes.cells) } + pub(crate) fn offset_rlc(&self) -> Expression { + self.memory_offset.expr() + } + pub(crate) fn length(&self) -> Expression { from_bytes::expr(&self.memory_length.cells) } + pub(crate) fn length_rlc(&self) -> Expression { + self.memory_length.expr() + } + pub(crate) fn address(&self) -> Expression { self.offset() + self.length() } diff --git a/zkevm-circuits/src/witness/step.rs b/zkevm-circuits/src/witness/step.rs index afa66d34e1..ccb6d98af9 100644 --- a/zkevm-circuits/src/witness/step.rs +++ b/zkevm-circuits/src/witness/step.rs @@ -192,10 +192,10 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::RETURN | OpcodeId::REVERT => ExecutionState::RETURN_REVERT, OpcodeId::RETURNDATASIZE => ExecutionState::RETURNDATASIZE, OpcodeId::RETURNDATACOPY => ExecutionState::RETURNDATACOPY, + OpcodeId::CREATE => ExecutionState::CREATE, + OpcodeId::CREATE2 => ExecutionState::CREATE2, // dummy ops OpcodeId::EXTCODECOPY => dummy!(ExecutionState::EXTCODECOPY), - OpcodeId::CREATE => dummy!(ExecutionState::CREATE), - OpcodeId::CREATE2 => dummy!(ExecutionState::CREATE2), OpcodeId::SELFDESTRUCT => dummy!(ExecutionState::SELFDESTRUCT), _ => unimplemented!("unimplemented opcode {:?}", op), }