From 9ab662b22ee90d9afd1425654014e60d0fe6a678 Mon Sep 17 00:00:00 2001 From: Kimi Wu Date: Fri, 9 Jun 2023 22:26:01 +0900 Subject: [PATCH] Feat/CREATE Part C - bus-mapping and gadget implementation (#1430) ### Description NOTE: This is an updated version of https://github.com/privacy-scaling-explorations/zkevm-circuits/pull/1358 This PR is actually based on top of https://github.com/privacy-scaling-explorations/zkevm-circuits/pull/1425 ### Issue Link #1130 ### 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 ### Contents Bus mapping implementation for CREATE/CREATE2 EVM circuit's gadget for CREATE/CREATE2 ### How Has This Been Tested? Tests can be found here: https://github.com/scroll-tech/zkevm-circuits/blob/2d2bfc6ccf179ade1a8d063f9586b93e5283a557/zkevm-circuits/src/evm_circuit/execution/create.rs#L678 --------- Co-authored-by: Rohit Narurkar --- bus-mapping/src/circuit_input_builder/call.rs | 7 + .../circuit_input_builder/input_state_ref.rs | 61 +- bus-mapping/src/evm/opcodes.rs | 28 +- bus-mapping/src/evm/opcodes/create.rs | 435 ++++++-- bus-mapping/src/state_db.rs | 6 + zkevm-circuits/src/evm_circuit/execution.rs | 6 +- .../src/evm_circuit/execution/callop.rs | 24 +- .../src/evm_circuit/execution/create.rs | 926 ++++++++++++++++++ .../evm_circuit/execution/return_revert.rs | 16 +- zkevm-circuits/src/evm_circuit/step.rs | 4 +- .../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 + 13 files changed, 1432 insertions(+), 157 deletions(-) create mode 100644 zkevm-circuits/src/evm_circuit/execution/create.rs diff --git a/bus-mapping/src/circuit_input_builder/call.rs b/bus-mapping/src/circuit_input_builder/call.rs index 39a484ea14..564444d536 100644 --- a/bus-mapping/src/circuit_input_builder/call.rs +++ b/bus-mapping/src/circuit_input_builder/call.rs @@ -140,6 +140,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/circuit_input_builder/input_state_ref.rs b/bus-mapping/src/circuit_input_builder/input_state_ref.rs index b0a5e85d1b..f930b8d128 100644 --- a/bus-mapping/src/circuit_input_builder/input_state_ref.rs +++ b/bus-mapping/src/circuit_input_builder/input_state_ref.rs @@ -76,7 +76,22 @@ impl<'a> CircuitInputStateRef<'a> { ExecStep { exec_state: ExecState::EndTx, gas_left: if prev_step.error.is_none() { - prev_step.gas_left - prev_step.gas_cost + let mut gas_left = prev_step.gas_left - prev_step.gas_cost; + + // for contract creation + let call = self.tx.calls()[0].clone(); + if call.is_create() { + let code_hash = self.sdb.get_account(&call.address).1.code_hash; + let bytecode_len = self.code(code_hash).unwrap().len() as u64; + let deposit_cost = bytecode_len * GasCost::CODE_DEPOSIT_BYTE_COST; + assert!( + gas_left >= deposit_cost, + "gas left {gas_left} is not enough for deposit cost {deposit_cost}" + ); + gas_left -= deposit_cost; + } + + gas_left } else { // consume all remaining gas when non revert err happens 0 @@ -461,6 +476,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 2 reversible [`AccountOp`] to update `sender` and `receiver`'s /// balance by `value`. If `fee` is existing (not None), also need to push 1 /// non-reversible [`AccountOp`] to update `sender` balance by `fee`. @@ -688,6 +721,32 @@ impl<'a> CircuitInputStateRef<'a> { )) } + /// read reversion info + 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); + } + } + + /// write reversion info + pub(crate) fn reversion_info_write(&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_write(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]) diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index cb4326ac66..60fd50275a 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -71,7 +71,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; @@ -251,19 +251,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 @@ -289,26 +283,26 @@ 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), // call, callcode, create & create2 can encounter DepthError error, ExecError::Depth(DepthError::Call) => Some(CallOpcode::<7>::gen_associated_ops), - ExecError::Depth(DepthError::Create) => Some(DummyCreate::::gen_associated_ops), - ExecError::Depth(DepthError::Create2) => Some(DummyCreate::::gen_associated_ops), + ExecError::Depth(DepthError::Create) => Some(Create::::gen_associated_ops), + ExecError::Depth(DepthError::Create2) => Some(Create::::gen_associated_ops), // more future errors place here _ => { evm_unimplemented!("TODO: error state {:?} not implemented", error); diff --git a/bus-mapping/src/evm/opcodes/create.rs b/bus-mapping/src/evm/opcodes/create.rs index 3966f64162..a9842dce40 100644 --- a/bus-mapping/src/evm/opcodes/create.rs +++ b/bus-mapping/src/evm/opcodes/create.rs @@ -1,38 +1,61 @@ use crate::{ - circuit_input_builder::{CircuitInputStateRef, ExecStep}, + circuit_input_builder::{ + CircuitInputStateRef, CopyDataType, CopyEvent, ExecStep, NumberOrHash, + }, + error::ExecError, evm::Opcode, - operation::{AccountField, AccountOp, CallContextField, TxAccessListAccountOp}, + 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 tx_id = state.tx_ctx.id(); + let callee = state.parse_call(geth_step)?; + let caller = state.call()?.clone(); + + state.call_context_read( + &mut exec_step, + caller.call_id, + CallContextField::TxId, + tx_id.into(), + ); + let depth = caller.depth; + state.call_context_read( + &mut exec_step, + caller.call_id, + CallContextField::Depth, + depth.into(), + ); + + state.reversion_info_read(&mut exec_step, &caller); + + // stack operation // Get low Uint64 of offset to generate copy steps. Since offset could // be Uint64 overflow if length is zero. 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 mut exec_step = state.new_step(geth_step)?; + let next_memory_word_size = state.call_ctx()?.memory_word_size(); let n_pop = if IS_CREATE2 { 4 } else { 3 }; for i in 0..n_pop { @@ -49,92 +72,77 @@ 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() }, )?; + // stack end - 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( + // Get caller's balance and nonce + let caller_balance = state.sdb.get_balance(&caller.address); + let caller_nonce = state.sdb.get_nonce(&caller.address); + state.account_read( &mut exec_step, - TxAccessListAccountOp { - tx_id: state.tx_ctx.id(), - address, - is_warm: true, - is_warm_prev: is_warm, - }, - )?; - - // Increase caller's nonce - let nonce_prev = state.sdb.get_nonce(&call.caller_address); - state.push_op_reversible( + caller.address, + AccountField::Balance, + caller_balance, + ); + state.account_read( &mut exec_step, - AccountOp { - address: call.caller_address, - field: AccountField::Nonce, - value: (nonce_prev + 1).into(), - value_prev: nonce_prev.into(), - }, - )?; + caller.address, + AccountField::Nonce, + caller_nonce.into(), + ); - // Add callee into access list - let is_warm = state.sdb.check_account_in_access_list(&call.address); - state.push_op_reversible( - &mut exec_step, - TxAccessListAccountOp { - tx_id, - address: call.address, - is_warm: true, - is_warm_prev: is_warm, - }, - )?; - - state.push_call(call.clone()); - - state.transfer( - &mut exec_step, - call.caller_address, - call.address, - true, - true, - call.value, - )?; + // 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(), + }, + )?; - // Increase callee's nonce - let nonce_prev = state.sdb.get_nonce(&call.address); - debug_assert!(nonce_prev == 0); - state.push_op_reversible( - &mut exec_step, - AccountOp { - address: call.address, - field: AccountField::Nonce, - value: 1.into(), - value_prev: 0.into(), - }, - )?; + // add contract address to access list + state.tx_access_list_write(&mut exec_step, address)?; - let memory_expansion_gas_cost = - memory_expansion_gas_cost(curr_memory_word_size, next_memory_word_size); + // 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 { + exec_step.error = Some(ExecError::ContractAddressCollision); + } + callee_account.code_hash + } else { + H256::zero() + }; - // EIP-150: all but one 64th of the caller's gas is sent to the callee. - let caller_gas_left = (geth_step.gas - geth_step.gas_cost - memory_expansion_gas_cost) / 64; + state.account_read( + &mut exec_step, + callee.address, + AccountField::CodeHash, + code_hash_previous.to_word(), + ); + } + // 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 - geth_step.gas_cost) / 64; for (field, value) in [ (CallContextField::ProgramCounter, (geth_step.pc + 1).into()), ( @@ -145,40 +153,251 @@ 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); } - 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::TxId, state.tx_ctx.id().into()), - ( - CallContextField::CallerAddress, - current_call.address.to_word(), - ), - (CallContextField::CalleeAddress, call.address.to_word()), - ( - CallContextField::RwCounterEndOfReversion, - call.rw_counter_end_of_reversion.to_word(), - ), - (CallContextField::IsPersistent, call.is_persistent.to_word()), - ] { - state.call_context_write(&mut exec_step, call.call_id, field, value); - } + state.push_call(callee.clone()); + state.reversion_info_write(&mut exec_step, &callee); + + // successful contract creation + if is_precheck_ok && !callee_exists { + let (initialization_code, code_hash) = if length > 0 { + handle_copy( + state, + &mut exec_step, + state.caller()?.call_id, + offset, + length, + )? + } else { + (vec![], CodeDB::empty_code_hash()) + }; + + // handle keccak_table_lookup + 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 { + 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 call.code_hash == CodeDB::empty_code_hash() { - // 1. Create with empty initcode. + // Transfer function will skip transfer if the value is zero + state.transfer( + &mut exec_step, + caller.address, + callee.address, + callee_exists, + !callee_exists, + callee.value, + )?; + + // EIP 161, increase callee's nonce + state.push_op_reversible( + &mut exec_step, + AccountOp { + address: callee.address, + field: AccountField::Nonce, + value: 1.into(), + value_prev: 0.into(), + }, + )?; + + if length > 0 { + for (field, value) in [ + (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, + callee.caller_address.to_word(), + ), + (CallContextField::CalleeAddress, callee.address.to_word()), + ( + CallContextField::RwCounterEndOfReversion, + callee.rw_counter_end_of_reversion.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, callee.call_id, field, value); + } + } + // if it's empty init code + else { + 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)?; + }; + } + // failed case: is_precheck_ok is false or callee_exists is true + else { + 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]) - } else { - // 2. Create with non-empty initcode. - Ok(vec![exec_step]) } + + Ok(vec![exec_step]) + } +} + +fn handle_copy( + state: &mut CircuitInputStateRef, + step: &mut ExecStep, + call_id: usize, + offset: usize, + length: usize, +) -> Result<(Vec, H256), Error> { + let initialization_bytes = state.caller_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() { + state.push_op( + step, + RW::READ, + MemoryOp::new(call_id, (offset + i).into(), *byte), + ); + } + + state.push_copy( + step, + CopyEvent { + rw_counter_start, + src_type: CopyDataType::Memory, + src_id: NumberOrHash::Number(call_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(); + println!("{:?}", container.stack); + println!("-----"); + println!("{:?}", step.bus_mapping_instance); + println!("-----"); + let operation = &container.stack[step.bus_mapping_instance[5].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 905e41093b..84bf451109 100644 --- a/bus-mapping/src/state_db.rs +++ b/bus-mapping/src/state_db.rs @@ -202,6 +202,12 @@ impl StateDB { account.nonce } + /// 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); diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index ec26da51cd..7fbebe3b4c 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -58,6 +58,7 @@ mod chainid; mod codecopy; mod codesize; mod comparator; +mod create; mod dummy; mod dup; mod end_block; @@ -130,6 +131,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; @@ -269,8 +271,8 @@ pub 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 9ca15f7859..a6925157c7 100644 --- a/zkevm-circuits/src/evm_circuit/execution/callop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/callop.rs @@ -175,16 +175,20 @@ impl ExecutionGadget for CallOpGadget { // skip the transfer (this is necessary for non-existing accounts, which // will not be crated when value is 0 and so the callee balance lookup // would be invalid). - let transfer = cb.condition(and::expr(&[is_call.expr(), is_precheck_ok.expr()]), |cb| { - TransferGadget::construct( - cb, - caller_address.expr(), - callee_address.expr(), - not::expr(call_gadget.callee_not_exists.expr()), - call_gadget.value.clone(), - &mut callee_reversion_info, - ) - }); + let transfer = cb.condition( + is_call.expr() * not::expr(is_insufficient_balance.expr()), + |cb| { + TransferGadget::construct( + cb, + caller_address.expr(), + callee_address.expr(), + not::expr(call_gadget.callee_not_exists.expr()), + 0.expr(), + call_gadget.value.clone(), + &mut callee_reversion_info, + ) + }, + ); // For CALLCODE opcode, verify caller balance is greater than or equal to stack // `value` in successful case. that is `is_insufficient_balance` is false. 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..3d929a7d35 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/create.rs @@ -0,0 +1,926 @@ +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::{ + ConstrainBuilderCommon, EVMConstraintBuilder, ReversionInfo, StepStateTransition, + Transition::{Delta, To}, + }, + math_gadget::{ + ConstantDivisionGadget, ContractCreateGadget, IsZeroGadget, LtGadget, LtWordGadget, + }, + memory_gadget::{MemoryAddressGadget, MemoryExpansionGadget}, + not, 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, INIT_CODE_WORD_GAS}, + Field, ToBigEndian, ToLittleEndian, ToScalar, U256, +}; +use ethers_core::utils::keccak256; +use gadgets::util::{and, expr_from_bytes, or, select}; +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, + tx_id: Cell, + reversion_info: ReversionInfo, + depth: Cell, + + is_create2: IsZeroGadget, + is_success: Cell, + was_warm: Cell, + value: Word, + + caller_balance: Word, + callee_reversion_info: ReversionInfo, + callee_nonce: Cell, + prev_code_hash: Cell, + transfer: TransferGadget, + create: ContractCreateGadget, + + init_code: MemoryAddressGadget, + init_code_word_size: ConstantDivisionGadget, + init_code_rlc: Cell, + keccak_output: Word, + + is_depth_in_range: LtGadget, + is_insufficient_balance: LtWordGadget, + is_nonce_in_range: LtGadget, + not_address_collision: IsZeroGadget, + + memory_expansion: MemoryExpansionGadget, + gas_left: ConstantDivisionGadget, +} + +impl ExecutionGadget + for CreateGadget +{ + const NAME: &'static str = "CREATE"; + + const EXECUTION_STATE: ExecutionState = S; + + fn configure(cb: &mut EVMConstraintBuilder) -> Self { + let opcode = cb.query_cell(); + cb.opcode_lookup(opcode.expr(), 1.expr()); + cb.require_in_set( + "Opcode is CREATE or CREATE2", + opcode.expr(), + vec![OpcodeId::CREATE2.expr(), OpcodeId::CREATE.expr()], + ); + let is_create2 = IsZeroGadget::construct(cb, opcode.expr() - OpcodeId::CREATE2.expr()); + + // 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 current_call_id = cb.curr.state.call_id.clone(); + let is_success = cb.query_bool(); + + // read from call context + let tx_id = cb.call_context(None, CallContextFieldTag::TxId); + let depth = cb.call_context(None, CallContextFieldTag::Depth); + let mut reversion_info = cb.reversion_info_read(None); + + let keccak_output = cb.query_word_rlc(); + let create = ContractCreateGadget::construct(cb); + let contract_addr = expr_from_bytes(&keccak_output.cells[..N_BYTES_ACCOUNT_ADDRESS]); + let contract_addr_rlc = cb.word_rlc::( + keccak_output + .cells + .iter() + .take(N_BYTES_ACCOUNT_ADDRESS) + .map(Expr::expr) + .collect::>() + .try_into() + .unwrap(), + ); + + // stack operations + let value = cb.query_word_rlc(); + let offset = cb.query_cell_phase2(); + let length = cb.query_word_rlc(); + cb.stack_pop(value.expr()); + cb.stack_pop(offset.expr()); + cb.stack_pop(length.expr()); + cb.condition(is_create2.expr(), |cb| { + cb.stack_pop(create.salt_word_rlc(cb).expr()); + }); + cb.stack_push(is_success.expr() * contract_addr_rlc); + + // read caller's balance and nonce + let caller_nonce = create.caller_nonce(); + let caller_balance = cb.query_word_rlc(); + cb.account_read( + create.caller_address(), + AccountFieldTag::Balance, + caller_balance.expr(), + ); + cb.account_read( + create.caller_address(), + AccountFieldTag::Nonce, + caller_nonce.expr(), + ); + + // Pre-check: call depth, user's nonce and user's balance + let is_depth_in_range = LtGadget::construct(cb, depth.expr(), 1025.expr()); + let is_insufficient_balance = LtWordGadget::construct(cb, &caller_balance, &value); + let is_nonce_in_range = LtGadget::construct(cb, caller_nonce.expr(), u64::MAX.expr()); + let is_precheck_ok = and::expr([ + is_depth_in_range.expr(), + not::expr(is_insufficient_balance.expr()), + is_nonce_in_range.expr(), + ]); + + // verify gas cost + let init_code = MemoryAddressGadget::construct(cb, offset, length); + 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 = init_code_word_size.quotient() + * select::expr( + is_create2.expr(), + (INIT_CODE_WORD_GAS + GasCost::COPY_SHA3).expr(), + INIT_CODE_WORD_GAS.expr(), + ); + 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(); + + let was_warm = cb.query_bool(); + let init_code_rlc = cb.query_cell_phase2(); + let prev_code_hash = cb.query_cell(); + let callee_nonce = cb.query_cell(); + let not_address_collision = cb.condition(is_precheck_ok.expr(), |cb| { + // increase caller's nonce + cb.account_write( + create.caller_address(), + AccountFieldTag::Nonce, + caller_nonce.expr() + 1.expr(), + caller_nonce.expr(), + Some(&mut reversion_info), + ); + + // add callee to access list + cb.account_access_list_write( + tx_id.expr(), + contract_addr.clone(), + 1.expr(), + was_warm.expr(), + Some(&mut reversion_info), + ); + + // read contract's previous hash + cb.account_read( + contract_addr.clone(), + AccountFieldTag::CodeHash, + prev_code_hash.expr(), + ); + + // ErrContractAddressCollision, if any one of following criteria meets. + // Nonce is not zero or account code hash is not either 0 or EMPTY_CODE_HASH. + IsZeroGadget::construct( + cb, + callee_nonce.expr() + + prev_code_hash.expr() * (prev_code_hash.expr() - cb.empty_code_hash_rlc()), + ) + }); + + 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); + } + + let mut callee_reversion_info = cb.reversion_info_write(Some(callee_call_id.expr())); + let transfer = cb.condition( + and::expr([is_precheck_ok.clone(), not_address_collision.expr()]), + |cb| { + 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. + cb.copy_table_lookup( + current_call_id.expr(), + CopyDataType::Memory.expr(), + create.code_hash_word_rlc(cb), + CopyDataType::Bytecode.expr(), + init_code.offset(), + init_code.address(), + 0.expr(), // dst_addr + init_code.length(), // length + init_code_rlc.expr(), // rlc_acc + init_code.length(), // rwc_inc + ); + }); + + // keccak table lookup to verify contract address. + cb.keccak_table_lookup( + create.input_rlc(cb), + create.input_length(), + keccak_output.expr(), + ); + + // propagate is_persistent + cb.require_equal( + "callee_is_persistent == is_persistent ⋅ is_success", + callee_reversion_info.is_persistent(), + reversion_info.is_persistent() * is_success.expr(), + ); + + // transfer + let transfer = TransferGadget::construct( + cb, + create.caller_address(), + contract_addr.clone(), + 0.expr(), + 1.expr(), + value.clone(), + &mut callee_reversion_info, + ); + + // EIP 161, the nonce of a newly created contract is 1 + cb.account_write( + contract_addr.clone(), + AccountFieldTag::Nonce, + 1.expr(), + 0.expr(), + Some(&mut callee_reversion_info), + ); + + cb.condition(init_code.has_length(), |cb| { + for (field_tag, value) in [ + (CallContextFieldTag::CallerId, current_call_id.expr()), + (CallContextFieldTag::IsSuccess, is_success.expr()), + ( + CallContextFieldTag::IsPersistent, + callee_reversion_info.is_persistent(), + ), + (CallContextFieldTag::TxId, tx_id.expr()), + (CallContextFieldTag::CallerAddress, create.caller_address()), + (CallContextFieldTag::CalleeAddress, contract_addr), + ( + 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)), + ] { + cb.call_context_lookup( + true.expr(), + Some(callee_call_id.expr()), + field_tag, + value, + ); + } + + cb.require_step_state_transition(StepStateTransition { + // +1: move to new context + rw_counter: Delta(cb.rw_counter_offset() + 1.expr()), + 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().expr() + ), + ..StepStateTransition::new_context() + }) + }); + + // handle state transition if empty init code + cb.condition(not::expr(init_code.has_length()), |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().expr(), + ), + ..Default::default() + }) + }); + + transfer + }, + ); + + cb.condition( + 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()), + ); + }, + ); + + // Handle the case where an error of ErrDepth, ErrInsufficientBalance, + // ErrNonceUintOverflow or ErrContractAddressCollision occurred. + cb.condition( + or::expr([ + not::expr(is_precheck_ok), + not::expr(not_address_collision.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()), + gas_left: Delta(-gas_cost.expr()), + ..StepStateTransition::default() + }); + }, + ); + + Self { + opcode, + reversion_info, + tx_id, + was_warm, + value, + depth, + callee_reversion_info, + transfer, + init_code, + init_code_rlc, + memory_expansion, + gas_left, + init_code_word_size, + create, + caller_balance, + is_depth_in_range, + is_insufficient_balance, + is_nonce_in_range, + keccak_output, + not_address_collision, + is_success, + prev_code_hash, + callee_nonce, + is_create2, + } + } + + 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())))?; + self.is_create2.assign( + region, + offset, + F::from(opcode.as_u64()) - F::from(OpcodeId::CREATE2.as_u64()), + )?; + + 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, + block.get_rws(step, 2).call_context_value().as_usize(), + block.get_rws(step, 3).call_context_value().as_usize() != 0, + )?; + + // 0..3 : TxId, Depth, RwCounterEndOfReversion and IsPersistent + // stack value starts from 4 + let [value, init_code_start, init_code_length] = + [4, 5, 6].map(|idx| block.get_rws(step, idx).stack_value()); + self.value + .assign(region, offset, Some(value.to_le_bytes()))?; + let salt = if is_create2 { + block.get_rws(step, 7).stack_value() + } else { + U256::zero() + }; + let rw_offset = if is_create2 { 8 } else { 7 }; + + // Pre-check: call depth, user's nonce and user's balance + let (caller_balance, _) = block.get_rws(step, rw_offset + 1).account_value_pair(); + let (caller_nonce, _) = block.get_rws(step, rw_offset + 2).account_value_pair(); + let is_precheck_ok = + if call.depth < 1025 && caller_balance >= value && caller_nonce.as_u64() < u64::MAX { + 1 + } else { + 0 + }; + + self.caller_balance + .assign(region, offset, Some(caller_balance.to_le_bytes()))?; + let (callee_prev_code_hash, was_warm) = if is_precheck_ok == 1 { + let (_, was_warm) = block + .get_rws(step, rw_offset + 4) + .tx_access_list_value_pair(); + let (callee_prev_code_hash, _) = + block.get_rws(step, rw_offset + 5).account_value_pair(); + (callee_prev_code_hash, was_warm) + } else { + (U256::from(0), false) + }; + + // 3 RWs while is_precheck_ok is true + // account_write(caller), tx_access_list_write(callee) and account_read(callee) + let [callee_rw_counter_end_of_reversion, callee_is_persistent] = [ + rw_offset + 11 - (1 - is_precheck_ok) * 3, + rw_offset + 12 - (1 - is_precheck_ok) * 3, + ] + .map(|i| block.get_rws(step, i).call_context_value()); + + // retrieve code_hash for creating address + let is_address_collision = !callee_prev_code_hash.is_zero(); + let code_hash_previous_rlc = if is_address_collision { + region.code_hash(callee_prev_code_hash) + } else { + Value::known(F::ZERO) + }; + self.prev_code_hash + .assign(region, offset, code_hash_previous_rlc)?; + self.not_address_collision.assign( + region, + offset, + F::from((is_address_collision).into()), + )?; + + // gas cost of memory expansion + let init_code_address = + self.init_code + .assign(region, offset, init_code_start, init_code_length)?; + let (_, memory_expansion_gas_cost) = self.memory_expansion.assign( + region, + offset, + step.memory_word_size(), + [init_code_address], + )?; + let (init_code_word_size, _) = self.init_code_word_size.assign( + region, + offset, + (31u64 + init_code_length.as_u64()).into(), + )?; + let gas_left = step.gas_left + - GasCost::CREATE + - memory_expansion_gas_cost + - u64::try_from(init_code_word_size).unwrap() + * if is_create2 { + INIT_CODE_WORD_GAS + GasCost::COPY_SHA3 + } else { + INIT_CODE_WORD_GAS + }; + self.gas_left.assign(region, offset, gas_left.into())?; + self.callee_reversion_info.assign( + region, + offset, + callee_rw_counter_end_of_reversion.low_u64() as usize, + callee_is_persistent.low_u64() != 0, + )?; + + // assign witness while pre-check is okay + let copy_rw_increase = init_code_length.as_usize(); + let code_hash = if is_precheck_ok == 1 { + // transfer + let [caller_balance_pair, callee_balance_pair] = if !value.is_zero() { + [ + rw_offset + copy_rw_increase + 14, + rw_offset + copy_rw_increase + 15, + ] + .map(|i| block.get_rws(step, i).account_value_pair()) + } else { + [(0.into(), 0.into()), (0.into(), 0.into())] + }; + self.transfer.assign( + region, + offset, + caller_balance_pair, + callee_balance_pair, + value, + )?; + + // copy_table_lookup + let values: Vec<_> = (rw_offset + 13..rw_offset + 13 + copy_rw_increase) + .map(|i| block.get_rws(step, i).memory_value()) + .collect(); + let code_hash = CodeDB::hash(&values); + let keccak_input: Vec = if is_create2 { + once(0xffu8) + .chain(call.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.address); + stream.append(&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.init_code_rlc.assign( + region, + offset, + region.keccak_rlc(&values.iter().rev().cloned().collect::>()), + )?; + self.was_warm + .assign(region, offset, Value::known(F::from(was_warm.into())))?; + self.callee_nonce + .assign(region, offset, Value::known(F::ZERO))?; + + code_hash + } else { + CodeDB::empty_code_hash() + }; + + self.create.assign( + region, + offset, + call.address, + caller_nonce.as_u64(), + Some(U256::from(code_hash.to_fixed_bytes())), + Some(salt), + )?; + + // If transfer value is zero, there is no balance update + let transfer_offset = if value.is_zero() { 2 } else { 0 }; + self.is_success.assign( + region, + offset, + Value::known(if is_precheck_ok == 0 || is_address_collision { + F::ZERO + } else if init_code_length.as_usize() == 0 { + F::ONE + } else { + block + .get_rws(step, 18 + rw_offset + copy_rw_increase - transfer_offset) + .call_context_value() + .to_scalar() + .unwrap() + }), + )?; + + 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.as_u64()), + 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 a piece of random data, + // We don't care the init code content bcs we don't really run it. + // Here, we assign data with [0x60; 10], + 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 creator_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 creator_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 (endowment in CREATE2) + }); + 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 (endowment in CREATE2) + }); + 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 = creator_bytecode(init_code, 23414.into(), *is_create2, *is_persistent); + let caller = Account { + address: *CALLER_ADDRESS, + code: root_code.into(), + nonce: 1.into(), + 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: creator_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: creator_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 = creator_bytecode_address_collision(initialization_code); + let caller = Account { + address: *CALLER_ADDRESS, + code: root_code.into(), + nonce: 1.into(), + 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: 1.into(), + 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: creator_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. + for is_create2 in [true, false] { + let init_code = initialization_bytecode(true); + let mut root_code = creator_bytecode(init_code.clone(), 23414.into(), is_create2, true); + root_code.append(&root_code.clone()); + + // bytecodes.into_iter().for_each(|code| { + let caller = Account { + address: *CALLER_ADDRESS, + code: root_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/execution/return_revert.rs b/zkevm-circuits/src/evm_circuit/execution/return_revert.rs index f5dbefa402..9f1c0eeed2 100644 --- a/zkevm-circuits/src/evm_circuit/execution/return_revert.rs +++ b/zkevm-circuits/src/evm_circuit/execution/return_revert.rs @@ -19,7 +19,7 @@ use crate::{ util::Expr, }; use bus_mapping::{circuit_input_builder::CopyDataType, evm::OpcodeId, state_db::CodeDB}; -use eth_types::{Field, ToScalar, U256}; +use eth_types::{evm_types::GasCost, Field, ToScalar, U256}; use halo2_proofs::{circuit::Value, plonk::Error}; #[derive(Clone, Debug)] @@ -95,6 +95,9 @@ impl ExecutionGadget for ReturnRevertGadget { let is_contract_deployment = is_create.clone() * is_success.expr() * not::expr(copy_rw_increase_is_zero.expr()); + let code_deposit_cost = is_contract_deployment.clone() + * GasCost::CODE_DEPOSIT_BYTE_COST.expr() + * range.length(); let (caller_id, address, reversion_info, code_hash, deployed_code_rlc) = cb.condition(is_contract_deployment.clone(), |cb| { // We don't need to place any additional constraints on code_hash because the @@ -155,7 +158,7 @@ impl ExecutionGadget for ReturnRevertGadget { + not::expr(is_success.expr()) * cb.curr.state.reversible_write_counter.expr(), ), - gas_left: Delta(-memory_expansion.gas_cost()), + gas_left: Delta(-memory_expansion.gas_cost() - code_deposit_cost.expr()), reversible_write_counter: To(0.expr()), memory_word_size: To(0.expr()), ..StepStateTransition::default() @@ -498,7 +501,7 @@ mod test { { let initializer = callee_bytecode(*is_return, *offset, *length).code(); - let root_code = bytecode! { + let mut root_code = bytecode! { PUSH32(Word::from_big_endian(&initializer)) PUSH1(0) MSTORE @@ -509,6 +512,13 @@ mod test { CREATE }; + if !is_return { + root_code.append(&bytecode! { + PUSH1(0) + PUSH1(0) + REVERT + }); + } let ctx = TestContext::<2, 1>::new( None, diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index 17af7a0531..0fb0f1f74d 100644 --- a/zkevm-circuits/src/evm_circuit/step.rs +++ b/zkevm-circuits/src/evm_circuit/step.rs @@ -264,9 +264,9 @@ impl From<&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::CREATE => dummy!(ExecutionState::CREATE), - OpcodeId::CREATE2 => dummy!(ExecutionState::CREATE2), OpcodeId::SELFDESTRUCT => dummy!(ExecutionState::SELFDESTRUCT), _ => unimplemented!("unimplemented opcode {:?}", op), } diff --git a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs index 854eb40bba..7313f14f3e 100644 --- a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs @@ -375,7 +375,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| { @@ -420,7 +420,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 @@ -432,7 +432,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 @@ -486,6 +486,8 @@ impl TransferWithGasFeeGadget { pub(crate) struct TransferGadget { sender: UpdateBalanceGadget, receiver: UpdateBalanceGadget, + must_create: Expression, + receiver_exists: Expression, pub(crate) value_is_zero: IsZeroGadget, } @@ -495,13 +497,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(), @@ -530,8 +536,10 @@ impl TransferGadget { }); Self { + must_create, sender, receiver, + receiver_exists, value_is_zero, } } @@ -544,6 +552,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 6a5a2154c1..f8f7f69274 100644 --- a/zkevm-circuits/src/evm_circuit/util/math_gadget/rlp.rs +++ b/zkevm-circuits/src/evm_circuit/util/math_gadget/rlp.rs @@ -207,7 +207,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 { @@ -216,7 +216,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, @@ -243,11 +243,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() @@ -255,6 +250,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(()) } @@ -293,9 +295,28 @@ impl ContractCreateGadget { ) } + /// Salt EVM word RLC. + pub(crate) fn salt_word_rlc(&self, cb: &EVMConstraintBuilder) -> 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: &EVMConstraintBuilder) -> Expression { + cb.keccak_rlc::( + self.salt + .iter() + .map(Expr::expr) + .collect::>() + .try_into() + .unwrap(), + ) } /// Caller address' RLC value. @@ -339,7 +360,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 adb3016fe4..0e07615922 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() }