diff --git a/Cargo.lock b/Cargo.lock index 1c3c813aa9..78db2818cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -364,6 +364,8 @@ dependencies = [ "eth-types", "ethers-core", "ethers-providers", + "gadgets", + "halo2_proofs 0.1.0-beta.1", "hex", "itertools", "keccak256", @@ -374,6 +376,8 @@ dependencies = [ "rand", "serde", "serde_json", + "strum", + "strum_macros", "tokio", "url", ] @@ -1712,8 +1716,10 @@ dependencies = [ name = "gadgets" version = "0.1.0" dependencies = [ + "array-init", "digest 0.7.6", "eth-types", + "ff 0.11.1", "halo2_proofs 0.1.0-beta.1", "rand", "rand_xorshift", diff --git a/bus-mapping/Cargo.toml b/bus-mapping/Cargo.toml index 70d6972131..546a33ace8 100644 --- a/bus-mapping/Cargo.toml +++ b/bus-mapping/Cargo.toml @@ -7,14 +7,18 @@ license = "MIT OR Apache-2.0" [dependencies] eth-types = { path = "../eth-types" } +gadgets = { path = "../gadgets" } keccak256 = { path = "../keccak256" } ethers-core = "0.6" ethers-providers = "0.6" +halo2_proofs = { version = "0.1.0-beta.1" } itertools = "0.10" lazy_static = "1.4" log = "0.4.14" serde = {version = "1.0.130", features = ["derive"] } serde_json = "1.0.66" +strum = "0.24" +strum_macros = "0.24" [dev-dependencies] hex = "0.4.3" diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 41a9576553..bcaf8cba6a 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -22,7 +22,7 @@ pub use call::{Call, CallContext, CallKind}; use core::fmt::Debug; use eth_types::{self, Address, GethExecStep, GethExecTrace, Word}; use ethers_providers::JsonRpcClient; -pub use execution::{CopyDetails, ExecState, ExecStep, StepAuxiliaryData}; +pub use execution::{CopyDataType, CopyEvent, CopyStep, ExecState, ExecStep, NumberOrHash}; pub use input_state_ref::CircuitInputStateRef; use std::collections::HashMap; pub use transaction::{Transaction, TransactionContext}; diff --git a/bus-mapping/src/circuit_input_builder/block.rs b/bus-mapping/src/circuit_input_builder/block.rs index d8f2d007f5..13f7e606b7 100644 --- a/bus-mapping/src/circuit_input_builder/block.rs +++ b/bus-mapping/src/circuit_input_builder/block.rs @@ -1,6 +1,6 @@ //! Block-related utility module -use super::transaction::Transaction; +use super::{transaction::Transaction, CopyEvent}; use crate::{ operation::{OperationContainer, RWCounter}, Error, @@ -60,6 +60,8 @@ pub struct Block { pub container: OperationContainer, /// Transactions contained in the block pub txs: Vec, + /// Copy events in this block. + pub copy_events: Vec, code: HashMap>, } @@ -92,6 +94,7 @@ impl Block { base_fee: eth_block.base_fee_per_gas.unwrap_or_default(), container: OperationContainer::new(), txs: Vec::new(), + copy_events: Vec::new(), code: HashMap::new(), }) } @@ -106,3 +109,10 @@ impl Block { &mut self.txs } } + +impl Block { + /// Push a copy event to the block. + pub fn add_copy_event(&mut self, copy: CopyEvent) { + self.copy_events.push(copy); + } +} diff --git a/bus-mapping/src/circuit_input_builder/execution.rs b/bus-mapping/src/circuit_input_builder/execution.rs index 8a2566b166..4ac95bd9b5 100644 --- a/bus-mapping/src/circuit_input_builder/execution.rs +++ b/bus-mapping/src/circuit_input_builder/execution.rs @@ -1,10 +1,13 @@ //! Execution step related module. -use crate::{error::ExecError, exec_trace::OperationRef, operation::RWCounter}; +use crate::{error::ExecError, exec_trace::OperationRef, operation::RWCounter, operation::RW}; use eth_types::{ evm_types::{Gas, GasCost, OpcodeId, ProgramCounter}, - GethExecStep, U256, + GethExecStep, H256, }; +use gadgets::impl_expr; +use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; +use strum_macros::EnumIter; /// An execution step of the EVM. #[derive(Clone, Debug)] @@ -38,8 +41,6 @@ pub struct ExecStep { pub bus_mapping_instance: Vec, /// Error generated by this step pub error: Option, - /// Step auxiliary data - pub aux_data: Option, } impl ExecStep { @@ -65,7 +66,6 @@ impl ExecStep { log_id, bus_mapping_instance: Vec::new(), error: None, - aux_data: None, } } } @@ -86,7 +86,6 @@ impl Default for ExecStep { log_id: 0, bus_mapping_instance: Vec::new(), error: None, - aux_data: None, } } } @@ -100,12 +99,6 @@ pub enum ExecState { BeginTx, /// Virtual step End Tx EndTx, - /// Virtual step Copy To Memory - CopyToMemory, - /// Virtual step Copy To Log - CopyToLog, - /// Virtal step Copy Code To Memory - CopyCodeToMemory, } impl ExecState { @@ -146,90 +139,96 @@ impl ExecState { } } -/// Provides specific details about the data copy for which an -/// [`StepAuxiliaryData`] holds info about. -#[derive(Clone, Copy, Debug)] -pub enum CopyDetails { - /// Origin of the copied bytes is or not the Tx CallData. - TxCallData(bool), - /// Origin of the copied bytes is bytecode. For which it's hash is provided. - Code(U256), - /// The bytes are being copied to a Log. - /// Call's state change's persistance and tx_id are provided. - /// the data start index when enter this copy step - Log((bool, usize, usize)), +/// Defines the various source/destination types for a copy event. +#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter)] +pub enum CopyDataType { + /// When the source for the copy event is the bytecode table. + Bytecode = 1, + /// When the source/destination for the copy event is memory. + Memory, + /// When the source for the copy event is tx's calldata. + TxCalldata, + /// When the destination for the copy event is tx's log. + TxLog, } -/// Auxiliary data of Execution step -#[derive(Clone, Copy, Debug)] -pub struct StepAuxiliaryData { - /// Source start address - pub(crate) src_addr: u64, - /// Destination address. (0x00..00 for Log related aux data). - pub(crate) dst_addr: u64, - /// Bytes left - pub(crate) bytes_left: u64, - /// Source end address - pub(crate) src_addr_end: u64, - /// Detail info about the copied data. - pub(crate) copy_details: CopyDetails, -} - -impl StepAuxiliaryData { - /// Generates a new `StepAuxiliaryData` instance. - pub fn new( - src_addr: u64, - dst_addr: u64, - bytes_left: u64, - src_addr_end: u64, - copy_details: CopyDetails, - ) -> Self { - Self { - src_addr, - dst_addr, - bytes_left, - src_addr_end, - copy_details, - } - } - - /// Source start address - pub fn src_addr(&self) -> u64 { - self.src_addr - } - - /// Destination address - pub fn dst_addr(&self) -> u64 { - self.dst_addr - } - - /// Bytes left - pub fn bytes_left(&self) -> u64 { - self.bytes_left - } - - /// Source end address - pub fn src_addr_end(&self) -> u64 { - self.src_addr_end +impl From for usize { + fn from(t: CopyDataType) -> Self { + t as usize } +} - /// Indicate origin of the data to copy - pub fn copy_details(&self) -> CopyDetails { - self.copy_details +impl Default for CopyDataType { + fn default() -> Self { + Self::Memory } +} - /// Returns true if the data origin is Code. - pub fn is_code_originated(&self) -> bool { - matches!(self.copy_details, CopyDetails::Code(_)) - } +impl_expr!(CopyDataType); + +/// Defines a single copy step in a copy event. This type is unified over the +/// source/destination row in the copy table. +#[derive(Clone, Debug, PartialEq)] +pub struct CopyStep { + /// Address (source/destination) for the copy step. + pub addr: u64, + /// Represents the source/destination's type. + pub tag: CopyDataType, + /// Whether this step is a read or write step. + pub rw: RW, + /// Byte value copied in this step. + pub value: u8, + /// Optional field which is enabled only for the source being `bytecode`, + /// and represents whether or not the byte is an opcode. + pub is_code: Option, + /// Represents whether or not the copy step is a padding row. + pub is_pad: bool, + /// Represents the current RW counter at this copy step. + pub rwc: RWCounter, + /// A decrementing value representing the RW counters left in the copy event + /// including the current step's RW counter. + pub rwc_inc_left: u64, +} - /// Returns true if the data origin is a Tx. - pub fn is_tx_originated(&self) -> bool { - matches!(self.copy_details, CopyDetails::TxCallData(_)) - } +/// Defines an enum type that can hold either a number or a hash value. +#[derive(Clone, Debug, PartialEq)] +pub enum NumberOrHash { + /// Variant to indicate a number value. + Number(usize), + /// Variant to indicate a 256-bits hash value. + Hash(H256), +} - /// Returns true if the data is copied to Logs. - pub fn is_log_destinated(&self) -> bool { - matches!(self.copy_details, CopyDetails::Log(_)) - } +/// Defines a copy event associated with EVM opcodes such as CALLDATACOPY, +/// CODECOPY, CREATE, etc. More information: +/// https://github.com/privacy-scaling-explorations/zkevm-specs/blob/master/specs/copy-proof.md. +#[derive(Clone, Debug)] +pub struct CopyEvent { + /// Represents the start address at the source of the copy event. + pub src_addr: u64, + /// Represents the end address at the source of the copy event. + pub src_addr_end: u64, + /// Represents the source type. + pub src_type: CopyDataType, + /// Represents the relevant ID for source. + pub src_id: NumberOrHash, + /// Represents the start address at the destination of the copy event. + pub dst_addr: u64, + /// Represents the destination type. + pub dst_type: CopyDataType, + /// Represents the relevant ID for destination. + pub dst_id: NumberOrHash, + /// An optional field to hold the log ID in case of the destination being + /// TxLog. + pub log_id: Option, + /// Represents the number of bytes copied as a part of this copy event. + pub length: u64, + /// Represents the list of copy steps in this copy event. + pub steps: Vec, + /// Helper field for witness generation. + pub tx_id: usize, + /// Helper field for witness generation. + pub call_id: usize, + /// Helper field for witness generation. + pub pc: ProgramCounter, } 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 49d77d47e7..4378059cf7 100644 --- a/bus-mapping/src/circuit_input_builder/input_state_ref.rs +++ b/bus-mapping/src/circuit_input_builder/input_state_ref.rs @@ -2,7 +2,7 @@ use super::{ get_call_memory_offset_length, get_create_init_code, Block, BlockContext, Call, CallContext, - CallKind, CodeSource, ExecState, ExecStep, Transaction, TransactionContext, + CallKind, CodeSource, CopyEvent, ExecState, ExecStep, Transaction, TransactionContext, }; use crate::{ error::{get_step_reported_error, ExecError}, @@ -43,18 +43,12 @@ impl<'a> CircuitInputStateRef<'a> { pub fn new_step(&self, geth_step: &GethExecStep) -> Result { let call_ctx = self.tx_ctx.call_ctx()?; - let pre_log_id = if self.tx.is_steps_empty() { - 0 - } else { - self.tx.last_step().log_id - }; - Ok(ExecStep::new( geth_step, call_ctx.index, self.block_ctx.rwc, call_ctx.reversible_write_counter, - pre_log_id, + self.tx_ctx.log_id, )) } @@ -85,7 +79,7 @@ impl<'a> CircuitInputStateRef<'a> { } else { 0 }, - log_id: prev_step.log_id, + log_id: self.tx_ctx.log_id, ..Default::default() } } @@ -784,6 +778,11 @@ impl<'a> CircuitInputStateRef<'a> { Ok(()) } + /// Push a copy event to the state. + pub fn push_copy(&mut self, copy: CopyEvent) { + self.block.add_copy_event(copy); + } + pub(crate) fn get_step_err( &self, step: &GethExecStep, diff --git a/bus-mapping/src/circuit_input_builder/tracer_tests.rs b/bus-mapping/src/circuit_input_builder/tracer_tests.rs index f795a79b7c..83d63c89b4 100644 --- a/bus-mapping/src/circuit_input_builder/tracer_tests.rs +++ b/bus-mapping/src/circuit_input_builder/tracer_tests.rs @@ -295,9 +295,9 @@ fn tracer_err_address_collision() { let mut code_b = Bytecode::default(); // pad code_creator to multiple of 32 bytes - let len = code_creator.code().len(); + let len = code_creator.to_vec().len(); let code_creator: Vec = code_creator - .code() + .to_vec() .iter() .cloned() .chain(0u8..((32 - len % 32) as u8)) @@ -438,9 +438,9 @@ fn tracer_err_code_store_out_of_gas() { let mut code_b = Bytecode::default(); // pad code_creator to multiple of 32 bytes - let len = code_creator.code().len(); + let len = code_creator.to_vec().len(); let code_creator: Vec = code_creator - .code() + .to_vec() .iter() .cloned() .chain(0..(32 - len % 32) as u8) @@ -544,9 +544,9 @@ fn tracer_err_invalid_code() { let mut code_b = Bytecode::default(); // pad code_creator to multiple of 32 bytes - let len = code_creator.code().len(); + let len = code_creator.to_vec().len(); let code_creator: Vec = code_creator - .code() + .to_vec() .iter() .cloned() .chain(0u8..((32 - len % 32) as u8)) @@ -648,9 +648,9 @@ fn tracer_err_max_code_size_exceeded() { let mut code_b = Bytecode::default(); // pad code_creator to multiple of 32 bytes - let len = code_creator.code().len(); + let len = code_creator.to_vec().len(); let code_creator: Vec = code_creator - .code() + .to_vec() .iter() .cloned() .chain(0u8..((32 - len % 32) as u8)) @@ -742,9 +742,9 @@ fn tracer_create_stop() { let mut code_b = Bytecode::default(); // pad code_creator to multiple of 32 bytes - let len = code_creator.code().len(); + let len = code_creator.to_vec().len(); let code_creator: Vec = code_creator - .code() + .to_vec() .iter() .cloned() .chain(0u8..((32 - len % 32) as u8)) @@ -1216,7 +1216,7 @@ fn tracer_err_invalid_opcode() { // The second opcode is invalid (0x0f) let mut code = bytecode::Bytecode::default(); code.write_op(OpcodeId::PC); - code.write(0x0f); + code.write(0x0f, true); let block: GethData = TestContext::<2, 1>::new( None, |accs| { @@ -1450,9 +1450,9 @@ fn create2_address() { let mut code_b = Bytecode::default(); // pad code_creator to multiple of 32 bytes - let len = code_creator.code().len(); + let len = code_creator.to_vec().len(); let code_creator: Vec = code_creator - .code() + .to_vec() .iter() .cloned() .chain(0u8..((32 - len % 32) as u8)) @@ -1551,9 +1551,9 @@ fn create_address() { let mut code_b = Bytecode::default(); // pad code_creator to multiple of 32 bytes - let len = code_creator.code().len(); + let len = code_creator.to_vec().len(); let code_creator: Vec = code_creator - .code() + .to_vec() .iter() .cloned() .chain(0u8..((32 - len % 32) as u8)) @@ -1877,9 +1877,9 @@ fn test_gen_access_trace_create_push_call_stack() { let mut code_b = Bytecode::default(); // pad code_creator to multiple of 32 bytes - let len = code_creator.code().len(); + let len = code_creator.to_vec().len(); let code_creator: Vec = code_creator - .code() + .to_vec() .iter() .cloned() .chain(0u8..((32 - len % 32) as u8)) diff --git a/bus-mapping/src/circuit_input_builder/transaction.rs b/bus-mapping/src/circuit_input_builder/transaction.rs index d260471a87..d22d3de342 100644 --- a/bus-mapping/src/circuit_input_builder/transaction.rs +++ b/bus-mapping/src/circuit_input_builder/transaction.rs @@ -17,6 +17,8 @@ use super::{call::ReversionGroup, Call, CallContext, CallKind, CodeSource, ExecS pub struct TransactionContext { /// Unique identifier of transaction of the block. The value is `index + 1`. id: usize, + /// The index of logs made in the transaction. + pub(crate) log_id: usize, /// Identifier if this transaction is last one of the block or not. is_last_tx: bool, /// Call stack. @@ -71,6 +73,7 @@ impl TransactionContext { .ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))? .as_u64() as usize + 1, + log_id: 0, is_last_tx, call_is_success, calls: Vec::new(), diff --git a/bus-mapping/src/constants.rs b/bus-mapping/src/constants.rs deleted file mode 100644 index efff0ce27f..0000000000 --- a/bus-mapping/src/constants.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Module for declaring constants. - -/// Maximum number of bytes copied in a single iteration of internal steps like -/// CopyToMemory and CopyCodeToMemory. -pub const MAX_COPY_BYTES: usize = 32usize; diff --git a/bus-mapping/src/evm/opcodes/calldatacopy.rs b/bus-mapping/src/evm/opcodes/calldatacopy.rs index d83b99f67b..c67d41275e 100644 --- a/bus-mapping/src/evm/opcodes/calldatacopy.rs +++ b/bus-mapping/src/evm/opcodes/calldatacopy.rs @@ -1,12 +1,8 @@ use super::Opcode; +use crate::circuit_input_builder::{CircuitInputStateRef, ExecStep}; +use crate::circuit_input_builder::{CopyDataType, CopyEvent, CopyStep, NumberOrHash}; use crate::operation::{CallContextField, MemoryOp, RW}; use crate::Error; -use crate::{ - circuit_input_builder::{ - CircuitInputStateRef, CopyDetails, ExecState, ExecStep, StepAuxiliaryData, - }, - constants::MAX_COPY_BYTES, -}; use eth_types::GethExecStep; #[derive(Clone, Copy, Debug)] @@ -18,9 +14,9 @@ impl Opcode for Calldatacopy { geth_steps: &[GethExecStep], ) -> Result, Error> { let geth_step = &geth_steps[0]; - let mut exec_steps = vec![gen_calldatacopy_step(state, geth_step)?]; - let memory_copy_steps = gen_memory_copy_steps(state, geth_steps)?; - exec_steps.extend(memory_copy_steps); + let exec_steps = vec![gen_calldatacopy_step(state, geth_step)?]; + let copy_event = gen_copy_event(state, geth_step)?; + state.push_copy(copy_event); Ok(exec_steps) } } @@ -66,7 +62,6 @@ fn gen_calldatacopy_step( CallContextField::CallerId, state.call()?.caller_id.into(), ); - state.call_context_read( &mut exec_step, state.call()?.call_id, @@ -84,87 +79,127 @@ fn gen_calldatacopy_step( Ok(exec_step) } -fn gen_memory_copy_step( +fn gen_copy_steps( state: &mut CircuitInputStateRef, exec_step: &mut ExecStep, src_addr: u64, dst_addr: u64, src_addr_end: u64, - bytes_left: usize, + bytes_left: u64, is_root: bool, -) -> Result<(), Error> { - for idx in 0..std::cmp::min(bytes_left, MAX_COPY_BYTES) { - let addr = src_addr + idx as u64; - let byte = if addr < src_addr_end { +) -> Result, Error> { + let mut copy_steps = Vec::with_capacity(2 * bytes_left as usize); + for idx in 0..bytes_left { + let addr = src_addr + idx; + let rwc = state.block_ctx.rwc; + let (value, is_pad) = if addr < src_addr_end { let byte = state.call_ctx()?.call_data[(addr - state.call()?.call_data_offset) as usize]; if !is_root { state.push_op( exec_step, RW::READ, - MemoryOp::new(state.call()?.caller_id, (addr as usize).into(), byte), + MemoryOp::new(state.call()?.caller_id, addr.into(), byte), ); + (byte, false) + } else { + (byte, false) } - byte } else { - 0 + (0, true) + }; + let tag = if is_root { + CopyDataType::TxCalldata + } else { + CopyDataType::Memory }; - state.memory_write(exec_step, (idx + dst_addr as usize).into(), byte)?; + // Read + copy_steps.push(CopyStep { + addr, + tag, + rw: RW::READ, + value, + is_code: None, + is_pad, + rwc, + rwc_inc_left: 0, + }); + // Write + copy_steps.push(CopyStep { + addr: dst_addr + idx, + tag: CopyDataType::Memory, + rw: RW::WRITE, + value, + is_code: None, + is_pad: false, + rwc: state.block_ctx.rwc, + rwc_inc_left: 0, + }); + state.memory_write(exec_step, (dst_addr + idx).into(), value)?; } - exec_step.aux_data = Some(StepAuxiliaryData::new( - src_addr, - dst_addr, - bytes_left as u64, - src_addr_end, - CopyDetails::TxCallData(is_root), - )); + for cs in copy_steps.iter_mut() { + cs.rwc_inc_left = state.block_ctx.rwc.0 as u64 - cs.rwc.0 as u64; + } - Ok(()) + Ok(copy_steps) } -fn gen_memory_copy_steps( +fn gen_copy_event( state: &mut CircuitInputStateRef, - geth_steps: &[GethExecStep], -) -> Result, Error> { - let memory_offset = geth_steps[0].stack.nth_last(0)?.as_u64(); - let data_offset = geth_steps[0].stack.nth_last(1)?.as_u64(); - let length = geth_steps[0].stack.nth_last(2)?.as_usize(); + geth_step: &GethExecStep, +) -> Result { + let memory_offset = geth_step.stack.nth_last(0)?.as_u64(); + let data_offset = geth_step.stack.nth_last(1)?.as_u64(); + let length = geth_step.stack.nth_last(2)?.as_u64(); let call_data_offset = state.call()?.call_data_offset; let call_data_length = state.call()?.call_data_length; - let (src_addr, buffer_addr_end) = ( + let (src_addr, src_addr_end) = ( call_data_offset + data_offset, call_data_offset + call_data_length, ); - let mut copied = 0; - let mut steps = vec![]; - while copied < length { - let mut exec_step = state.new_step(&geth_steps[1])?; - exec_step.exec_state = ExecState::CopyToMemory; - gen_memory_copy_step( - state, - &mut exec_step, - src_addr + copied as u64, - memory_offset + copied as u64, - buffer_addr_end, - length - copied, - state.call()?.is_root, - )?; - steps.push(exec_step); - copied += MAX_COPY_BYTES; - } + let mut exec_step = state.new_step(geth_step)?; + let copy_steps = gen_copy_steps( + state, + &mut exec_step, + src_addr, + memory_offset, + src_addr_end, + length, + state.call()?.is_root, + )?; - Ok(steps) + let (src_type, src_id) = if state.call()?.is_root { + (CopyDataType::TxCalldata, state.tx_ctx.id()) + } else { + (CopyDataType::Memory, state.call()?.caller_id) + }; + + Ok(CopyEvent { + src_type, + src_id: NumberOrHash::Number(src_id), + src_addr, + src_addr_end, + dst_type: CopyDataType::Memory, + dst_id: NumberOrHash::Number(state.call()?.call_id), + dst_addr: memory_offset, + log_id: None, + length, + steps: copy_steps, + tx_id: state.tx_ctx.id(), + call_id: state.call()?.call_id, + pc: exec_step.pc, + }) } #[cfg(test)] mod calldatacopy_tests { use crate::{ - circuit_input_builder::ExecState, + circuit_input_builder::{CopyDataType, CopyStep, ExecState, NumberOrHash}, mock::BlockData, - operation::{CallContextField, CallContextOp, MemoryOp, StackOp, RW}, + operation::{CallContextField, CallContextOp, MemoryOp, RWCounter, StackOp, RW}, }; use eth_types::{ bytecode, @@ -307,12 +342,13 @@ mod calldatacopy_tests { ] ); - // memory writes. + // Memory reads/writes. + // // 1. First `call_data_length` memory ops are RW::WRITE and come from the `CALL` - // opcode. (we skip checking those) + // opcode. We skip checking those. + // // 2. Following that, we should have tuples of (RW::READ and RW::WRITE) where - // the caller memory is read and the current call's memory is written - // to. + // the caller memory is read and the current call's memory is written to. assert_eq!( builder.block.container.memory.len(), call_data_length + 2 * copy_size @@ -325,34 +361,97 @@ mod calldatacopy_tests { { let mut memory_ops = Vec::with_capacity(2 * copy_size); (0..copy_size).for_each(|idx| { + let value = if offset + call_data_offset + idx < memory_a.len() { + memory_a[offset + call_data_offset + idx] + } else { + 0 + }; memory_ops.push(( RW::READ, - MemoryOp::new( - caller_id, - (call_data_offset + offset + idx).into(), - memory_a[call_data_offset + idx], - ), + MemoryOp::new(caller_id, (call_data_offset + offset + idx).into(), value), )); memory_ops.push(( RW::WRITE, - MemoryOp::new( - expected_call_id, - (dst_offset + idx).into(), - memory_a[call_data_offset + idx], - ), + MemoryOp::new(expected_call_id, (dst_offset + idx).into(), value), )); }); memory_ops }, ); + + let copy_events = builder.block.copy_events.clone(); + assert_eq!(copy_events.len(), 1); + assert_eq!(copy_events[0].steps.len(), 2 * copy_size); + assert_eq!(copy_events[0].src_id, NumberOrHash::Number(caller_id)); + assert_eq!( + copy_events[0].dst_id, + NumberOrHash::Number(expected_call_id) + ); + assert_eq!(copy_events[0].length, copy_size as u64); + assert!(copy_events[0].log_id.is_none()); + assert_eq!(copy_events[0].src_addr as usize, offset + call_data_offset); + assert_eq!( + copy_events[0].src_addr_end as usize, + offset + call_data_offset + call_data_length + ); + assert_eq!(copy_events[0].dst_addr as usize, dst_offset); + + let mut rwc = RWCounter(step.rwc.0 + 6); + let mut rwc_inc = copy_events[0].steps.first().unwrap().rwc_inc_left; + for (idx, copy_rw_pair) in copy_events[0].steps.chunks(2).enumerate() { + assert_eq!(copy_rw_pair.len(), 2); + let (value, is_pad) = memory_a + .get(offset + call_data_offset + idx) + .cloned() + .map_or((0, true), |v| (v, false)); + // Read + let read_step = copy_rw_pair[0].clone(); + assert_eq!( + read_step, + CopyStep { + addr: (offset + call_data_offset + idx) as u64, + tag: CopyDataType::Memory, + rw: RW::READ, + is_code: None, + value, + is_pad, + rwc: if !is_pad { rwc.inc_pre() } else { rwc }, + rwc_inc_left: rwc_inc, + } + ); + if !is_pad { + rwc_inc -= 1; + } + // Write + let write_step = copy_rw_pair[1].clone(); + assert_eq!( + write_step, + CopyStep { + addr: (dst_offset + idx) as u64, + tag: CopyDataType::Memory, + rw: RW::WRITE, + is_code: None, + value, + is_pad: false, + rwc: rwc.inc_pre(), + rwc_inc_left: rwc_inc, + } + ); + rwc_inc -= 1; + } } #[test] fn calldatacopy_opcode_root() { + let size = 0x40; + let offset = 0x00; + let dst_offset = 0x00; + let calldata = vec![1, 3, 5, 7, 9, 2, 4, 6, 8]; + let calldata_len = calldata.len(); let code = bytecode! { - PUSH32(0) - PUSH32(0) - PUSH32(0x40) + PUSH32(size) + PUSH32(offset) + PUSH32(dst_offset) CALLDATACOPY STOP }; @@ -361,7 +460,12 @@ mod calldatacopy_tests { let block: GethData = TestContext::<2, 1>::new( None, account_0_code_account_1_no_code(code), - tx_from_1_to_0, + |mut txs, accs| { + txs[0] + .to(accs[0].address) + .from(accs[1].address) + .input(calldata.clone().into()); + }, |block, _tx| block, ) .unwrap() @@ -378,6 +482,7 @@ mod calldatacopy_tests { .find(|step| step.exec_state == ExecState::Op(OpcodeId::CALLDATACOPY)) .unwrap(); + let expected_call_id = builder.block.txs()[0].calls()[step.call_index].call_id; assert_eq!(step.bus_mapping_instance.len(), 5); assert_eq!( @@ -387,15 +492,15 @@ mod calldatacopy_tests { [ ( RW::READ, - &StackOp::new(1, StackAddress::from(1021), Word::from(0x40)) + &StackOp::new(1, StackAddress::from(1021), dst_offset.into()) ), ( RW::READ, - &StackOp::new(1, StackAddress::from(1022), Word::from(0)) + &StackOp::new(1, StackAddress::from(1022), offset.into()) ), ( RW::READ, - &StackOp::new(1, StackAddress::from(1023), Word::from(0)) + &StackOp::new(1, StackAddress::from(1023), size.into()) ), ] ); @@ -419,10 +524,82 @@ mod calldatacopy_tests { &CallContextOp { call_id: builder.block.txs()[0].calls()[0].call_id, field: CallContextField::CallDataLength, - value: Word::zero(), + value: calldata_len.into(), }, ), ] ); + + // Memory reads/writes. + // + // 1. Since its a root call, we should only have memory RW::WRITE where the + // current call's memory is written to. + assert_eq!(builder.block.container.memory.len(), size); + assert_eq!( + (0..size) + .map(|idx| &builder.block.container.memory[idx]) + .map(|op| (op.rw(), op.op().clone())) + .collect::>(), + { + let mut memory_ops = Vec::with_capacity(size); + (0..size).for_each(|idx| { + let value = if offset + idx < calldata_len { + calldata[offset + idx] + } else { + 0 + }; + memory_ops.push(( + RW::WRITE, + MemoryOp::new(expected_call_id, (dst_offset + idx).into(), value), + )); + }); + memory_ops + }, + ); + + let copy_events = builder.block.copy_events.clone(); + + // single copy event with `size` reads and `size` writes. + assert_eq!(copy_events.len(), 1); + assert_eq!(copy_events[0].steps.len(), 2 * size); + + let mut rwc = RWCounter(step.rwc.0 + 5); + for (idx, copy_rw_pair) in copy_events[0].steps.chunks(2).enumerate() { + assert_eq!(copy_rw_pair.len(), 2); + let (value, is_pad) = calldata + .get(offset as usize + idx) + .cloned() + .map_or((0, true), |v| (v, false)); + // read + let read_step = copy_rw_pair[0].clone(); + assert_eq!( + read_step, + CopyStep { + addr: (offset + idx) as u64, + tag: CopyDataType::TxCalldata, + rw: RW::READ, + value, + is_code: None, + is_pad, + rwc, + rwc_inc_left: (size - idx) as u64, + } + ); + // write + let write_step = copy_rw_pair[1].clone(); + assert_eq!( + write_step, + CopyStep { + addr: (dst_offset + idx) as u64, + tag: CopyDataType::Memory, + rw: RW::WRITE, + value, + is_code: None, + is_pad: false, + rwc: rwc.inc_pre(), + rwc_inc_left: (size - idx) as u64, + } + ); + } } } diff --git a/bus-mapping/src/evm/opcodes/codecopy.rs b/bus-mapping/src/evm/opcodes/codecopy.rs index d892c7190a..c859b88a97 100644 --- a/bus-mapping/src/evm/opcodes/codecopy.rs +++ b/bus-mapping/src/evm/opcodes/codecopy.rs @@ -1,11 +1,11 @@ use crate::{ circuit_input_builder::{ - CircuitInputStateRef, CopyDetails, ExecState, ExecStep, StepAuxiliaryData, + CircuitInputStateRef, CopyDataType, CopyEvent, CopyStep, ExecStep, NumberOrHash, }, - constants::MAX_COPY_BYTES, + operation::RW, Error, }; -use eth_types::{GethExecStep, ToWord}; +use eth_types::{Bytecode, GethExecStep}; use super::Opcode; @@ -18,9 +18,9 @@ impl Opcode for Codecopy { geth_steps: &[GethExecStep], ) -> Result, Error> { let geth_step = &geth_steps[0]; - let mut exec_steps = vec![gen_codecopy_step(state, geth_step)?]; - let memory_copy_steps = gen_memory_copy_steps(state, geth_steps)?; - exec_steps.extend(memory_copy_steps); + let exec_steps = vec![gen_codecopy_step(state, geth_step)?]; + let copy_event = gen_copy_event(state, geth_step)?; + state.push_copy(copy_event); Ok(exec_steps) } } @@ -51,62 +51,93 @@ fn gen_codecopy_step( Ok(exec_step) } -fn gen_memory_copy_step( +fn gen_copy_steps( state: &mut CircuitInputStateRef, exec_step: &mut ExecStep, - aux_data: StepAuxiliaryData, - code: &[u8], -) -> Result<(), Error> { - for idx in 0..std::cmp::min(aux_data.bytes_left as usize, MAX_COPY_BYTES) { - let addr = (aux_data.src_addr as usize) + idx; - let byte = if addr < (aux_data.src_addr_end as usize) { - code[addr] + src_addr: u64, + dst_addr: u64, + bytes_left: u64, + src_addr_end: u64, + bytecode: &Bytecode, +) -> Result, Error> { + let mut steps = Vec::with_capacity(2 * bytes_left as usize); + for idx in 0..bytes_left { + let addr = src_addr + idx; + let (value, is_code, is_pad) = if addr < src_addr_end { + bytecode + .get(addr as usize) + .map_or((0, None, true), |e| (e.value, Some(e.is_code), false)) } else { - 0 + (0, None, true) }; - state.memory_write(exec_step, ((aux_data.dst_addr as usize) + idx).into(), byte)?; + // Read + steps.push(CopyStep { + addr, + tag: CopyDataType::Bytecode, + rw: RW::READ, + value, + is_code, + is_pad, + rwc: state.block_ctx.rwc, + rwc_inc_left: bytes_left - idx, + }); + // Write + steps.push(CopyStep { + addr: dst_addr + idx, + tag: CopyDataType::Memory, + rw: RW::WRITE, + value, + is_code: None, + is_pad: false, + rwc: state.block_ctx.rwc, + rwc_inc_left: bytes_left - idx, + }); + state.memory_write(exec_step, (dst_addr + idx).into(), value)?; } - - exec_step.aux_data = Some(aux_data); - - Ok(()) + Ok(steps) } -fn gen_memory_copy_steps( +fn gen_copy_event( state: &mut CircuitInputStateRef, - geth_steps: &[GethExecStep], -) -> Result, Error> { - let dest_offset = geth_steps[0].stack.nth_last(0)?.as_u64(); - let code_offset = geth_steps[0].stack.nth_last(1)?.as_u64(); - let length = geth_steps[0].stack.nth_last(2)?.as_u64(); + geth_step: &GethExecStep, +) -> Result { + let dst_offset = geth_step.stack.nth_last(0)?.as_u64(); + let code_offset = geth_step.stack.nth_last(1)?.as_u64(); + let length = geth_step.stack.nth_last(2)?.as_u64(); let code_hash = state.call()?.code_hash; - let code = state.code(code_hash)?; - let src_addr_end = code.len() as u64; - - let code_hash = code_hash.to_word(); - let mut copied = 0; - let mut steps = vec![]; - while copied < length { - let mut exec_step = state.new_step(&geth_steps[1])?; - exec_step.exec_state = ExecState::CopyCodeToMemory; - gen_memory_copy_step( - state, - &mut exec_step, - StepAuxiliaryData::new( - code_offset + copied, - dest_offset + copied, - length - copied, - src_addr_end, - CopyDetails::Code(code_hash), - ), - &code, - )?; - steps.push(exec_step); - copied += MAX_COPY_BYTES as u64; - } + let bytecode: Bytecode = state + .code(code_hash)? + .try_into() + .map_err(eth_types::Error::BytecodeError)?; + let src_addr_end = bytecode.to_vec().len() as u64; - Ok(steps) + let mut exec_step = state.new_step(geth_step)?; + let copy_steps = gen_copy_steps( + state, + &mut exec_step, + code_offset, + dst_offset, + length, + src_addr_end, + &bytecode, + )?; + + Ok(CopyEvent { + src_type: CopyDataType::Bytecode, + src_id: NumberOrHash::Hash(code_hash), + src_addr: code_offset, + src_addr_end, + dst_type: CopyDataType::Memory, + dst_id: NumberOrHash::Number(state.call()?.call_id), + dst_addr: dst_offset, + log_id: None, + length, + steps: copy_steps, + tx_id: state.tx_ctx.id(), + call_id: state.call()?.call_id, + pc: exec_step.pc, + }) } #[cfg(test)] @@ -115,31 +146,31 @@ mod codecopy_tests { bytecode, evm_types::{MemoryAddress, OpcodeId, StackAddress}, geth_types::GethData, - Word, + Word, H256, }; + use ethers_core::utils::keccak256; use mock::{ test_ctx::helpers::{account_0_code_account_1_no_code, tx_from_1_to_0}, TestContext, }; use crate::{ + circuit_input_builder::{CopyDataType, CopyStep, ExecState, NumberOrHash}, mock::BlockData, - operation::{MemoryOp, StackOp, RW}, + operation::{MemoryOp, RWCounter, StackOp, RW}, }; - use super::*; - #[test] fn codecopy_opcode_impl() { test_ok(0x00, 0x00, 0x40); test_ok(0x20, 0x40, 0xA0); } - fn test_ok(dest_offset: usize, code_offset: usize, size: usize) { + fn test_ok(dst_offset: usize, code_offset: usize, size: usize) { let code = bytecode! { PUSH32(size) PUSH32(code_offset) - PUSH32(dest_offset) + PUSH32(dst_offset) CODECOPY STOP }; @@ -164,6 +195,8 @@ mod codecopy_tests { .find(|step| step.exec_state == ExecState::Op(OpcodeId::CODECOPY)) .unwrap(); + let expected_call_id = builder.block.txs()[0].calls()[step.call_index].call_id; + assert_eq!( [0, 1, 2] .map(|idx| &builder.block.container.stack[step.bus_mapping_instance[idx].as_usize()]) @@ -171,7 +204,7 @@ mod codecopy_tests { [ ( RW::READ, - &StackOp::new(1, StackAddress::from(1021), Word::from(dest_offset)), + &StackOp::new(1, StackAddress::from(1021), Word::from(dst_offset)), ), ( RW::READ, @@ -183,6 +216,8 @@ mod codecopy_tests { ), ] ); + + // RW table memory writes. assert_eq!( (0..size) .map(|idx| &builder.block.container.memory[idx]) @@ -194,9 +229,9 @@ mod codecopy_tests { RW::WRITE, MemoryOp::new( 1, - MemoryAddress::from(dest_offset + idx), - if code_offset + idx < code.code().len() { - code.code()[code_offset + idx] + MemoryAddress::from(dst_offset + idx), + if code_offset + idx < code.to_vec().len() { + code.to_vec()[code_offset + idx] } else { 0 }, @@ -205,5 +240,62 @@ mod codecopy_tests { }) .collect::>(), ); + + let copy_events = builder.block.copy_events.clone(); + assert_eq!(copy_events.len(), 1); + assert_eq!(copy_events[0].steps.len(), 2 * size); + assert_eq!( + copy_events[0].src_id, + NumberOrHash::Hash(H256(keccak256(&code.to_vec()))) + ); + assert_eq!(copy_events[0].src_addr as usize, code_offset); + assert_eq!(copy_events[0].src_addr_end as usize, code.to_vec().len()); + assert_eq!(copy_events[0].src_type, CopyDataType::Bytecode); + assert_eq!( + copy_events[0].dst_id, + NumberOrHash::Number(expected_call_id) + ); + assert_eq!(copy_events[0].dst_addr as usize, dst_offset); + assert_eq!(copy_events[0].dst_type, CopyDataType::Memory); + assert!(copy_events[0].log_id.is_none()); + assert_eq!(copy_events[0].length as usize, size); + + let mut rwc = RWCounter(step.rwc.0 + 3); + for (idx, copy_rw_pair) in copy_events[0].steps.chunks(2).enumerate() { + assert_eq!(copy_rw_pair.len(), 2); + let (value, is_code, is_pad) = code + .get(code_offset + idx) + .map_or((0, None, true), |e| (e.value, Some(e.is_code), false)); + // Read + let read_step = copy_rw_pair[0].clone(); + assert_eq!( + read_step, + CopyStep { + addr: (code_offset + idx) as u64, + tag: CopyDataType::Bytecode, + rw: RW::READ, + value, + is_code, + is_pad, + rwc, + rwc_inc_left: (size - idx) as u64, + } + ); + // Write + let write_step = copy_rw_pair[1].clone(); + assert_eq!( + write_step, + CopyStep { + addr: (dst_offset + idx) as u64, + tag: CopyDataType::Memory, + rw: RW::WRITE, + value, + is_code: None, + is_pad: false, + rwc: rwc.inc_pre(), + rwc_inc_left: (size - idx) as u64, + } + ); + } } } diff --git a/bus-mapping/src/evm/opcodes/logs.rs b/bus-mapping/src/evm/opcodes/logs.rs index 22590d6d8c..9f6474c4fa 100644 --- a/bus-mapping/src/evm/opcodes/logs.rs +++ b/bus-mapping/src/evm/opcodes/logs.rs @@ -1,15 +1,11 @@ use super::Opcode; -use crate::operation::{CallContextField, TxLogField}; +use crate::circuit_input_builder::{CircuitInputStateRef, ExecState, ExecStep}; +use crate::circuit_input_builder::{CopyDataType, CopyEvent, CopyStep, NumberOrHash}; +use crate::operation::{CallContextField, TxLogField, RW}; use crate::Error; -use crate::{ - circuit_input_builder::{ - CircuitInputStateRef, CopyDetails, ExecState, ExecStep, StepAuxiliaryData, - }, - constants::MAX_COPY_BYTES, -}; -use eth_types::evm_types::{MemoryAddress, OpcodeId}; +use eth_types::evm_types::OpcodeId; use eth_types::Word; -use eth_types::{GethExecStep, ToBigEndian, ToWord}; +use eth_types::{GethExecStep, ToWord}; #[derive(Clone, Copy, Debug)] pub(crate) struct Log; @@ -20,11 +16,13 @@ impl Opcode for Log { geth_steps: &[GethExecStep], ) -> Result, Error> { let geth_step = &geth_steps[0]; - - let mut exec_steps = vec![gen_log_step(state, geth_step)?]; - let log_copy_steps = gen_log_copy_steps(state, geth_steps)?; - exec_steps.extend(log_copy_steps); - Ok(exec_steps) + let mut exec_step = gen_log_step(state, geth_step)?; + if state.call()?.is_persistent { + let copy_event = gen_copy_event(state, geth_step, &mut exec_step)?; + state.push_copy(copy_event); + state.tx_ctx.log_id += 1; + } + Ok(vec![exec_step]) } } @@ -77,12 +75,11 @@ fn gen_log_step( Word::from(state.call()?.is_persistent as u8), ); - let log_id = exec_step.log_id; if state.call()?.is_persistent { state.tx_log_write( &mut exec_step, state.tx_ctx.id(), - log_id + 1, + state.tx_ctx.log_id + 1, TxLogField::Address, 0, state.call()?.address.to_word(), @@ -108,7 +105,7 @@ fn gen_log_step( state.tx_log_write( &mut exec_step, state.tx_ctx.id(), - log_id + 1, + state.tx_ctx.log_id + 1, TxLogField::Topic, i, topic, @@ -119,96 +116,115 @@ fn gen_log_step( Ok(exec_step) } -fn gen_log_copy_step( +fn gen_copy_steps( state: &mut CircuitInputStateRef, - geth_steps: &[GethExecStep], + geth_step: &GethExecStep, exec_step: &mut ExecStep, src_addr: u64, src_addr_end: u64, bytes_left: usize, data_start_index: usize, -) -> Result<(), Error> { +) -> Result, Error> { // Get memory data - let memory_address: MemoryAddress = Word::from(src_addr).try_into()?; - let mem_read_value = geth_steps[0].memory.read_word(memory_address).to_be_bytes(); - - let data_end_index = std::cmp::min(bytes_left, MAX_COPY_BYTES); - for (idx, _) in mem_read_value.iter().enumerate().take(data_end_index) { + let mem = geth_step + .memory + .read_chunk(src_addr.into(), bytes_left.into()); + let mut copy_steps = Vec::with_capacity(2 * bytes_left); + for (idx, byte) in mem.iter().enumerate() { let addr = src_addr + idx as u64; - let byte = if addr < src_addr_end { - let byte = mem_read_value[idx]; - state.memory_read(exec_step, (addr as usize).into(), byte)?; - byte + let rwc = state.block_ctx.rwc; + let (value, is_pad) = if addr < src_addr_end { + state.memory_read(exec_step, (addr as usize).into(), *byte)?; + (*byte, false) } else { - 0 + (0, true) }; - // write to tx log if persistent - let log_id = exec_step.log_id; - if state.call()?.is_persistent { - state.tx_log_write( - exec_step, - state.tx_ctx.id(), - log_id, - TxLogField::Data, - data_start_index + idx, - Word::from(byte), - )?; - } - } - exec_step.aux_data = Some(StepAuxiliaryData::new( - src_addr, - 0u64, - bytes_left as u64, - src_addr_end, - CopyDetails::Log(( - state.call()?.is_persistent, + // Read + copy_steps.push(CopyStep { + addr, + tag: CopyDataType::Memory, + rw: RW::READ, + value, + is_code: None, + is_pad, + rwc, + rwc_inc_left: 0, + }); + // Write + copy_steps.push(CopyStep { + addr: (data_start_index + idx) as u64, + tag: CopyDataType::TxLog, + rw: RW::WRITE, + value, + is_code: None, + is_pad: false, + rwc: state.block_ctx.rwc, + rwc_inc_left: 0, + }); + state.tx_log_write( + exec_step, state.tx_ctx.id(), - data_start_index, - )), - )); + state.tx_ctx.log_id + 1, + TxLogField::Data, + data_start_index + idx, + Word::from(value), + )?; + } - Ok(()) + Ok(copy_steps) } -fn gen_log_copy_steps( +fn gen_copy_event( state: &mut CircuitInputStateRef, - geth_steps: &[GethExecStep], -) -> Result, Error> { - let memory_start = geth_steps[0].stack.nth_last(0)?.as_u64(); - let msize = geth_steps[0].stack.nth_last(1)?.as_usize(); - - let (src_addr, buffer_addr_end) = (memory_start, memory_start + msize as u64); - - let mut copied = 0; - let mut steps = vec![]; - while copied < msize { - let mut exec_step = state.new_step(&geth_steps[1])?; - exec_step.log_id += 1; - - exec_step.exec_state = ExecState::CopyToLog; - gen_log_copy_step( - state, - geth_steps, - &mut exec_step, - src_addr + copied as u64, - buffer_addr_end, - msize - copied, - copied, - )?; - steps.push(exec_step); - copied += MAX_COPY_BYTES; + geth_step: &GethExecStep, + exec_step: &mut ExecStep, +) -> Result { + assert!(state.call()?.is_persistent, "Error: Call is not persistent"); + let memory_start = geth_step.stack.nth_last(0)?.as_u64(); + let msize = geth_step.stack.nth_last(1)?.as_usize(); + + let (src_addr, src_addr_end) = (memory_start, memory_start + msize as u64); + + let mut steps = gen_copy_steps( + state, + geth_step, + exec_step, + src_addr, + src_addr_end, + msize, + 0, + )?; + + for cs in steps.iter_mut() { + cs.rwc_inc_left = state.block_ctx.rwc.0 as u64 - cs.rwc.0 as u64; } - Ok(steps) + Ok(CopyEvent { + src_type: CopyDataType::Memory, + src_id: NumberOrHash::Number(state.call()?.call_id), + src_addr, + src_addr_end, + dst_type: CopyDataType::TxLog, + dst_id: NumberOrHash::Number(state.tx_ctx.id()), + dst_addr: 0, + log_id: Some(state.tx_ctx.log_id as u64 + 1), + length: msize as u64, + steps, + tx_id: state.tx_ctx.id(), + call_id: state.call()?.call_id, + pc: exec_step.pc, + }) } #[cfg(test)] mod log_tests { use crate::{ - circuit_input_builder::ExecState, + circuit_input_builder::{CopyDataType, CopyStep, ExecState, NumberOrHash}, mock::BlockData, - operation::{CallContextField, CallContextOp, MemoryOp, StackOp, TxLogField, TxLogOp, RW}, + operation::{ + CallContextField, CallContextOp, MemoryOp, RWCounter, StackOp, TxLogField, TxLogOp, RW, + }, }; use eth_types::{ bytecode, @@ -309,6 +325,8 @@ mod log_tests { .find(|step| step.exec_state == ExecState::Op(cur_op_code)) .unwrap(); + let expected_call_id = builder.block.txs()[0].calls()[step.call_index].call_id; + assert_eq!( [0, 1] .map(|idx| &builder.block.container.stack[step.bus_mapping_instance[idx].as_usize()]) @@ -385,6 +403,23 @@ mod log_tests { ),] ); } + + // log topic writes + let mut log_topic_ops = Vec::with_capacity(topic_count); + for (idx, topic) in topics.iter().rev().enumerate() { + log_topic_ops.push(( + RW::WRITE, + TxLogOp::new(1, step.log_id + 1, TxLogField::Topic, idx, *topic), + )); + } + assert_eq!( + (1..1 + topic_count) + .map(|idx| &builder.block.container.tx_log[idx]) + .map(|op| (op.rw(), op.op().clone())) + .collect::>(), + { log_topic_ops }, + ); + // memory reads. let mut log_data_ops = Vec::with_capacity(msize); assert_eq!( @@ -416,31 +451,78 @@ mod log_tests { memory_ops }, ); - - // log topic writes - let mut log_topic_ops = Vec::with_capacity(topic_count); - for (idx, topic) in topics.iter().rev().enumerate() { - log_topic_ops.push(( - RW::WRITE, - TxLogOp::new(1, step.log_id + 1, TxLogField::Topic, idx, *topic), - )); - } - assert_eq!( - (1..1 + topic_count) + ((1 + topic_count)..msize + 1 + topic_count) .map(|idx| &builder.block.container.tx_log[idx]) .map(|op| (op.rw(), op.op().clone())) .collect::>(), - { log_topic_ops }, + { log_data_ops }, ); - // log data writes + let copy_events = builder.block.copy_events.clone(); + assert_eq!(copy_events.len(), 1); + assert_eq!(copy_events[0].steps.len(), 2 * msize); + assert_eq!(copy_events[0].src_type, CopyDataType::Memory); assert_eq!( - ((1 + topic_count)..msize + 1 + topic_count) - .map(|idx| &builder.block.container.tx_log[idx]) - .map(|op| (op.rw(), op.op().clone())) - .collect::>(), - { log_data_ops }, + copy_events[0].src_id, + NumberOrHash::Number(expected_call_id) ); + assert_eq!(copy_events[0].src_addr as usize, mstart); + assert_eq!(copy_events[0].src_addr_end as usize, mstart + msize); + assert_eq!(copy_events[0].dst_type, CopyDataType::TxLog); + assert_eq!(copy_events[0].dst_id, NumberOrHash::Number(1)); // tx_id + assert_eq!(copy_events[0].dst_addr as usize, 0); + assert_eq!(copy_events[0].length as usize, msize); + assert_eq!(copy_events[0].log_id, Some(step.log_id as u64 + 1)); + + let mut rwc = RWCounter( + step.rwc.0 + // start of rwc + 6 + // 2 stack reads + 4 call context reads + 1 + // TxLogField::Address write + topic_count + // stack read for topics + topic_count, + ); // TxLogField::Topic write + let mut rwc_inc = copy_events[0].steps.first().unwrap().rwc_inc_left; + for (idx, copy_rw_pair) in copy_events[0].steps.chunks(2).enumerate() { + assert_eq!(copy_rw_pair.len(), 2); + let (value, is_pad) = memory_data + .get(mstart + idx) + .cloned() + .map_or((0, true), |v| (v, false)); + // Read + let read_step = copy_rw_pair[0].clone(); + assert_eq!( + read_step, + CopyStep { + addr: (mstart + idx) as u64, + rw: RW::READ, + tag: CopyDataType::Memory, + value, + is_code: None, + is_pad, + rwc: if !is_pad { rwc.inc_pre() } else { rwc }, + rwc_inc_left: rwc_inc, + } + ); + if !is_pad { + rwc_inc -= 1; + } + // Write + let write_step = copy_rw_pair[1].clone(); + assert_eq!( + write_step, + CopyStep { + addr: idx as u64, + rw: RW::WRITE, + tag: CopyDataType::TxLog, + value, + is_code: None, + is_pad: false, + rwc: rwc.inc_pre(), + rwc_inc_left: rwc_inc, + } + ); + rwc_inc -= 1; + } } } diff --git a/bus-mapping/src/lib.rs b/bus-mapping/src/lib.rs index 03561eeacd..4fe12c7c63 100644 --- a/bus-mapping/src/lib.rs +++ b/bus-mapping/src/lib.rs @@ -226,7 +226,6 @@ extern crate alloc; pub mod circuit_input_builder; -pub mod constants; pub mod error; pub mod evm; pub mod exec_trace; diff --git a/circuit-benchmarks/src/evm_circuit.rs b/circuit-benchmarks/src/evm_circuit.rs index ebadd6ce02..9fb695fc2e 100644 --- a/circuit-benchmarks/src/evm_circuit.rs +++ b/circuit-benchmarks/src/evm_circuit.rs @@ -25,6 +25,7 @@ impl Circuit for TestCircuit { let rw_table = [(); 11].map(|_| meta.advice_column()); let bytecode_table = [(); 5].map(|_| meta.advice_column()); let block_table = [(); 3].map(|_| meta.advice_column()); + let copy_table = [(); 11].map(|_| meta.advice_column()); // Use constant expression to mock constant instance column for a more // reasonable benchmark. let power_of_randomness = [(); 31].map(|_| Expression::Constant(F::one())); @@ -36,6 +37,7 @@ impl Circuit for TestCircuit { &rw_table, &bytecode_table, &block_table, + ©_table, ) } diff --git a/eth-types/src/bytecode.rs b/eth-types/src/bytecode.rs index 0e4863145d..1bac328307 100644 --- a/eth-types/src/bytecode.rs +++ b/eth-types/src/bytecode.rs @@ -3,17 +3,30 @@ use crate::{evm_types::OpcodeId, Bytes, Word}; use std::collections::HashMap; +/// Helper struct that represents a single element in a bytecode. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct BytecodeElement { + /// The byte value of the element. + pub value: u8, + /// Whether the element is an opcode or push data byte. + pub is_code: bool, +} + /// EVM Bytecode #[derive(Debug, Default, Clone, PartialEq)] pub struct Bytecode { - code: Vec, + code: Vec, num_opcodes: usize, markers: HashMap, } impl From for Bytes { fn from(code: Bytecode) -> Self { - code.code.into() + code.code + .iter() + .map(|e| e.value) + .collect::>() + .into() } } @@ -27,14 +40,14 @@ pub enum BytecodeError { } impl Bytecode { - /// Get a reference to the generated code - pub fn code(&self) -> &[u8] { - &self.code + /// Get the bytecode element at an index. + pub fn get(&self, index: usize) -> Option { + self.code.get(index).cloned() } /// Get the generated code pub fn to_vec(&self) -> Vec { - self.code.clone() + self.code.iter().map(|e| e.value).collect() } /// Append @@ -53,12 +66,12 @@ impl Bytecode { fn write_op_internal(&mut self, op: u8) -> &mut Self { self.num_opcodes += 1; - self.write(op) + self.write(op, true) } /// Write byte - pub fn write(&mut self, byte: u8) -> &mut Self { - self.code.push(byte); + pub fn write(&mut self, value: u8, is_code: bool) -> &mut Self { + self.code.push(BytecodeElement { value, is_code }); self } @@ -73,7 +86,7 @@ impl Bytecode { value.to_little_endian(&mut bytes); // Write the bytes MSB to LSB for i in 0..n { - self.write(bytes[n - 1 - i]); + self.write(bytes[n - 1 - i], false); } // Check if the full value could be pushed for byte in bytes.iter().skip(n) { diff --git a/eth-types/src/error.rs b/eth-types/src/error.rs index f79561acd7..34f7c69690 100644 --- a/eth-types/src/error.rs +++ b/eth-types/src/error.rs @@ -3,6 +3,8 @@ use core::fmt::{Display, Formatter, Result as FmtResult}; use std::error::Error as StdError; +use crate::bytecode::BytecodeError; + /// Error type for any BusMapping related failure. #[derive(Debug)] pub enum Error { @@ -31,6 +33,14 @@ pub enum Error { /// Error when an EvmWord is too big to be converted into a /// `MemoryAddress`. WordToMemAddr, + /// Error propagated from bytecode module. + BytecodeError(BytecodeError), +} + +impl From for Error { + fn from(e: BytecodeError) -> Self { + Self::BytecodeError(e) + } } impl Display for Error { diff --git a/gadgets/Cargo.toml b/gadgets/Cargo.toml index c8b3a51de8..97086c7546 100644 --- a/gadgets/Cargo.toml +++ b/gadgets/Cargo.toml @@ -6,6 +6,8 @@ authors = ["The appliedzkp team"] license = "MIT OR Apache-2.0" [dependencies] +array-init = "2.0.0" +ff = "0.11" halo2_proofs = { version = "0.1.0-beta.1" } sha3 = "0.7.2" eth-types = { path = "../eth-types" } diff --git a/gadgets/src/binary_number.rs b/gadgets/src/binary_number.rs index 1653167271..111c67b968 100644 --- a/gadgets/src/binary_number.rs +++ b/gadgets/src/binary_number.rs @@ -36,7 +36,7 @@ where } /// Config for the binary number chip. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct BinaryNumberConfig { /// Must be constrained to be binary for correctness. pub bits: [Column; N], @@ -100,6 +100,7 @@ where /// T. /// - creating expressions (via the Config) that evaluate to 1 when the bits /// match a specific value and 0 otherwise. +#[derive(Clone, Debug)] pub struct BinaryNumberChip { config: BinaryNumberConfig, _marker: PhantomData, diff --git a/gadgets/src/is_zero.rs b/gadgets/src/is_zero.rs index 8da0f7f715..abee41d252 100644 --- a/gadgets/src/is_zero.rs +++ b/gadgets/src/is_zero.rs @@ -11,6 +11,8 @@ use halo2_proofs::{ poly::Rotation, }; +use crate::util::Expr; + /// Trait that needs to be implemented for any gadget or circuit that wants to /// implement `IsZero`. pub trait IsZeroInstruction { @@ -36,6 +38,13 @@ pub struct IsZeroConfig { pub is_zero_expression: Expression, } +impl IsZeroConfig { + /// Returns the is_zero expression + pub fn expr(&self) -> Expression { + self.is_zero_expression.clone() + } +} + /// Wrapper arround [`IsZeroConfig`] for which [`Chip`] is implemented. pub struct IsZeroChip { config: IsZeroConfig, @@ -63,7 +72,7 @@ impl IsZeroChip { value_inv: Column, ) -> IsZeroConfig { // dummy initialization - let mut is_zero_expression = Expression::Constant(F::zero()); + let mut is_zero_expression = 0.expr(); meta.create_gate("is_zero gate", |meta| { let q_enable = q_enable(meta); @@ -71,19 +80,13 @@ impl IsZeroChip { let value_inv = meta.query_advice(value_inv, Rotation::cur()); let value = value(meta); - let one = Expression::Constant(F::one()); - is_zero_expression = one - value.clone() * value_inv.clone(); - - // This checks `value_inv ≡ value.invert()` when `value` is not - // zero: value ⋅ (1 - value ⋅ value_inv) - let poly1 = value * is_zero_expression.clone(); - // This checks `value_inv ≡ 0` when `value` is zero: - // value_inv ⋅ (1 - value ⋅ value_inv) - let poly2 = value_inv * is_zero_expression.clone(); + is_zero_expression = 1.expr() - value.clone() * value_inv; - [poly1, poly2] - .into_iter() - .map(move |poly| q_enable.clone() * poly) + // We wish to satisfy the below constrain for the following cases: + // + // 1. value == 0 + // 2. if value != 0, require is_zero_expression == 0 => value_inv == value.invert() + [q_enable * value * is_zero_expression.clone()] }); IsZeroConfig:: { diff --git a/gadgets/src/less_than.rs b/gadgets/src/less_than.rs index b90805de3c..4d57e330cd 100644 --- a/gadgets/src/less_than.rs +++ b/gadgets/src/less_than.rs @@ -28,7 +28,7 @@ pub trait LtInstruction { } /// Config for the Lt chip. -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct LtConfig { /// Denotes the lt outcome. If lhs < rhs then lt == 1, otherwise lt == 0. pub lt: Column, @@ -247,7 +247,7 @@ mod test { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { - let chip = LtChip::construct(config.lt.clone()); + let chip = LtChip::construct(config.lt); let values: Vec<_> = self .values @@ -366,7 +366,7 @@ mod test { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { - let chip = LtChip::construct(config.lt.clone()); + let chip = LtChip::construct(config.lt); let values: Vec<_> = self .values diff --git a/gadgets/src/util.rs b/gadgets/src/util.rs index 755381871a..57fc7abdec 100644 --- a/gadgets/src/util.rs +++ b/gadgets/src/util.rs @@ -190,7 +190,7 @@ pub fn expr_from_bytes>(bytes: &[E]) -> Expression { value } -/// Returns 2**by as Field +/// Returns 2**by as FieldExt pub fn pow_of_two(by: usize) -> F { F::from(2).pow(&[by as u64, 0, 0, 0]) } diff --git a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs index e8f86c66b2..ba854b63e6 100644 --- a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs +++ b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs @@ -760,7 +760,7 @@ mod tests { // First add all non-push bytes, which should all be seen as code for byte in 0u8..=255u8 { if !is_push(byte) { - bytecode.write(byte); + bytecode.write(byte, true); rows.push(BytecodeRow { hash: Fr::zero(), tag: Fr::from(BytecodeFieldTag::Byte as u64), diff --git a/zkevm-circuits/src/copy_circuit.rs b/zkevm-circuits/src/copy_circuit.rs new file mode 100644 index 0000000000..ffa10bb130 --- /dev/null +++ b/zkevm-circuits/src/copy_circuit.rs @@ -0,0 +1,918 @@ +//! The Copy circuit implements constraints and lookups for read-write steps for +//! copied bytes while execution opcodes such as CALLDATACOPY, CODECOPY, LOGS, +//! etc. + +use bus_mapping::circuit_input_builder::{CopyDataType, CopyEvent, CopyStep, NumberOrHash}; +use eth_types::{Field, ToAddress, ToScalar, U256}; +use gadgets::{ + binary_number::{BinaryNumberChip, BinaryNumberConfig}, + less_than::{LtChip, LtConfig, LtInstruction}, + util::{and, not, or, Expr}, +}; +use halo2_proofs::{ + circuit::{Layouter, Region}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Selector, VirtualCells}, + poly::Rotation, +}; + +use crate::evm_circuit::{ + table::{BytecodeFieldTag, LookupTable, RwTableTag, TxContextFieldTag, TxLogFieldTag}, + util::{constraint_builder::BaseConstraintBuilder, RandomLinearCombination}, + witness::Block, +}; + +/// The rw table shared between evm circuit and state circuit +#[derive(Clone, Copy, Debug)] +pub struct CopyCircuit { + /// Whether the row is enabled or not. + pub q_enable: Column, + /// Whether this row denotes a step. A read row is a step and a write row is + /// not. + pub q_step: Selector, + /// Whether the row is the first read-write pair for a copy event. + pub is_first: Column, + /// Whether the row is the last read-write pair for a copy event. + pub is_last: Column, + /// The relevant ID for the read-write row, represented as a random linear + /// combination. The ID may be one of the below: + /// 1. Call ID/Caller ID for CopyDataType::Memory + /// 2. RLC encoding of bytecode hash for CopyDataType::Bytecode + /// 3. Transaction ID for CopyDataType::TxCalldata, CopyDataType::TxLog + pub id: Column, + /// The source/destination address for this copy step. + pub addr: Column, + /// The end of the source buffer for the copy event. + pub src_addr_end: Column, + /// The number of bytes left to be copied. + pub bytes_left: Column, + /// The value copied in this copy step. + pub value: Column, + /// In case of a bytecode tag, this denotes whether or not the copied byte + /// is an opcode or push data byte. + pub is_code: Column, + /// Whether the row is padding. + pub is_pad: Column, + /// The associated read-write counter for this row. + pub rw_counter: Column, + /// Decrementing counter denoting reverse read-write counter. + pub rwc_inc_left: Column, + /// Binary chip to constrain the copy table conditionally depending on the + /// current row's tag, whether it is Bytecode, Memory, TxCalldata or + /// TxLog. + pub tag: BinaryNumberConfig, + /// Lt chip to check: src_addr < src_addr_end. + /// Since `src_addr` and `src_addr_end` are u64, 8 bytes are sufficient for + /// the Lt chip. + pub addr_lt_addr_end: LtConfig, +} + +impl LookupTable for CopyCircuit { + fn table_exprs(&self, meta: &mut VirtualCells) -> Vec> { + vec![ + meta.query_advice(self.is_first, Rotation::cur()), + meta.query_advice(self.id, Rotation::cur()), // src_id + self.tag.value(Rotation::cur())(meta), // src_tag + meta.query_advice(self.id, Rotation::next()), // dst_id + self.tag.value(Rotation::next())(meta), // dst_tag + meta.query_advice(self.addr, Rotation::cur()), // src_addr + meta.query_advice(self.src_addr_end, Rotation::cur()), // src_addr_end + meta.query_advice(self.addr, Rotation::next()), // dst_addr + meta.query_advice(self.bytes_left, Rotation::cur()), // length + meta.query_advice(self.rw_counter, Rotation::cur()), // rw_counter + meta.query_advice(self.rwc_inc_left, Rotation::cur()), // rwc_inc_left + ] + } +} + +impl CopyCircuit { + /// Configure the Copy Circuit constraining read-write steps and doing + /// appropriate lookups to the Tx Table, RW Table and Bytecode Table. + pub fn configure( + meta: &mut ConstraintSystem, + tx_table: &dyn LookupTable, + rw_table: &dyn LookupTable, + bytecode_table: &dyn LookupTable, + ) -> Self { + let q_enable = meta.fixed_column(); + let q_step = meta.complex_selector(); + let is_first = meta.advice_column(); + let is_last = meta.advice_column(); + let id = meta.advice_column(); + let addr = meta.advice_column(); + let src_addr_end = meta.advice_column(); + let bytes_left = meta.advice_column(); + let value = meta.advice_column(); + let is_code = meta.advice_column(); + let is_pad = meta.advice_column(); + let rw_counter = meta.advice_column(); + let rwc_inc_left = meta.advice_column(); + + let tag = BinaryNumberChip::configure(meta, q_enable); + + let addr_lt_addr_end = LtChip::configure( + meta, + |meta| meta.query_selector(q_step), + |meta| meta.query_advice(addr, Rotation::cur()), + |meta| meta.query_advice(src_addr_end, Rotation::cur()), + ); + + meta.create_gate("verify row", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_boolean( + "is_first is boolean", + meta.query_advice(is_first, Rotation::cur()), + ); + cb.require_boolean( + "is_last is boolean", + meta.query_advice(is_last, Rotation::cur()), + ); + cb.require_zero( + "is_first == 0 when q_step == 0", + and::expr([ + not::expr(meta.query_selector(q_step)), + meta.query_advice(is_first, Rotation::cur()), + ]), + ); + cb.require_zero( + "is_last == 0 when q_step == 1", + and::expr([ + meta.query_advice(is_last, Rotation::cur()), + meta.query_selector(q_step), + ]), + ); + + let not_last_two_rows = 1.expr() + - meta.query_advice(is_last, Rotation::cur()) + - meta.query_advice(is_last, Rotation::next()); + cb.condition(not_last_two_rows, |cb| { + cb.require_equal( + "rows[0].id == rows[2].id", + meta.query_advice(id, Rotation::cur()), + meta.query_advice(id, Rotation(2)), + ); + cb.require_equal( + "rows[0].tag == rows[2].tag", + tag.value(Rotation::cur())(meta), + tag.value(Rotation(2))(meta), + ); + cb.require_equal( + "rows[0].addr + 1 == rows[2].addr", + meta.query_advice(addr, Rotation::cur()) + 1.expr(), + meta.query_advice(addr, Rotation(2)), + ); + cb.require_equal( + "rows[0].src_addr_end == rows[2].src_addr_end for non-last step", + meta.query_advice(src_addr_end, Rotation::cur()), + meta.query_advice(src_addr_end, Rotation(2)), + ); + }); + + let rw_diff = and::expr([ + or::expr([ + tag.value_equals(CopyDataType::Memory, Rotation::cur())(meta), + tag.value_equals(CopyDataType::TxLog, Rotation::cur())(meta), + ]), + not::expr(meta.query_advice(is_pad, Rotation::cur())), + ]); + cb.condition( + not::expr(meta.query_advice(is_last, Rotation::cur())), + |cb| { + cb.require_equal( + "rows[0].rw_counter + rw_diff == rows[1].rw_counter", + meta.query_advice(rw_counter, Rotation::cur()) + rw_diff.clone(), + meta.query_advice(rw_counter, Rotation::next()), + ); + cb.require_equal( + "rows[0].rwc_inc_left - rw_diff == rows[1].rwc_inc_left", + meta.query_advice(rwc_inc_left, Rotation::cur()) - rw_diff.clone(), + meta.query_advice(rwc_inc_left, Rotation::next()), + ); + }, + ); + cb.condition(meta.query_advice(is_last, Rotation::cur()), |cb| { + cb.require_equal( + "rwc_inc_left == rw_diff for last row in the copy slot", + meta.query_advice(rwc_inc_left, Rotation::cur()), + rw_diff, + ); + }); + + cb.gate(meta.query_fixed(q_enable, Rotation::cur())) + }); + + meta.create_gate("verify step", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_zero( + "bytes_left == 1 for last step", + and::expr([ + meta.query_advice(is_last, Rotation::next()), + 1.expr() - meta.query_advice(bytes_left, Rotation::cur()), + ]), + ); + cb.condition( + not::expr(meta.query_advice(is_last, Rotation::next())), + |cb| { + cb.require_equal( + "bytes_left == bytes_left_next + 1 for non-last step", + meta.query_advice(bytes_left, Rotation::cur()), + meta.query_advice(bytes_left, Rotation(2)) + 1.expr(), + ); + }, + ); + cb.require_equal( + "write value == read value", + 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([ + meta.query_advice(is_pad, Rotation::cur()), + meta.query_advice(value, Rotation::cur()), + ]), + ); + cb.require_equal( + "is_pad == 1 - (src_addr < src_addr_end) for read row", + 1.expr() - addr_lt_addr_end.is_lt(meta, None), + meta.query_advice(is_pad, Rotation::cur()), + ); + cb.require_zero( + "is_pad == 0 for write row", + meta.query_advice(is_pad, Rotation::next()), + ); + + cb.gate(meta.query_selector(q_step)) + }); + + meta.lookup_any("Memory lookup", |meta| { + let cond = meta.query_fixed(q_enable, Rotation::cur()) + * tag.value_equals(CopyDataType::Memory, Rotation::cur())(meta) + * not::expr(meta.query_advice(is_pad, Rotation::cur())); + vec![ + meta.query_advice(rw_counter, Rotation::cur()), + not::expr(meta.query_selector(q_step)), + RwTableTag::Memory.expr(), + meta.query_advice(id, Rotation::cur()), // call_id + meta.query_advice(addr, Rotation::cur()), // memory address + 0.expr(), + 0.expr(), + meta.query_advice(value, Rotation::cur()), + 0.expr(), + 0.expr(), + 0.expr(), + ] + .into_iter() + .zip(rw_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (cond.clone() * arg, table)) + .collect() + }); + + meta.lookup_any("TxLog lookup", |meta| { + let cond = meta.query_fixed(q_enable, Rotation::cur()) + * tag.value_equals(CopyDataType::TxLog, Rotation::cur())(meta); + vec![ + meta.query_advice(rw_counter, Rotation::cur()), + 1.expr(), + RwTableTag::TxLog.expr(), + meta.query_advice(id, Rotation::cur()), // tx_id + meta.query_advice(addr, Rotation::cur()), // byte_index || field_tag || log_id + 0.expr(), + 0.expr(), + meta.query_advice(value, Rotation::cur()), + 0.expr(), + 0.expr(), + 0.expr(), + ] + .into_iter() + .zip(rw_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (cond.clone() * arg, table)) + .collect() + }); + + meta.lookup_any("Bytecode lookup", |meta| { + let cond = meta.query_fixed(q_enable, Rotation::cur()) + * tag.value_equals(CopyDataType::Bytecode, Rotation::cur())(meta) + * not::expr(meta.query_advice(is_pad, Rotation::cur())); + vec![ + meta.query_advice(id, Rotation::cur()), + BytecodeFieldTag::Byte.expr(), + meta.query_advice(addr, Rotation::cur()), + meta.query_advice(is_code, Rotation::cur()), + meta.query_advice(value, Rotation::cur()), + ] + .into_iter() + .zip(bytecode_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (cond.clone() * arg, table)) + .collect() + }); + + meta.lookup_any("Tx calldata lookup", |meta| { + let cond = meta.query_fixed(q_enable, Rotation::cur()) + * tag.value_equals(CopyDataType::TxCalldata, Rotation::cur())(meta) + * not::expr(meta.query_advice(is_pad, Rotation::cur())); + vec![ + meta.query_advice(id, Rotation::cur()), + TxContextFieldTag::CallData.expr(), + meta.query_advice(addr, Rotation::cur()), + meta.query_advice(value, Rotation::cur()), + ] + .into_iter() + .zip(tx_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (cond.clone() * arg, table)) + .collect() + }); + + Self { + q_enable, + q_step, + is_first, + is_last, + id, + addr, + src_addr_end, + bytes_left, + value, + is_code, + is_pad, + rw_counter, + rwc_inc_left, + tag, + addr_lt_addr_end, + } + } + + /// Assign a witness block to the Copy Circuit. + pub fn assign_block( + &self, + layouter: &mut impl Layouter, + block: &Block, + ) -> Result<(), Error> { + let tag_chip = BinaryNumberChip::construct(self.tag); + let lt_chip = LtChip::construct(self.addr_lt_addr_end); + + layouter.assign_region( + || "assign copy table", + |mut region| { + let mut offset = 0; + for copy_event in block.copy_events.values() { + for (step_idx, copy_step) in copy_event.steps.iter().enumerate() { + self.assign_step( + &mut region, + offset, + block.randomness, + copy_event, + step_idx, + copy_step, + &tag_chip, + <_chip, + )?; + offset += 1; + } + } + // pad two rows in the end to satisfy Halo2 cell assignment check + for _ in 0..2 { + self.assign_padding_row(&mut region, offset, &tag_chip)?; + offset += 1; + } + Ok(()) + }, + ) + } + + #[allow(clippy::too_many_arguments)] + fn assign_step( + &self, + region: &mut Region, + offset: usize, + randomness: F, + copy_event: &CopyEvent, + step_idx: usize, + copy_step: &CopyStep, + tag_chip: &BinaryNumberChip, + lt_chip: &LtChip, + ) -> Result<(), Error> { + // q_enable + region.assign_fixed(|| "q_enable", self.q_enable, offset, || Ok(F::one()))?; + // enable q_step on the Read step + if copy_step.rw.is_read() { + self.q_step.enable(region, offset)?; + } + + let id = if copy_step.rw.is_read() { + ©_event.src_id + } else { + ©_event.dst_id + }; + let bytes_left = copy_event.length - step_idx as u64 / 2; + + // is_first + region.assign_advice( + || format!("assign is_first {}", offset), + self.is_first, + offset, + || Ok(if step_idx == 0 { F::one() } else { F::zero() }), + )?; + // is_last + region.assign_advice( + || format!("assign is_last {}", offset), + self.is_last, + offset, + || { + Ok(if step_idx == copy_event.steps.len() - 1 { + F::one() + } else { + F::zero() + }) + }, + )?; + // id + region.assign_advice( + || format!("assign id {}", offset), + self.id, + offset, + || { + Ok(match id { + NumberOrHash::Number(n) => F::from(*n as u64), + NumberOrHash::Hash(h) => { + // since code hash in the bytecode table is represented in + // the little-endian form, we reverse the big-endian bytes + // of H256. + let le_bytes = { + let mut b = h.to_fixed_bytes(); + b.reverse(); + b + }; + RandomLinearCombination::random_linear_combine(le_bytes, randomness) + } + }) + }, + )?; + // addr + region.assign_advice( + || format!("assign addr {}", offset), + self.addr, + offset, + || { + Ok(match copy_step.tag { + CopyDataType::TxLog => { + let addr = (U256::from(copy_step.addr) + + (U256::from(TxLogFieldTag::Data as u64) << 32) + + (U256::from(copy_event.log_id.unwrap()) << 48)) + .to_address(); + addr.to_scalar().unwrap() + } + _ => F::from(copy_step.addr), + }) + }, + )?; + // value + region.assign_advice( + || format!("assign value {}", offset), + self.value, + offset, + || Ok(F::from(copy_step.value as u64)), + )?; + // is_code + region.assign_advice( + || format!("assign is_code {}", offset), + self.is_code, + offset, + || Ok(copy_step.is_code.map_or(F::zero(), |v| F::from(v))), + )?; + // is_pad + region.assign_advice( + || format!("assign is_pad {}", offset), + self.is_pad, + offset, + || Ok(F::from(copy_step.is_pad)), + )?; + // rw_counter + region.assign_advice( + || format!("assign rw_counter {}", offset), + self.rw_counter, + offset, + || Ok(F::from(copy_step.rwc.0 as u64)), + )?; + // rwc_inc_left + region.assign_advice( + || format!("assign rwc_inc_left {}", offset), + self.rwc_inc_left, + offset, + || Ok(F::from(copy_step.rwc_inc_left)), + )?; + // tag binary number chip + tag_chip.assign(region, offset, ©_step.tag)?; + // assignment for read steps + if copy_step.rw.is_read() { + // src_addr_end + region.assign_advice( + || format!("assign src_addr_end {}", offset), + self.src_addr_end, + offset, + || Ok(F::from(copy_event.src_addr_end)), + )?; + // bytes_left + region.assign_advice( + || format!("assign bytes_left {}", offset), + self.bytes_left, + offset, + || Ok(F::from(bytes_left)), + )?; + // lt chip + lt_chip.assign( + region, + offset, + F::from(copy_step.addr), + F::from(copy_event.src_addr_end), + )?; + } + Ok(()) + } + + fn assign_padding_row( + &self, + region: &mut Region, + offset: usize, + tag_chip: &BinaryNumberChip, + ) -> Result<(), Error> { + // q_enable + region.assign_fixed(|| "q_enable", self.q_enable, offset, || Ok(F::zero()))?; + // is_first + region.assign_advice( + || format!("assign is_first {}", offset), + self.is_first, + offset, + || Ok(F::zero()), + )?; + // is_last + region.assign_advice( + || format!("assign is_last {}", offset), + self.is_last, + offset, + || Ok(F::zero()), + )?; + // id + region.assign_advice( + || format!("assign id {}", offset), + self.id, + offset, + || Ok(F::zero()), + )?; + // addr + region.assign_advice( + || format!("assign addr {}", offset), + self.addr, + offset, + || Ok(F::zero()), + )?; + // src_addr_end + region.assign_advice( + || format!("assign src_addr_end {}", offset), + self.src_addr_end, + offset, + || Ok(F::zero()), + )?; + // bytes_left + region.assign_advice( + || format!("assign bytes_left {}", offset), + self.bytes_left, + offset, + || Ok(F::zero()), + )?; + // value + region.assign_advice( + || format!("assign value {}", offset), + self.value, + offset, + || Ok(F::zero()), + )?; + // is_code + region.assign_advice( + || format!("assign is_code {}", offset), + self.is_code, + offset, + || Ok(F::zero()), + )?; + // is_pad + region.assign_advice( + || format!("assign is_pad {}", offset), + self.is_pad, + offset, + || Ok(F::zero()), + )?; + // rw_counter + region.assign_advice( + || format!("assign rw_counter {}", offset), + self.rw_counter, + offset, + || Ok(F::zero()), + )?; + // rwc_inc_left + region.assign_advice( + || format!("assign rwc_inc_left {}", offset), + self.rwc_inc_left, + offset, + || Ok(F::zero()), + )?; + // tag + tag_chip.assign(region, offset, &CopyDataType::default())?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use bus_mapping::{ + circuit_input_builder::{CircuitInputBuilder, CopyDataType}, + mock::BlockData, + operation::RWCounter, + }; + use eth_types::{bytecode, geth_types::GethData, Field, Word}; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + dev::{MockProver, VerifyFailure}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, + }; + use itertools::Itertools; + use mock::TestContext; + use rand::{prelude::SliceRandom, Rng}; + + use crate::{ + evm_circuit::witness::{block_convert, Block, Bytecode, RwMap, Transaction}, + rw_table::RwTable, + }; + + use super::CopyCircuit; + + #[derive(Clone)] + struct MyConfig { + tx_table: [Column; 4], + rw_table: RwTable, + bytecode_table: [Column; 5], + copy_table: CopyCircuit, + } + + impl MyConfig { + fn load_txs( + &self, + layouter: &mut impl Layouter, + txs: &[Transaction], + randomness: F, + ) -> Result<(), Error> { + layouter.assign_region( + || "tx table", + |mut region| { + let mut offset = 0; + for column in self.tx_table { + region.assign_advice( + || "tx table all-zero row", + column, + offset, + || Ok(F::zero()), + )?; + } + offset += 1; + + for tx in txs.iter() { + for row in tx.table_assignments(randomness) { + for (column, value) in self.tx_table.iter().zip_eq(row) { + region.assign_advice( + || format!("tx table row {}", offset), + *column, + offset, + || Ok(value), + )?; + } + offset += 1; + } + } + Ok(()) + }, + ) + } + + fn load_rws( + &self, + layouter: &mut impl Layouter, + rws: &RwMap, + randomness: F, + ) -> Result<(), Error> { + layouter.assign_region( + || "rw table", + |mut region| { + let mut offset = 0; + self.rw_table + .assign(&mut region, offset, &Default::default())?; + offset += 1; + + let mut rows = rws + .0 + .values() + .flat_map(|rws| rws.iter()) + .collect::>(); + + rows.sort_by_key(|a| a.rw_counter()); + let mut expected_rw_counter = 1; + for rw in rows { + assert!(rw.rw_counter() == expected_rw_counter); + expected_rw_counter += 1; + + self.rw_table.assign( + &mut region, + offset, + &rw.table_assignment(randomness), + )?; + offset += 1; + } + Ok(()) + }, + ) + } + + fn load_bytecodes<'a>( + &self, + layouter: &mut impl Layouter, + bytecodes: impl IntoIterator + Clone, + randomness: F, + ) -> Result<(), Error> { + layouter.assign_region( + || "bytecode table", + |mut region| { + let mut offset = 0; + for column in self.bytecode_table { + region.assign_advice( + || "bytecode table all-zero row", + column, + offset, + || Ok(F::zero()), + )?; + } + offset += 1; + + for bytecode in bytecodes.clone() { + for row in bytecode.table_assignments(randomness) { + for (column, value) in self.bytecode_table.iter().zip_eq(row) { + region.assign_advice( + || format!("bytecode table row {}", offset), + *column, + offset, + || Ok(value), + )?; + } + offset += 1; + } + } + Ok(()) + }, + ) + } + } + + #[derive(Default)] + struct MyCircuit { + block: Block, + } + + impl MyCircuit { + pub fn new(block: Block) -> Self { + Self { block } + } + } + + impl Circuit for MyCircuit { + type Config = MyConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let tx_table = [(); 4].map(|_| meta.advice_column()); + let rw_table = RwTable::construct(meta); + let bytecode_table = [(); 5].map(|_| meta.advice_column()); + let copy_table = CopyCircuit::configure(meta, &tx_table, &rw_table, &bytecode_table); + + MyConfig { + tx_table, + rw_table, + bytecode_table, + copy_table, + } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), halo2_proofs::plonk::Error> { + config.load_txs(&mut layouter, &self.block.txs, self.block.randomness)?; + config.load_rws(&mut layouter, &self.block.rws, self.block.randomness)?; + config.load_bytecodes( + &mut layouter, + self.block.bytecodes.values(), + self.block.randomness, + )?; + config.copy_table.assign_block(&mut layouter, &self.block) + } + } + + fn run_circuit(k: u32, block: Block) -> Result<(), Vec> { + let circuit = MyCircuit::::new(block); + let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); + prover.verify() + } + + fn gen_calldatacopy_data() -> CircuitInputBuilder { + let code = bytecode! { + PUSH32(Word::from(0x20)) + PUSH32(Word::from(0x00)) + PUSH32(Word::from(0x00)) + CALLDATACOPY + STOP + }; + let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(code).unwrap(); + let block: GethData = test_ctx.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(); + builder + } + + fn gen_codecopy_data() -> CircuitInputBuilder { + let code = bytecode! { + PUSH32(Word::from(0x20)) + PUSH32(Word::from(0x00)) + PUSH32(Word::from(0x00)) + CODECOPY + STOP + }; + let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(code).unwrap(); + let block: GethData = test_ctx.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(); + builder + } + + #[test] + fn copy_circuit_valid_calldatacopy() { + let builder = gen_calldatacopy_data(); + let block = block_convert(&builder.block, &builder.code_db); + assert!(run_circuit(10, block).is_ok()); + } + + #[test] + fn copy_circuit_valid_codecopy() { + let builder = gen_codecopy_data(); + let block = block_convert(&builder.block, &builder.code_db); + assert!(run_circuit(10, block).is_ok()); + } + + fn perturb_tag(block: &mut bus_mapping::circuit_input_builder::Block, tag: CopyDataType) { + debug_assert!(!block.copy_events.is_empty()); + debug_assert!(!block.copy_events[0].steps.is_empty()); + + let mut rng = rand::thread_rng(); + let idxs = block.copy_events[0] + .steps + .iter() + .enumerate() + .filter(|(_i, step)| step.tag == tag) + .map(|(i, _step)| i) + .collect::>(); + let rand_idx = idxs.choose(&mut rng).unwrap(); + match rng.gen::() { + f if f < 0.25 => block.copy_events[0].steps[*rand_idx].addr = rng.gen(), + f if f < 0.5 => block.copy_events[0].steps[*rand_idx].value = rng.gen(), + f if f < 0.75 => block.copy_events[0].steps[*rand_idx].rwc = RWCounter(rng.gen()), + _ => block.copy_events[0].steps[*rand_idx].rwc_inc_left = rng.gen(), + } + } + + #[test] + fn copy_circuit_invalid_calldatacopy() { + let mut builder = gen_calldatacopy_data(); + match rand::thread_rng().gen_bool(0.5) { + true => perturb_tag(&mut builder.block, CopyDataType::Memory), + false => perturb_tag(&mut builder.block, CopyDataType::TxCalldata), + } + let block = block_convert(&builder.block, &builder.code_db); + assert!(run_circuit(10, block).is_err()); + } + + #[test] + fn copy_circuit_invalid_codecopy() { + let mut builder = gen_codecopy_data(); + match rand::thread_rng().gen_bool(0.5) { + true => perturb_tag(&mut builder.block, CopyDataType::Memory), + false => perturb_tag(&mut builder.block, CopyDataType::Bytecode), + } + let block = block_convert(&builder.block, &builder.code_db); + assert!(run_circuit(10, block).is_err()); + } +} diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 3f3b6793f7..262b3a57c2 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -34,10 +34,10 @@ impl EvmCircuit { rw_table: &dyn LookupTable, bytecode_table: &dyn LookupTable, block_table: &dyn LookupTable, + copy_table: &dyn LookupTable, ) -> Self { let fixed_table = [(); 4].map(|_| meta.fixed_column()); let byte_table = [(); 1].map(|_| meta.fixed_column()); - let execution = Box::new(ExecutionConfig::configure( meta, power_of_randomness, @@ -47,6 +47,7 @@ impl EvmCircuit { rw_table, bytecode_table, block_table, + copy_table, )); Self { @@ -104,7 +105,8 @@ impl EvmCircuit { layouter: &mut impl Layouter, block: &Block, ) -> Result<(), Error> { - self.execution.assign_block(layouter, block, false) + self.execution.assign_block(layouter, block, false)?; + Ok(()) } /// Assign exact steps in block without padding for unit test purpose @@ -114,7 +116,8 @@ impl EvmCircuit { layouter: &mut impl Layouter, block: &Block, ) -> Result<(), Error> { - self.execution.assign_block(layouter, block, true) + self.execution.assign_block(layouter, block, true)?; + Ok(()) } /// Calculate which rows are "actually" used in the circuit @@ -142,6 +145,7 @@ impl EvmCircuit { #[cfg(any(feature = "test", test))] pub mod test { use crate::{ + copy_circuit::CopyCircuit, evm_circuit::{ table::FixedTableTag, witness::{Block, BlockContext, Bytecode, RwMap, Transaction}, @@ -190,6 +194,7 @@ pub mod test { rw_table: RwTable, bytecode_table: [Column; 5], block_table: [Column; 3], + copy_table: CopyCircuit, evm_circuit: EvmCircuit, } @@ -374,6 +379,7 @@ pub mod test { let rw_table = RwTable::construct(meta); let bytecode_table = [(); 5].map(|_| meta.advice_column()); let block_table = [(); 3].map(|_| meta.advice_column()); + let copy_table = CopyCircuit::configure(meta, &tx_table, &rw_table, &bytecode_table); // This gate is used just to get the array of expressions from the power of // randomness instance column, so that later on we don't need to query @@ -398,6 +404,7 @@ pub mod test { rw_table, bytecode_table, block_table, + copy_table, evm_circuit: EvmCircuit::configure( meta, power_of_randomness, @@ -405,6 +412,7 @@ pub mod test { &rw_table, &bytecode_table, &block_table, + ©_table, ), } } @@ -426,6 +434,7 @@ pub mod test { self.block.randomness, )?; config.load_block(&mut layouter, &self.block.context, self.block.randomness)?; + config.copy_table.assign_block(&mut layouter, &self.block)?; config .evm_circuit .assign_block_exact(&mut layouter, &self.block) diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 47dbf81ccf..35bf947ad0 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -38,8 +38,6 @@ mod chainid; mod codecopy; mod codesize; mod comparator; -mod copy_code_to_memory; -mod copy_to_log; mod dummy; mod dup; mod end_block; @@ -54,7 +52,6 @@ mod jumpdest; mod jumpi; mod logs; mod memory; -mod memory_copy; mod msize; mod mul_div_mod; mod mulmod; @@ -90,8 +87,6 @@ use chainid::ChainIdGadget; use codecopy::CodeCopyGadget; use codesize::CodesizeGadget; use comparator::ComparatorGadget; -use copy_code_to_memory::CopyCodeToMemoryGadget; -use copy_to_log::CopyToLogGadget; use dummy::DummyGadget; use dup::DupGadget; use end_block::EndBlockGadget; @@ -106,7 +101,6 @@ use jumpdest::JumpdestGadget; use jumpi::JumpiGadget; use logs::LogGadget; use memory::MemoryGadget; -use memory_copy::CopyToMemoryGadget; use msize::MsizeGadget; use mul_div_mod::MulDivModGadget; use mulmod::MulModGadget; @@ -154,11 +148,10 @@ pub(crate) struct ExecutionConfig { q_step_last: Selector, advices: [Column; STEP_WIDTH], step: Step, - // internal state gadgets height_map: HashMap, stored_expressions_map: HashMap>>, + // internal state gadgets begin_tx_gadget: BeginTxGadget, - copy_to_memory_gadget: CopyToMemoryGadget, end_block_gadget: EndBlockGadget, end_tx_gadget: EndTxGadget, // opcode gadgets @@ -176,8 +169,6 @@ pub(crate) struct ExecutionConfig { codecopy_gadget: CodeCopyGadget, codesize_gadget: CodesizeGadget, comparator_gadget: ComparatorGadget, - copy_code_to_memory_gadget: CopyCodeToMemoryGadget, - copy_to_log_gadget: CopyToLogGadget, dup_gadget: DupGadget, extcodehash_gadget: ExtcodehashGadget, gas_gadget: GasGadget, @@ -241,6 +232,7 @@ impl ExecutionConfig { rw_table: &dyn LookupTable, bytecode_table: &dyn LookupTable, block_table: &dyn LookupTable, + copy_table: &dyn LookupTable, ) -> Self { let q_usable = meta.complex_selector(); let q_step = meta.advice_column(); @@ -371,9 +363,6 @@ impl ExecutionConfig { advices, // internal states begin_tx_gadget: configure_gadget!(), - copy_code_to_memory_gadget: configure_gadget!(), - copy_to_memory_gadget: configure_gadget!(), - copy_to_log_gadget: configure_gadget!(), end_block_gadget: configure_gadget!(), end_tx_gadget: configure_gadget!(), // opcode gadgets @@ -455,6 +444,7 @@ impl ExecutionConfig { rw_table, bytecode_table, block_table, + copy_table, &power_of_randomness, &cell_manager, ); @@ -576,9 +566,7 @@ impl ExecutionConfig { ), ]) .filter(move |(_, from, _)| *from == G::EXECUTION_STATE) - .map(|(_, _, to)| { - 1.expr() - step_next.execution_state_selector(to) - }), + .map(|(_, _, to)| 1.expr() - step_next.execution_state_selector(to)), ) .chain( IntoIterator::into_iter([ @@ -600,26 +588,19 @@ impl ExecutionConfig { ExecutionState::EndBlock, vec![ExecutionState::EndTx, ExecutionState::EndBlock], ), - ( - "Only ExecutionState which copies memory to memory can transit to CopyToMemory", - ExecutionState::CopyToMemory, - vec![ExecutionState::CopyToMemory, ExecutionState::CALLDATACOPY], - ), ]) .filter(move |(_, _, from)| !from.contains(&G::EXECUTION_STATE)) - .map(|(_, to, _)| { - step_next.execution_state_selector([to]) - }), + .map(|(_, to, _)| step_next.execution_state_selector([to])), ) // Accumulate all state transition checks. // This can be done because all summed values are enforced to be boolean. .reduce(|accum, poly| accum + poly) .map(move |poly| { - q_usable.clone() - * q_step.clone() - * (1.expr() - q_step_last.clone()) - * step_curr.execution_state_selector([G::EXECUTION_STATE]) - * poly + q_usable.clone() + * q_step.clone() + * (1.expr() - q_step_last.clone()) + * step_curr.execution_state_selector([G::EXECUTION_STATE]) + * poly }) }); @@ -635,6 +616,7 @@ impl ExecutionConfig { rw_table: &dyn LookupTable, bytecode_table: &dyn LookupTable, block_table: &dyn LookupTable, + copy_table: &dyn LookupTable, power_of_randomness: &[Expression; 31], cell_manager: &CellManager, ) { @@ -649,6 +631,7 @@ impl ExecutionConfig { Table::Bytecode => bytecode_table, Table::Block => block_table, Table::Byte => byte_table, + Table::Copy => copy_table, } .table_exprs(meta); vec![( @@ -830,9 +813,6 @@ impl ExecutionConfig { match step.execution_state { // internal states ExecutionState::BeginTx => assign_exec_step!(self.begin_tx_gadget), - ExecutionState::CopyCodeToMemory => assign_exec_step!(self.copy_code_to_memory_gadget), - ExecutionState::CopyToLog => assign_exec_step!(self.copy_to_log_gadget), - ExecutionState::CopyToMemory => assign_exec_step!(self.copy_to_memory_gadget), ExecutionState::EndTx => assign_exec_step!(self.end_tx_gadget), ExecutionState::EndBlock => assign_exec_step!(self.end_block_gadget), // opcode diff --git a/zkevm-circuits/src/evm_circuit/execution/add_sub.rs b/zkevm-circuits/src/evm_circuit/execution/add_sub.rs index 780735713c..47e68c7074 100644 --- a/zkevm-circuits/src/evm_circuit/execution/add_sub.rs +++ b/zkevm-circuits/src/evm_circuit/execution/add_sub.rs @@ -28,7 +28,7 @@ pub(crate) struct AddSubGadget { } impl ExecutionGadget for AddSubGadget { - const NAME: &'static str = "ADD"; + const NAME: &'static str = "ADD_SUB"; const EXECUTION_STATE: ExecutionState = ExecutionState::ADD_SUB; diff --git a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs index 327279607d..2bad1c1bc7 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs @@ -12,13 +12,13 @@ use crate::{ }, from_bytes, memory_gadget::{MemoryAddressGadget, MemoryCopierGasGadget, MemoryExpansionGadget}, - CachedRegion, Cell, MemoryAddress, + not, select, CachedRegion, Cell, MemoryAddress, }, witness::{Block, Call, ExecStep, Transaction}, }, util::Expr, }; -use bus_mapping::evm::OpcodeId; +use bus_mapping::{circuit_input_builder::CopyDataType, evm::OpcodeId}; use eth_types::Field; use eth_types::ToLittleEndian; use halo2_proofs::plonk::Error; @@ -33,6 +33,7 @@ pub(crate) struct CallDataCopyGadget { src_id: Cell, call_data_length: Cell, call_data_offset: Cell, // Only used in the internal call + copy_rwc_inc: Cell, memory_expansion: MemoryExpansionGadget, memory_copier_gas: MemoryCopierGasGadget, } @@ -108,50 +109,37 @@ impl ExecutionGadget for CallDataCopyGadget { memory_expansion.gas_cost(), ); - // Constrain the next step CopyToMemory if length != 0 - cb.constrain_next_step( - ExecutionState::CopyToMemory, - Some(memory_address.has_length()), - |cb| { - let next_src_addr = cb.query_cell(); - let next_dst_addr = cb.query_cell(); - let next_bytes_left = cb.query_cell(); - let next_src_addr_end = cb.query_cell(); - let next_from_tx = cb.query_cell(); - let next_src_id = cb.query_cell(); - cb.require_equal( - "next_src_addr = data_offset + call_data_offset", - next_src_addr.expr(), - from_bytes::expr(&data_offset.cells) + call_data_offset.expr(), - ); - cb.require_equal( - "next_dst_addr = memory_offset", - next_dst_addr.expr(), - memory_address.offset(), - ); - cb.require_equal( - "next_bytes_left = length", - next_bytes_left.expr(), - memory_address.length(), - ); - cb.require_equal( - "next_src_addr_end = call_data_length + call_data_offset", - next_src_addr_end.expr(), - call_data_length.expr() + call_data_offset.expr(), - ); - cb.require_equal( - "next_from_tx = is_root", - next_from_tx.expr(), - cb.curr.state.is_root.expr(), - ); - cb.require_equal("next_src_id = src_id", next_src_id.expr(), src_id.expr()); - }, + let copy_rwc_inc = cb.query_cell(); + let src_tag = select::expr( + cb.curr.state.is_root.expr(), + CopyDataType::TxCalldata.expr(), + CopyDataType::Memory.expr(), ); + cb.condition(memory_address.has_length(), |cb| { + cb.copy_table_lookup( + src_id.expr(), + src_tag, + cb.curr.state.call_id.expr(), + CopyDataType::Memory.expr(), + call_data_offset.expr() + from_bytes::expr(&data_offset.cells), + call_data_offset.expr() + call_data_length.expr(), + memory_address.offset(), + memory_address.length(), + cb.curr.state.rw_counter.expr() + cb.rw_counter_offset().expr(), + copy_rwc_inc.expr(), + ); + }); + cb.condition(not::expr(memory_address.has_length()), |cb| { + cb.require_zero( + "if no bytes to copy, copy table rwc inc == 0", + copy_rwc_inc.expr(), + ); + }); // State transition let step_state_transition = StepStateTransition { // 1 tx id lookup + 3 stack pop + option(calldatalength lookup) - rw_counter: Delta(cb.rw_counter_offset()), + rw_counter: Delta(cb.rw_counter_offset() + copy_rwc_inc.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta(3.expr()), gas_left: Delta( @@ -169,6 +157,7 @@ impl ExecutionGadget for CallDataCopyGadget { src_id, call_data_length, call_data_offset, + copy_rwc_inc, memory_expansion, memory_copier_gas, } @@ -215,6 +204,17 @@ impl ExecutionGadget for CallDataCopyGadget { self.call_data_offset .assign(region, offset, Some(F::from(call_data_offset as u64)))?; + let key = (tx.id, call.id, step.program_counter as usize); + let copy_rwc_inc = block + .copy_events + .get(&key) + .unwrap() + .steps + .first() + .map_or(F::zero(), |cs| F::from(cs.rwc_inc_left)); + self.copy_rwc_inc + .assign(region, offset, Some(copy_rwc_inc))?; + // Memory expansion let (_, memory_expansion_gas_cost) = self.memory_expansion.assign( region, @@ -278,13 +278,13 @@ mod test { call_data_length: usize, dst_offset: usize, offset: usize, - copy_size: usize, + length: usize, ) { let (addr_a, addr_b) = (mock::MOCK_ACCOUNTS[0], mock::MOCK_ACCOUNTS[1]); // code B gets called by code A, so the call is an internal call. let code_b = bytecode! { - PUSH32(copy_size) // size + PUSH32(length) // size PUSH32(offset) // offset PUSH32(dst_offset) // dst_offset CALLDATACOPY @@ -301,8 +301,8 @@ mod test { // call ADDR_B. PUSH1(0x00) // retLength PUSH1(0x00) // retOffset - PUSH1(call_data_length) // argsLength - PUSH1(call_data_offset) // argsOffset + PUSH32(call_data_length) // argsLength + PUSH32(call_data_offset) // argsOffset PUSH1(0x00) // value PUSH32(addr_b.to_word()) // addr PUSH32(0x1_0000) // gas @@ -331,25 +331,25 @@ mod test { #[test] fn calldatacopy_gadget_simple() { - test_ok_root(0x40, 0x40, 0x00, 0x0A); - test_ok_internal(0x40, 0x40, 0xA0, 0x10, 0x0A); + test_ok_root(0x40, 0x40, 0x00, 10); + test_ok_internal(0x40, 0x40, 0xA0, 0x10, 10); } #[test] - fn calldatacopy_gadget_multi_step() { - test_ok_root(0x80, 0x40, 0x10, 0x5A); - test_ok_internal(0x30, 0x70, 0x20, 0x10, 0x5A); + fn calldatacopy_gadget_large() { + test_ok_root(0x204, 0x103, 0x102, 0x101); + test_ok_internal(0x30, 0x204, 0x103, 0x102, 0x101); } #[test] fn calldatacopy_gadget_out_of_bound() { - test_ok_root(0x40, 0x40, 0x20, 0x28); - test_ok_internal(0x40, 0x20, 0xA0, 0x28, 0x0A); + test_ok_root(0x40, 0x40, 0x20, 40); + test_ok_internal(0x40, 0x20, 0xA0, 0x28, 10); } #[test] fn calldatacopy_gadget_zero_length() { - test_ok_root(0x40, 0x40, 0x00, 0x00); - test_ok_internal(0x40, 0x40, 0xA0, 0x10, 0x00); + test_ok_root(0x40, 0x40, 0x00, 0); + test_ok_internal(0x40, 0x40, 0xA0, 0x10, 0); } } diff --git a/zkevm-circuits/src/evm_circuit/execution/codecopy.rs b/zkevm-circuits/src/evm_circuit/execution/codecopy.rs index 063e4e6eee..c484d2fdb8 100644 --- a/zkevm-circuits/src/evm_circuit/execution/codecopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/codecopy.rs @@ -1,6 +1,6 @@ use std::convert::TryInto; -use bus_mapping::evm::OpcodeId; +use bus_mapping::{circuit_input_builder::CopyDataType, evm::OpcodeId}; use eth_types::{Field, ToLittleEndian}; use halo2_proofs::plonk::Error; @@ -13,7 +13,7 @@ use crate::{ constraint_builder::{ConstraintBuilder, StepStateTransition, Transition}, from_bytes, memory_gadget::{MemoryAddressGadget, MemoryCopierGasGadget, MemoryExpansionGadget}, - CachedRegion, Cell, MemoryAddress, + not, CachedRegion, Cell, MemoryAddress, }, witness::{Block, Call, ExecStep, Transaction}, }, @@ -38,6 +38,9 @@ pub(crate) struct CodeCopyGadget { /// Opcode CODECOPY needs to copy code bytes into memory. We account for /// the copying costs using the memory copier gas gadget. memory_copier_gas: MemoryCopierGasGadget, + /// RW inverse counter from the copy table at the start of related copy + /// steps. + copy_rwc_inc: Cell, } impl ExecutionGadget for CodeCopyGadget { @@ -49,17 +52,17 @@ impl ExecutionGadget for CodeCopyGadget { let opcode = cb.query_cell(); // Query elements to be popped from the stack. - let dest_memory_offset = cb.query_cell(); + let dst_memory_offset = cb.query_cell(); let code_offset = cb.query_rlc(); let size = cb.query_rlc(); // Pop items from stack. - cb.stack_pop(dest_memory_offset.expr()); + cb.stack_pop(dst_memory_offset.expr()); cb.stack_pop(code_offset.expr()); cb.stack_pop(size.expr()); // Construct memory address in the destionation (memory) to which we copy code. - let dst_memory_addr = MemoryAddressGadget::construct(cb, dest_memory_offset, size.clone()); + let dst_memory_addr = MemoryAddressGadget::construct(cb, dst_memory_offset, size); // Fetch the hash of bytecode running in current environment. let code_hash = cb.curr.state.code_hash.clone(); @@ -81,49 +84,31 @@ impl ExecutionGadget for CodeCopyGadget { memory_expansion.gas_cost(), ); - // Constrain the next step to be the internal `CopyCodeToMemory` step and add - // some preliminary checks on its auxiliary data. - cb.constrain_next_step( - ExecutionState::CopyCodeToMemory, - Some(dst_memory_addr.has_length()), - |cb| { - let next_src_addr = cb.query_cell(); - let next_dst_addr = cb.query_cell(); - let next_bytes_left = cb.query_cell(); - let next_src_addr_end = cb.query_cell(); - let next_code_hash = cb.query_word(); - - cb.require_equal( - "next_src_addr == code_offset", - next_src_addr.expr(), - from_bytes::expr(&code_offset.cells), - ); - cb.require_equal( - "next_dst_addr = memory_offset", - next_dst_addr.expr(), - dst_memory_addr.offset(), - ); - cb.require_equal( - "next_bytes_left = length", - next_bytes_left.expr(), - size.expr(), - ); - cb.require_equal( - "next_src_addr_end == code_size", - next_src_addr_end.expr(), - code_size.expr(), - ); - cb.require_equal( - "next_code_hash == code_hash", - next_code_hash.expr(), - code_hash.expr(), - ); - }, - ); + let copy_rwc_inc = cb.query_cell(); + cb.condition(dst_memory_addr.has_length(), |cb| { + cb.copy_table_lookup( + code_hash.expr(), + CopyDataType::Bytecode.expr(), + cb.curr.state.call_id.expr(), + CopyDataType::Memory.expr(), + from_bytes::expr(&code_offset.cells), + code_size.expr(), + dst_memory_addr.offset(), + dst_memory_addr.length(), + cb.curr.state.rw_counter.expr() + cb.rw_counter_offset().expr(), + copy_rwc_inc.expr(), + ); + }); + cb.condition(not::expr(dst_memory_addr.has_length()), |cb| { + cb.require_zero( + "if no bytes to copy, copy table rwc inc == 0", + copy_rwc_inc.expr(), + ); + }); // Expected state transition. let step_state_transition = StepStateTransition { - rw_counter: Transition::Delta(cb.rw_counter_offset()), + rw_counter: Transition::Delta(cb.rw_counter_offset() + copy_rwc_inc.expr()), program_counter: Transition::Delta(1.expr()), stack_pointer: Transition::Delta(3.expr()), memory_word_size: Transition::To(memory_expansion.next_memory_word_size()), @@ -141,6 +126,7 @@ impl ExecutionGadget for CodeCopyGadget { dst_memory_addr, memory_expansion, memory_copier_gas, + copy_rwc_inc, } } @@ -149,7 +135,7 @@ impl ExecutionGadget for CodeCopyGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, - _tx: &Transaction, + tx: &Transaction, call: &Call, step: &ExecStep, ) -> Result<(), Error> { @@ -197,6 +183,17 @@ impl ExecutionGadget for CodeCopyGadget { self.memory_copier_gas .assign(region, offset, size.as_u64(), memory_expansion_cost)?; + let key = (tx.id, call.id, step.program_counter as usize); + let copy_rwc_inc = block + .copy_events + .get(&key) + .unwrap() + .steps + .first() + .map_or(F::zero(), |cs| F::from(cs.rwc_inc_left)); + self.copy_rwc_inc + .assign(region, offset, Some(copy_rwc_inc))?; + Ok(()) } } @@ -211,8 +208,8 @@ mod tests { fn test_ok(memory_offset: usize, code_offset: usize, size: usize, large: bool) { let mut code = bytecode! {}; if large { - for _ in 0..128 { - code.push(1, Word::from(0)); + for _ in 0..0x101 { + code.push(1, Word::from(123)); } } let tail = bytecode! { @@ -234,7 +231,7 @@ mod tests { } #[test] - fn codecopy_gadget() { + fn codecopy_gadget_simple() { test_ok(0x00, 0x00, 0x20, false); test_ok(0x20, 0x30, 0x30, false); test_ok(0x10, 0x20, 0x42, false); @@ -242,6 +239,6 @@ mod tests { #[test] fn codecopy_gadget_large() { - test_ok(0x00, 0x00, 0x40, true); + test_ok(0x103, 0x102, 0x101, true); } } diff --git a/zkevm-circuits/src/evm_circuit/execution/copy_code_to_memory.rs b/zkevm-circuits/src/evm_circuit/execution/copy_code_to_memory.rs deleted file mode 100644 index 488b201133..0000000000 --- a/zkevm-circuits/src/evm_circuit/execution/copy_code_to_memory.rs +++ /dev/null @@ -1,472 +0,0 @@ -use array_init::array_init; -use bus_mapping::{circuit_input_builder::CopyDetails, constants::MAX_COPY_BYTES}; -use eth_types::{Field, ToLittleEndian}; -use halo2_proofs::plonk::Error; - -use crate::{ - evm_circuit::{ - param::{N_BYTES_MEMORY_ADDRESS, N_BYTES_MEMORY_WORD_SIZE}, - step::ExecutionState, - util::{ - constraint_builder::{ConstraintBuilder, StepStateTransition, Transition}, - math_gadget::ComparisonGadget, - memory_gadget::BufferReaderGadget, - CachedRegion, Cell, Word, - }, - witness::{Block, Call, ExecStep, Transaction}, - }, - util::Expr, -}; - -use super::ExecutionGadget; - -#[derive(Clone, Debug)] -/// This gadget is responsible for copying bytes from an account's code to -/// memory. This is an internal gadget used by the `CodeCopyGadget`. -pub(crate) struct CopyCodeToMemoryGadget { - /// Offset in the source (bytecode) to read from. - src_addr: Cell, - /// Offset in the destination (memory) to write to. - dst_addr: Cell, - /// Number of bytes left to be copied in this iteration. - bytes_left: Cell, - /// Source (bytecode) bytes end here. - src_addr_end: Cell, - /// Keccak-256 hash of the bytecode source. - code_hash: Word, - /// Array of booleans to mark whether or not the byte in question is an - /// opcode byte or an argument that follows the opcode. For example, - /// `is_code = true` for `POP`, `is_code = true` for `PUSH32`, but - /// `is_code = false` for the 32 bytes that follow the `PUSH32` opcode. - is_codes: [Cell; MAX_COPY_BYTES], - /// Gadget to assign bytecode to buffer and read byte-by-byte. - buffer_reader: BufferReaderGadget, - /// Comparison gadget to conditionally stop this iterative internal step - /// once all the bytes have been copied. - finish_gadget: ComparisonGadget, -} - -impl ExecutionGadget for CopyCodeToMemoryGadget { - const NAME: &'static str = "COPYCODETOMEMORY"; - - const EXECUTION_STATE: ExecutionState = ExecutionState::CopyCodeToMemory; - - fn configure(cb: &mut ConstraintBuilder) -> Self { - // Query cells for the internal step's auxiliary data and construct the buffer - // reader. - let src_addr = cb.query_cell(); - let dst_addr = cb.query_cell(); - let bytes_left = cb.query_cell(); - let src_addr_end = cb.query_cell(); - let code_hash = cb.query_word(); - let is_codes = array_init(|_| cb.query_bool()); - let buffer_reader = BufferReaderGadget::construct(cb, src_addr.expr(), src_addr_end.expr()); - - // For every byte in the bytecode's span covered in this iteration. - for (idx, is_code) in is_codes.iter().enumerate() { - // Lookup the bytecode table for the byte value read at the appropriate source - // memory address from the buffer. - cb.condition(buffer_reader.read_flag(idx), |cb| { - cb.bytecode_lookup( - code_hash.expr(), - src_addr.expr() + idx.expr(), - is_code.expr(), - buffer_reader.byte(idx), - ); - }); - // Lookup the RW table for a memory write operation at the appropriate - // destination memory address. - cb.condition(buffer_reader.has_data(idx), |cb| { - cb.memory_lookup( - 1.expr(), - dst_addr.expr() + idx.expr(), - buffer_reader.byte(idx), - None, - ); - }); - } - - // Construct the comparison gadget using the number of bytes copied in this - // iteration and the number bytes that were left to be copied before the - // start of this iteration. - let copied_size = buffer_reader.num_bytes(); - let finish_gadget = ComparisonGadget::construct(cb, copied_size.clone(), bytes_left.expr()); - let (lt, finished) = finish_gadget.expr(); - - // We should have continued only until there were no more bytes left to be - // copied. In case the copied size was less than the number of bytes - // left, the iterative process should not be finished. - cb.add_constraint( - "Constrain num_bytes <= bytes_left", - (1.expr() - lt) * (1.expr() - finished.clone()), - ); - - // If the iterative process has not yet finished, we constrain the next step to - // be another `CopyCodeToMemory` while adding some additional - // constraints to the auxiliary data. - cb.constrain_next_step( - ExecutionState::CopyCodeToMemory, - Some(1.expr() - finished), - |cb| { - let next_src_addr = cb.query_cell(); - let next_dst_addr = cb.query_cell(); - let next_bytes_left = cb.query_cell(); - let next_src_addr_end = cb.query_cell(); - let next_code_hash = cb.query_word(); - - cb.require_equal( - "next_src_addr == src_addr + copied_size", - next_src_addr.expr(), - src_addr.expr() + copied_size.clone(), - ); - cb.require_equal( - "dst_addr + copied_size == next_dst_addr", - next_dst_addr.expr(), - dst_addr.expr() + copied_size.clone(), - ); - cb.require_equal( - "next_bytes_left == bytes_left - copied_size", - next_bytes_left.expr(), - bytes_left.expr() - copied_size.clone(), - ); - cb.require_equal( - "next_src_addr_end == src_addr_end", - next_src_addr_end.expr(), - src_addr_end.expr(), - ); - cb.require_equal( - "next_code_hash == code_hash", - next_code_hash.expr(), - code_hash.expr(), - ); - }, - ); - - // Since this is an internal step for `CODECOPY` opcode, we only increment the - // RW counter. The program counter, stack pointer, and other fields do - // not change. - let step_state_transition = StepStateTransition { - rw_counter: Transition::Delta(cb.rw_counter_offset()), - ..Default::default() - }; - cb.require_step_state_transition(step_state_transition); - - Self { - src_addr, - dst_addr, - bytes_left, - src_addr_end, - code_hash, - is_codes, - buffer_reader, - finish_gadget, - } - } - - fn assign_exec_step( - &self, - region: &mut CachedRegion<'_, '_, F>, - offset: usize, - block: &Block, - _tx: &Transaction, - _call: &Call, - step: &ExecStep, - ) -> Result<(), Error> { - // Read the auxiliary data. - let aux = if step.aux_data.is_none() { - // TODO: Handle error correctly returning err - unreachable!("could not find aux_data for this step") - } else { - step.aux_data.unwrap() - }; - - let code_hash = match aux.copy_details() { - CopyDetails::Code(code) => code, - _ => unreachable!("the source has to come from code not calldata"), - }; - - let code = block - .bytecodes - .get(&code_hash) - .unwrap_or_else(|| panic!("could not find bytecode with hash={:?}", code_hash)); - // Assign to the appropriate cells. - self.src_addr - .assign(region, offset, Some(F::from(aux.src_addr())))?; - self.dst_addr - .assign(region, offset, Some(F::from(aux.dst_addr())))?; - self.bytes_left - .assign(region, offset, Some(F::from(aux.bytes_left())))?; - self.src_addr_end - .assign(region, offset, Some(F::from(aux.src_addr_end())))?; - self.code_hash - .assign(region, offset, Some(code.hash.to_le_bytes()))?; - - // Initialise selectors and bytes for the buffer reader. - let mut selectors = vec![false; MAX_COPY_BYTES]; - let mut bytes = vec![0u8; MAX_COPY_BYTES]; - let is_codes = code - .table_assignments(block.randomness) - .iter() - .skip(1) - .map(|c| c[3]) - .collect::>(); - for idx in 0..std::cmp::min(aux.bytes_left() as usize, MAX_COPY_BYTES) { - selectors[idx] = true; - let addr = aux.src_addr() as usize + idx; - bytes[idx] = if addr < aux.src_addr_end() as usize { - assert!(addr < code.bytes.len()); - self.is_codes[idx].assign(region, offset, Some(is_codes[addr]))?; - code.bytes[addr] - } else { - 0 - }; - } - - self.buffer_reader.assign( - region, - offset, - aux.src_addr(), - aux.src_addr_end(), - &bytes, - &selectors, - )?; - - // The number of bytes copied here will be the sum of 1s over the selector - // vector. - let num_bytes_copied = std::cmp::min(aux.bytes_left(), MAX_COPY_BYTES as u64); - - // Assign the comparison gadget. - self.finish_gadget.assign( - region, - offset, - F::from(num_bytes_copied), - F::from(aux.bytes_left()), - )?; - - Ok(()) - } -} - -#[cfg(test)] -pub(crate) mod test { - use super::MAX_COPY_BYTES; - use std::collections::HashMap; - - use bus_mapping::{ - circuit_input_builder::{CopyDetails, StepAuxiliaryData}, - evm::OpcodeId, - }; - use eth_types::{bytecode, Word}; - use halo2_proofs::arithmetic::BaseExt; - use halo2_proofs::pairing::bn256::Fr; - - use crate::evm_circuit::{ - step::ExecutionState, - table::RwTableTag, - test::run_test_circuit_incomplete_fixed_table, - witness::{Block, Bytecode, Call, ExecStep, Rw, RwMap, Transaction}, - }; - - #[allow(clippy::too_many_arguments)] - pub(crate) fn make_copy_code_step( - call_id: usize, - src_addr: u64, - dst_addr: u64, - src_addr_end: u64, - bytes_left: usize, - program_counter: u64, - stack_pointer: usize, - memory_size: u64, - rw_counter: usize, - rws: &mut RwMap, - bytes_map: &HashMap, - code: &Bytecode, - ) -> (ExecStep, usize) { - let mut rw_offset = 0usize; - let memory_rws: &mut Vec<_> = rws.0.entry(RwTableTag::Memory).or_insert_with(Vec::new); - let rw_idx_start = memory_rws.len(); - - for idx in 0..std::cmp::min(bytes_left, MAX_COPY_BYTES) { - let addr = src_addr + idx as u64; - let byte = if addr < src_addr_end { - assert!(bytes_map.contains_key(&addr)); - bytes_map[&addr] - } else { - 0 - }; - memory_rws.push(Rw::Memory { - rw_counter: rw_counter + rw_offset, - is_write: true, - call_id, - memory_address: dst_addr + idx as u64, - byte, - }); - rw_offset += 1; - } - - let rw_idx_end = rws.0[&RwTableTag::Memory].len(); - let aux_data = StepAuxiliaryData::new( - src_addr, - dst_addr, - bytes_left as u64, - src_addr_end, - CopyDetails::Code(code.hash), - ); - let step = ExecStep { - execution_state: ExecutionState::CopyCodeToMemory, - rw_indices: (rw_idx_start..rw_idx_end) - .map(|idx| (RwTableTag::Memory, idx)) - .collect(), - rw_counter, - program_counter, - stack_pointer, - memory_size, - gas_cost: 0, - aux_data: Some(aux_data), - ..Default::default() - }; - - (step, rw_offset) - } - - #[allow(clippy::too_many_arguments)] - pub(crate) fn make_copy_code_steps( - call_id: usize, - code: &Bytecode, - src_addr: u64, - dst_addr: u64, - length: usize, - program_counter: u64, - stack_pointer: usize, - memory_size: u64, - rw_counter: &mut usize, - rws: &mut RwMap, - steps: &mut Vec, - ) { - let bytes_map = (0..(code.bytes.len() as u64)) - .zip(code.bytes.iter().copied()) - .collect(); - - let mut copied = 0; - while copied < length { - let (step, rw_offset) = make_copy_code_step( - call_id, - src_addr + copied as u64, - dst_addr + copied as u64, - code.bytes.len() as u64, - length - copied, - program_counter, - stack_pointer, - memory_size, - *rw_counter, - rws, - &bytes_map, - code, - ); - steps.push(step); - *rw_counter += rw_offset; - copied += MAX_COPY_BYTES; - } - } - - fn test_ok(src_addr: u64, dst_addr: u64, length: usize) { - let randomness = Fr::rand(); - let call_id = 1; - let mut rws = RwMap::default(); - let mut rw_counter = 1; - let mut steps = Vec::new(); - let memory_size = (dst_addr + length as u64 + 31) / 32 * 32; - - // generate random bytecode longer than `src_addr_end` - let code = bytecode! { - PUSH32(Word::from(0x123)) - POP - PUSH32(Word::from(0x213)) - POP - PUSH32(Word::from(0x321)) - POP - PUSH32(Word::from(0x12349AB)) - POP - PUSH32(Word::from(0x1928835)) - POP - }; - - let code = Bytecode::new(code.to_vec()); - let dummy_code = Bytecode::new(vec![OpcodeId::RETURN.as_u8()]); - - let program_counter = 0; - let stack_pointer = 1024; - make_copy_code_steps( - call_id, - &code, - src_addr, - dst_addr, - length, - program_counter, - stack_pointer, - memory_size, - &mut rw_counter, - &mut rws, - &mut steps, - ); - - steps.push(ExecStep { - execution_state: ExecutionState::RETURN, - rw_counter, - program_counter, - stack_pointer, - memory_size, - opcode: Some(OpcodeId::RETURN), - ..Default::default() - }); - - let block = Block { - randomness, - txs: vec![Transaction { - id: 1, - calls: vec![Call { - id: call_id, - is_root: true, - is_create: false, - code_hash: dummy_code.hash, - ..Default::default() - }], - steps, - ..Default::default() - }], - rws, - bytecodes: HashMap::from_iter([(dummy_code.hash, dummy_code), (code.hash, code)]), - ..Default::default() - }; - assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); - } - - #[test] - fn copy_code_to_memory_single_step() { - test_ok( - 0x00, // src_addr - 0x00, // dst_addr - 54, // length - ); - } - - #[test] - fn copy_code_to_memory_multi_step() { - test_ok( - 0x00, // src_addr - 0x40, // dst_addr - 123, // length - ); - } - - #[test] - fn copy_code_to_memory_oob() { - // since the bytecode we construct above is (34 * 5) = 170 bytes long, copying - // 200 bytes means we go out-of-bounds. - test_ok( - 0x10, // src_addr - 0x20, // dst_addr - 200, // length - ); - } -} diff --git a/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs b/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs deleted file mode 100644 index d90bba5b9d..0000000000 --- a/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs +++ /dev/null @@ -1,463 +0,0 @@ -use crate::{ - evm_circuit::{ - execution::ExecutionGadget, - param::{N_BYTES_MEMORY_ADDRESS, N_BYTES_MEMORY_WORD_SIZE}, - step::ExecutionState, - table::TxLogFieldTag, - util::{ - constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, - math_gadget::ComparisonGadget, - memory_gadget::BufferReaderGadget, - CachedRegion, Cell, - }, - witness::{Block, Call, ExecStep, Transaction}, - }, - util::Expr, -}; -use bus_mapping::{circuit_input_builder::CopyDetails, constants::MAX_COPY_BYTES}; -use eth_types::Field; -use halo2_proofs::plonk::Error; - -/// Multi-step gadget for copying data from memory to RW log -#[derive(Clone, Debug)] -pub(crate) struct CopyToLogGadget { - // The src memory address to copy from - src_addr: Cell, - // The number of bytes left to copy - bytes_left: Cell, - // The src address bound of the buffer - src_addr_end: Cell, - // the call is persistent or not - is_persistent: Cell, - // The tx id - tx_id: Cell, - // the log data start index fetched from last step - data_start_index: Cell, - // Buffer reader gadget - buffer_reader: BufferReaderGadget, - // The comparison gadget between num bytes copied and bytes_left - finish_gadget: ComparisonGadget, -} - -impl ExecutionGadget for CopyToLogGadget { - const NAME: &'static str = "CopyToLogGadget"; - - const EXECUTION_STATE: ExecutionState = ExecutionState::CopyToLog; - - fn configure(cb: &mut ConstraintBuilder) -> Self { - let src_addr = cb.query_cell(); - let bytes_left = cb.query_cell(); - let src_addr_end = cb.query_cell(); - let is_persistent = cb.query_bool(); - let tx_id = cb.query_cell(); - let data_start_index = cb.query_cell(); - let buffer_reader = BufferReaderGadget::construct(cb, src_addr.expr(), src_addr_end.expr()); - - // Copy bytes from src and dst - for i in 0..MAX_COPY_BYTES { - let read_flag = buffer_reader.read_flag(i); - // Read bytes[i] from memory - cb.condition(read_flag.clone(), |cb| { - cb.memory_lookup( - 0.expr(), - src_addr.expr() + i.expr(), - buffer_reader.byte(i), - None, - ) - }); - - // Write bytes[i] to log when selectors[i] != 0 and is_persistent = true - cb.condition(buffer_reader.has_data(i) * is_persistent.expr(), |cb| { - cb.tx_log_lookup( - tx_id.expr(), - cb.curr.state.log_id.expr(), - TxLogFieldTag::Data, - data_start_index.expr() + i.expr(), - buffer_reader.byte(i), - ) - }); - } - - let copied_size = buffer_reader.num_bytes(); - let finish_gadget = ComparisonGadget::construct(cb, copied_size.clone(), bytes_left.expr()); - let (lt, finished) = finish_gadget.expr(); - // Constrain lt == 1 or finished == 1 - cb.add_constraint( - "Constrain num_bytes <= bytes_left", - (1.expr() - lt) * (1.expr() - finished.clone()), - ); - - // When finished == 0, constraint the CopyToLog state in next step - cb.constrain_next_step(ExecutionState::CopyToLog, Some(1.expr() - finished), |cb| { - let next_src_addr = cb.query_cell(); - let next_bytes_left = cb.query_cell(); - let next_src_addr_end = cb.query_cell(); - let next_is_persistent = cb.query_bool(); - let next_tx_id = cb.query_cell(); - let next_data_start_index = cb.query_cell(); - - cb.require_equal( - "next_src_addr == src_addr + copied_size", - next_src_addr.expr(), - src_addr.expr() + copied_size.clone(), - ); - - cb.require_equal( - "next_bytes_left == bytes_left - copied_size", - next_bytes_left.expr(), - bytes_left.expr() - copied_size.clone(), - ); - cb.require_equal( - "next_src_addr_end == src_addr_end", - next_src_addr_end.expr(), - src_addr_end.expr(), - ); - cb.require_equal( - "next_is_persistent == is_persistent", - next_is_persistent.expr(), - is_persistent.expr(), - ); - cb.require_equal("next_tx_id == tx_id", next_tx_id.expr(), tx_id.expr()); - cb.require_equal( - "next_data_start_index == data_start_index + MAX_COPY_BYTES - ", - next_data_start_index.expr(), - data_start_index.expr() + MAX_COPY_BYTES.expr(), - ); - }); - - // State transition - let step_state_transition = StepStateTransition { - rw_counter: Delta(cb.rw_counter_offset()), - ..Default::default() - }; - cb.require_step_state_transition(step_state_transition); - - Self { - src_addr, - bytes_left, - src_addr_end, - is_persistent, - tx_id, - data_start_index, - buffer_reader, - finish_gadget, - } - } - - fn assign_exec_step( - &self, - region: &mut CachedRegion<'_, '_, F>, - offset: usize, - block: &Block, - _: &Transaction, - _: &Call, - step: &ExecStep, - ) -> Result<(), Error> { - // Read the auxiliary data. - let aux = if step.aux_data.is_none() { - // TODO: Handle error correctly returning err - unreachable!("could not find aux_data for this step") - } else { - step.aux_data.unwrap() - }; - - // log won't use dst_addr - assert!(aux.dst_addr() == 0u64); - let (is_persistent, tx_id, data_start_index) = match aux.copy_details() { - CopyDetails::Log((is_persistent, tx_id, data_index)) => { - (is_persistent, tx_id, data_index) - } - _ => unreachable!("the data copy is not related to a LOG op"), - }; - - self.src_addr - .assign(region, offset, Some(F::from(aux.src_addr())))?; - self.bytes_left - .assign(region, offset, Some(F::from(aux.bytes_left())))?; - self.src_addr_end - .assign(region, offset, Some(F::from(aux.src_addr_end())))?; - self.is_persistent - .assign(region, offset, Some(F::from(is_persistent as u64)))?; - self.tx_id - .assign(region, offset, Some(F::from(tx_id as u64)))?; - self.data_start_index - .assign(region, offset, Some(F::from(data_start_index as u64)))?; - // Retrieve the bytes and selectors - - let mut rw_idx = 0; - let mut bytes = vec![0u8; MAX_COPY_BYTES]; - let mut selectors = vec![false; MAX_COPY_BYTES]; - - for idx in 0..std::cmp::min(aux.bytes_left() as usize, MAX_COPY_BYTES) { - let src_addr = aux.src_addr() as usize + idx; - selectors[idx] = true; - bytes[idx] = if selectors[idx] && src_addr < aux.src_addr_end() as usize { - let index = step.rw_indices[rw_idx]; - if is_persistent { - rw_idx += 2; - } else { - rw_idx += 1; - } - - block.rws[index].memory_value() - } else { - 0 - }; - } - - self.buffer_reader.assign( - region, - offset, - aux.src_addr(), - aux.src_addr_end(), - &bytes, - &selectors, - )?; - - let num_bytes_copied = std::cmp::min(aux.bytes_left(), MAX_COPY_BYTES as u64); - self.finish_gadget.assign( - region, - offset, - F::from(num_bytes_copied), - F::from(aux.bytes_left()), - )?; - - Ok(()) - } -} - -#[cfg(test)] -pub mod test { - use crate::evm_circuit::{ - step::ExecutionState, - table::{RwTableTag, TxLogFieldTag}, - test::{rand_bytes, run_test_circuit_incomplete_fixed_table}, - witness::{Block, Bytecode, Call, ExecStep, Rw, RwMap, Transaction}, - }; - use bus_mapping::{ - circuit_input_builder::{CopyDetails, StepAuxiliaryData}, - constants::MAX_COPY_BYTES, - }; - use eth_types::{evm_types::OpcodeId, Word}; - use halo2_proofs::arithmetic::BaseExt; - use halo2_proofs::pairing::bn256::Fr; - use std::collections::HashMap; - use std::convert::TryInto; - - #[allow(clippy::too_many_arguments)] - fn make_log_copy_step( - call_id: usize, - src_addr: u64, - src_addr_end: u64, - bytes_left: usize, - data_start_index: usize, - program_counter: u64, - stack_pointer: usize, - memory_size: u64, - rw_counter: usize, - rws: &mut RwMap, - bytes_map: &HashMap, - log_id: u64, - is_persistent: bool, - tx_id: usize, - ) -> (ExecStep, usize) { - let mut selectors = vec![0u8; MAX_COPY_BYTES]; - let mut rw_offset: usize = 0; - let memory_rws: &mut Vec<_> = rws.0.entry(RwTableTag::Memory).or_insert_with(Vec::new); - let mut txlog_rws: Vec = Vec::new(); - let mut rw_indices: Vec<(RwTableTag, usize)> = Vec::new(); - let mut memory_index = 0; - let mut log_data_index = 0; - - for (idx, selector) in selectors.iter_mut().enumerate() { - if idx < bytes_left { - *selector = 1; - let addr = src_addr + idx as u64; - let byte = if addr < src_addr_end { - assert!(bytes_map.contains_key(&addr)); - memory_rws.push(Rw::Memory { - rw_counter: rw_counter + rw_offset, - is_write: false, - call_id, - memory_address: src_addr + idx as u64, - byte: bytes_map[&addr], - }); - rw_indices.push((RwTableTag::Memory, memory_index + data_start_index)); - memory_index += 1; - rw_offset += 1; - bytes_map[&addr] - } else { - 0 - }; - if is_persistent { - txlog_rws.push(Rw::TxLog { - rw_counter: rw_counter + rw_offset, - is_write: true, - tx_id, - log_id, - field_tag: TxLogFieldTag::Data, - index: data_start_index + idx, - value: Word::from(byte), - }); - rw_indices.push((RwTableTag::TxLog, log_data_index + data_start_index)); - log_data_index += 1; - rw_offset += 1; - } - } - } - let log_rws: &mut Vec<_> = rws.0.entry(RwTableTag::TxLog).or_insert_with(Vec::new); - log_rws.extend(txlog_rws); - - let aux_data = StepAuxiliaryData::new( - src_addr, - Default::default(), - bytes_left as u64, - src_addr_end, - CopyDetails::Log((is_persistent, tx_id, data_start_index)), - ); - - let step = ExecStep { - execution_state: ExecutionState::CopyToLog, - rw_indices, - rw_counter, - program_counter, - stack_pointer, - memory_size, - gas_cost: 0, - log_id: if is_persistent { - log_id.try_into().unwrap() - } else { - 0 - }, - aux_data: Some(aux_data), - ..Default::default() - }; - (step, rw_offset) - } - - #[allow(clippy::too_many_arguments)] - pub(crate) fn make_log_copy_steps( - call_id: usize, - buffer: &[u8], - buffer_addr: u64, - src_addr: u64, - length: usize, - program_counter: u64, - stack_pointer: usize, - memory_size: u64, - rw_counter: &mut usize, - rws: &mut RwMap, - steps: &mut Vec, - log_id: u64, - is_persistent: bool, - tx_id: usize, - ) { - let buffer_addr_end = buffer_addr + buffer.len() as u64; - let bytes_map = (buffer_addr..buffer_addr_end) - .zip(buffer.iter().copied()) - .collect(); - - let mut copied = 0; - while copied < length { - let (step, rw_offset) = make_log_copy_step( - call_id, - src_addr + copied as u64, - buffer_addr_end, - length - copied, - copied, - program_counter, - stack_pointer, - memory_size, - *rw_counter, - rws, - &bytes_map, - log_id, - is_persistent, - tx_id, - ); - steps.push(step); - *rw_counter += rw_offset; - copied += MAX_COPY_BYTES; - } - } - - fn test_ok_copy_to_log(src_addr: u64, src_addr_end: u64, length: usize, is_persistent: bool) { - let randomness = Fr::rand(); - let bytecode = Bytecode::new(vec![OpcodeId::RETURN.as_u8()]); - let call_id = 1; - let mut rws = RwMap(Default::default()); - let mut rw_counter = 1; - let mut steps = Vec::new(); - let tx_id = 1; - let log_id = 0; - let buffer = rand_bytes((src_addr_end - src_addr) as usize); - let memory_size = (src_addr + length as u64 + 31) / 32 * 32; - - make_log_copy_steps( - call_id, - &buffer, - src_addr, - src_addr, - length, - 0, - 1023, - memory_size, - &mut rw_counter, - &mut rws, - &mut steps, - log_id, - is_persistent, - tx_id, - ); - - steps.push(ExecStep { - execution_state: ExecutionState::RETURN, - rw_counter, - program_counter: 0, - stack_pointer: 1023, - memory_size, - opcode: Some(OpcodeId::RETURN), - ..Default::default() - }); - - let block = Block { - randomness, - txs: vec![Transaction { - id: 1, - calls: vec![Call { - id: call_id, - is_root: false, - is_create: false, - code_hash: bytecode.hash, - ..Default::default() - }], - steps, - ..Default::default() - }], - rws, - bytecodes: HashMap::from_iter([(bytecode.hash, bytecode)]), - ..Default::default() - }; - assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); - } - - #[test] - fn copy_to_log_simple() { - // is_persistent = true - test_ok_copy_to_log(0x10, 0x30, 0x2, true); - test_ok_copy_to_log(0x100, 0x180, 0x40, true); - // is_persistent = false - test_ok_copy_to_log(0x10, 0x30, 0x2, false); - test_ok_copy_to_log(0x100, 0x180, 0x40, false); - } - - #[test] - fn copy_to_log_multi_step() { - // is_persistent = true - test_ok_copy_to_log(0x20, 0xA0, 80, true); - test_ok_copy_to_log(0x10, 0xA0, 160, true); - // is_persistent = false - test_ok_copy_to_log(0x20, 0xA0, 80, false); - } -} diff --git a/zkevm-circuits/src/evm_circuit/execution/jump.rs b/zkevm-circuits/src/evm_circuit/execution/jump.rs index 38aa26260f..4a589eca34 100644 --- a/zkevm-circuits/src/evm_circuit/execution/jump.rs +++ b/zkevm-circuits/src/evm_circuit/execution/jump.rs @@ -101,7 +101,7 @@ mod test { JUMP }; for _ in 0..(destination - 34) { - bytecode.write(0); + bytecode.write(0, false); } bytecode.append(&bytecode! { JUMPDEST diff --git a/zkevm-circuits/src/evm_circuit/execution/jumpi.rs b/zkevm-circuits/src/evm_circuit/execution/jumpi.rs index 65b13de6e6..dee1462a58 100644 --- a/zkevm-circuits/src/evm_circuit/execution/jumpi.rs +++ b/zkevm-circuits/src/evm_circuit/execution/jumpi.rs @@ -133,7 +133,7 @@ mod test { STOP }; for _ in 0..(destination - 68) { - bytecode.write(0); + bytecode.write(0, false); } bytecode.append(&bytecode! { JUMPDEST diff --git a/zkevm-circuits/src/evm_circuit/execution/logs.rs b/zkevm-circuits/src/evm_circuit/execution/logs.rs index 5a964fdd2f..a5b7bd961f 100644 --- a/zkevm-circuits/src/evm_circuit/execution/logs.rs +++ b/zkevm-circuits/src/evm_circuit/execution/logs.rs @@ -10,15 +10,15 @@ use crate::{ ConstraintBuilder, StepStateTransition, Transition::{Delta, To}, }, - from_bytes, memory_gadget::{MemoryAddressGadget, MemoryExpansionGadget}, - sum, CachedRegion, Cell, Word, + not, sum, CachedRegion, Cell, Word, }, witness::{Block, Call, ExecStep, Transaction}, }, util::Expr, }; use array_init::array_init; +use bus_mapping::circuit_input_builder::CopyDataType; use eth_types::Field; use eth_types::{evm_types::GasCost, evm_types::OpcodeId, ToLittleEndian, ToScalar}; use halo2_proofs::plonk::Error; @@ -35,6 +35,7 @@ pub(crate) struct LogGadget { is_static_call: Cell, is_persistent: Cell, tx_id: Cell, + copy_rwc_inc: Cell, memory_expansion: MemoryExpansionGadget, } @@ -114,7 +115,7 @@ impl ExecutionGadget for LogGadget { } // check memory copy - let memory_address = MemoryAddressGadget::construct(cb, mstart, msize.clone()); + let memory_address = MemoryAddressGadget::construct(cb, mstart, msize); // Calculate the next memory size and the gas cost for this memory // access @@ -124,57 +125,39 @@ impl ExecutionGadget for LogGadget { [memory_address.address()], ); - // If the iterative process has not yet finished, we constrain the next step to - // be another `CopyToLog` while adding some additional - // constraints to the auxiliary data. - // Constrain the next step CopyToLog if length != 0 - cb.constrain_next_step( - ExecutionState::CopyToLog, - Some(memory_address.has_length()), - |cb| { - let next_src_addr = cb.query_cell(); - let next_bytes_left = cb.query_cell(); - let next_src_addr_end = cb.query_cell(); - let next_is_persistent = cb.query_bool(); - let next_tx_id = cb.query_cell(); - let next_data_start_index = cb.query_cell(); - - cb.require_equal( - "next_src_addr = memory_offset", - next_src_addr.expr(), - memory_address.offset(), - ); - cb.require_equal( - "next_bytes_left = length", - next_bytes_left.expr(), - memory_address.length(), - ); - cb.require_equal( - "next_src_addr_end = memory_offset + length", - next_src_addr_end.expr(), - memory_address.offset() + memory_address.length(), - ); - cb.require_equal( - "next_is_persistent = is_persistent", - next_is_persistent.expr(), - is_persistent.expr(), - ); - cb.require_equal("next_tx_id = tx_id", next_tx_id.expr(), tx_id.expr()); - cb.require_zero( - "next_data_start_index starts with 0", - next_data_start_index.expr(), - ); - }, - ); + let copy_rwc_inc = cb.query_cell(); + let dst_addr = (1u64 << 32).expr() * TxLogFieldTag::Data.expr() + + (1u64 << 48).expr() * (cb.curr.state.log_id.expr() + 1.expr()); + let cond = memory_address.has_length() * is_persistent.expr(); + cb.condition(cond.clone(), |cb| { + cb.copy_table_lookup( + cb.curr.state.call_id.expr(), + CopyDataType::Memory.expr(), + tx_id.expr(), + CopyDataType::TxLog.expr(), + memory_address.offset(), + memory_address.address(), + dst_addr, + memory_address.length(), + cb.curr.state.rw_counter.expr() + cb.rw_counter_offset().expr(), + copy_rwc_inc.expr(), + ); + }); + cb.condition(not::expr(cond), |cb| { + cb.require_zero( + "if length is 0 or tx is not persistent, copy table rwc inc == 0", + copy_rwc_inc.expr(), + ); + }); let gas_cost = GasCost::LOG.as_u64().expr() + GasCost::LOG.as_u64().expr() * topic_count.clone() - + 8.expr() * from_bytes::expr(&msize.cells) + + 8.expr() * memory_address.length() + memory_expansion.gas_cost(); // State transition let step_state_transition = StepStateTransition { - rw_counter: Delta(cb.rw_counter_offset()), + rw_counter: Delta(cb.rw_counter_offset() + copy_rwc_inc.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta(2.expr() + topic_count), memory_word_size: To(memory_expansion.next_memory_word_size()), @@ -194,6 +177,7 @@ impl ExecutionGadget for LogGadget { is_static_call, is_persistent, tx_id, + copy_rwc_inc, memory_expansion, } } @@ -257,20 +241,32 @@ impl ExecutionGadget for LogGadget { self.tx_id .assign(region, offset, Some(F::from(tx.id as u64)))?; + let key = (tx.id, call.id, step.program_counter as usize); + let copy_rwc_inc = block + .copy_events + .get(&key) + .unwrap() + .steps + .first() + .map_or(F::zero(), |cs| F::from(cs.rwc_inc_left)); + self.copy_rwc_inc + .assign(region, offset, Some(copy_rwc_inc))?; + Ok(()) } } #[cfg(test)] mod test { - use eth_types::{bytecode, evm_types::OpcodeId, Bytecode, Word}; + use eth_types::{evm_types::OpcodeId, Bytecode, Word}; use mock::TestContext; + use rand::Rng; use crate::test_util::run_test_circuits; //TODO:add is_persistent = false cases #[test] - fn log_tests() { + fn log_gadget_simple() { // zero topic: log0 test_log_ok(&[]); // one topic: log1 @@ -289,7 +285,7 @@ mod test { } #[test] - fn multi_log_tests() { + fn log_gadget_multi_logs() { // zero topic: log0 test_multi_log_ok(&[]); // one topic: log1 @@ -309,9 +305,9 @@ mod test { // test single log code and single copy log step fn test_log_ok(topics: &[Word]) { - let pushdata = "1234567890abcdef1234567890abcdef"; - // prepare first 32 bytes for memory reading - let mut code_prepare = prepare_code(pushdata, 0); + let mut pushdata = [0u8; 320]; + rand::thread_rng().try_fill(&mut pushdata[..]).unwrap(); + let mut code_prepare = prepare_code(&pushdata, 1); let log_codes = [ OpcodeId::LOG0, @@ -350,9 +346,9 @@ mod test { // test multi log op codes and multi copy log steps fn test_multi_log_ok(topics: &[Word]) { // prepare memory data - let pushdata = "1234567890abcdef1234567890abcdef"; - // prepare first 32 bytes for memory reading - let mut code_prepare = prepare_code(pushdata, 0); + let mut pushdata = [0u8; 320]; + rand::thread_rng().try_fill(&mut pushdata[..]).unwrap(); + let mut code_prepare = prepare_code(&pushdata, 0); let log_codes = [ OpcodeId::LOG0, @@ -379,7 +375,7 @@ mod test { // second log op code // prepare additinal bytes for memory reading - code.append(&prepare_code(pushdata, 0x20)); + code.append(&prepare_code(&pushdata, 0x20)); mstart = 0x00usize; // when mszie > 0x20 (32) needs multi copy steps msize = 0x30usize; @@ -403,16 +399,15 @@ mod test { } /// prepare memory reading data - fn prepare_code(data: &str, offset: u32) -> Bytecode { - // data is in hex format - assert_eq!(data.bytes().len(), 32); + fn prepare_code(data: &[u8], offset: usize) -> Bytecode { + assert_eq!(data.len() % 32, 0); // prepare memory data - let pushdata = hex::decode(data).unwrap(); - return bytecode! { - // populate memory. - PUSH16(Word::from_big_endian(&pushdata)) - PUSH1(offset) // offset - MSTORE - }; + let mut code = Bytecode::default(); + for (i, d) in data.chunks(32).enumerate() { + code.push(32, Word::from_big_endian(d)); + code.push(32, Word::from(offset + i * 32)); + code.write_op(OpcodeId::MSTORE); + } + code } } diff --git a/zkevm-circuits/src/evm_circuit/execution/memory_copy.rs b/zkevm-circuits/src/evm_circuit/execution/memory_copy.rs deleted file mode 100644 index 21b071b48a..0000000000 --- a/zkevm-circuits/src/evm_circuit/execution/memory_copy.rs +++ /dev/null @@ -1,496 +0,0 @@ -use crate::{ - evm_circuit::{ - execution::ExecutionGadget, - param::{N_BYTES_MEMORY_ADDRESS, N_BYTES_MEMORY_WORD_SIZE}, - step::ExecutionState, - table::TxContextFieldTag, - util::{ - constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, - math_gadget::ComparisonGadget, - memory_gadget::BufferReaderGadget, - CachedRegion, Cell, - }, - witness::{Block, Call, ExecStep, Transaction}, - }, - util::Expr, -}; -use bus_mapping::{circuit_input_builder::CopyDetails, constants::MAX_COPY_BYTES}; -use eth_types::Field; -use halo2_proofs::plonk::Error; - -/// Multi-step gadget for copying data from memory or Tx calldata to memory -#[derive(Clone, Debug)] -pub(crate) struct CopyToMemoryGadget { - // The src memory address to copy from - src_addr: Cell, - // The dst memory address to copy to - dst_addr: Cell, - // The number of bytes left to copy - bytes_left: Cell, - // The src address bound of the buffer - src_addr_end: Cell, - // Indicate whether src is from Tx Calldata - from_tx: Cell, - // Source from where we read the bytes. This equals the tx ID in case of a root call, or caller - // ID in case of an internal call - src_id: Cell, - // Buffer reader gadget - buffer_reader: BufferReaderGadget, - // The comparison gadget between num bytes copied and bytes_left - finish_gadget: ComparisonGadget, -} - -impl ExecutionGadget for CopyToMemoryGadget { - const NAME: &'static str = "COPYTOMEMORY"; - - const EXECUTION_STATE: ExecutionState = ExecutionState::CopyToMemory; - - fn configure(cb: &mut ConstraintBuilder) -> Self { - let src_addr = cb.query_cell(); - let dst_addr = cb.query_cell(); - let bytes_left = cb.query_cell(); - let src_addr_end = cb.query_cell(); - let from_tx = cb.query_bool(); - let src_id = cb.query_cell(); - let buffer_reader = BufferReaderGadget::construct(cb, src_addr.expr(), src_addr_end.expr()); - let from_memory = 1.expr() - from_tx.expr(); - - // Copy bytes from src and dst - for i in 0..MAX_COPY_BYTES { - let read_flag = buffer_reader.read_flag(i); - // Read bytes[i] from memory - cb.condition(from_memory.clone() * read_flag.clone(), |cb| { - cb.memory_lookup( - 0.expr(), - src_addr.expr() + i.expr(), - buffer_reader.byte(i), - Some(src_id.expr()), - ) - }); - // Read bytes[i] from Tx - cb.condition(from_tx.expr() * read_flag.clone(), |cb| { - cb.tx_context_lookup( - src_id.expr(), - TxContextFieldTag::CallData, - Some(src_addr.expr() + i.expr()), - buffer_reader.byte(i), - ) - }); - // Write bytes[i] to memory when selectors[i] != 0 - cb.condition(buffer_reader.has_data(i), |cb| { - cb.memory_lookup( - 1.expr(), - dst_addr.expr() + i.expr(), - buffer_reader.byte(i), - None, - ) - }); - } - - let copied_size = buffer_reader.num_bytes(); - let finish_gadget = ComparisonGadget::construct(cb, copied_size.clone(), bytes_left.expr()); - let (lt, finished) = finish_gadget.expr(); - // Constrain lt == 1 or finished == 1 - cb.add_constraint( - "Constrain num_bytes <= bytes_left", - (1.expr() - lt) * (1.expr() - finished.clone()), - ); - - // When finished == 0, constraint the CopyToMemory state in next step - cb.constrain_next_step( - ExecutionState::CopyToMemory, - Some(1.expr() - finished), - |cb| { - let next_src_addr = cb.query_cell(); - let next_dst_addr = cb.query_cell(); - let next_bytes_left = cb.query_cell(); - let next_src_addr_end = cb.query_cell(); - let next_from_tx = cb.query_cell(); - let next_src_id = cb.query_cell(); - cb.require_equal( - "next_src_addr == src_addr + copied_size", - next_src_addr.expr(), - src_addr.expr() + copied_size.clone(), - ); - cb.require_equal( - "dst_addr + copied_size == next_dst_addr", - next_dst_addr.expr(), - dst_addr.expr() + copied_size.clone(), - ); - cb.require_equal( - "next_bytes_left == bytes_left - copied_size", - next_bytes_left.expr(), - bytes_left.expr() - copied_size.clone(), - ); - cb.require_equal( - "next_src_addr_end == src_addr_end", - next_src_addr_end.expr(), - src_addr_end.expr(), - ); - cb.require_equal( - "next_from_tx == from_tx", - next_from_tx.expr(), - from_tx.expr(), - ); - cb.require_equal("next_src_id == src_id", next_src_id.expr(), src_id.expr()); - }, - ); - - // State transition - let step_state_transition = StepStateTransition { - rw_counter: Delta(cb.rw_counter_offset()), - ..Default::default() - }; - cb.require_step_state_transition(step_state_transition); - - Self { - src_addr, - dst_addr, - bytes_left, - src_addr_end, - from_tx, - src_id, - buffer_reader, - finish_gadget, - } - } - - fn assign_exec_step( - &self, - region: &mut CachedRegion<'_, '_, F>, - offset: usize, - block: &Block, - tx: &Transaction, - call: &Call, - step: &ExecStep, - ) -> Result<(), Error> { - // Read the auxiliary data. - let aux = if step.aux_data.is_none() { - // TODO: Handle error correctly returning err - unreachable!("could not find aux_data for this step") - } else { - step.aux_data.unwrap() - }; - - let from_tx = match aux.copy_details() { - CopyDetails::TxCallData(root_call) => root_call, - _ => unreachable!("the source has to come from calldata and not code"), - }; - - self.src_addr - .assign(region, offset, Some(F::from(aux.src_addr())))?; - self.dst_addr - .assign(region, offset, Some(F::from(aux.dst_addr())))?; - self.bytes_left - .assign(region, offset, Some(F::from(aux.bytes_left())))?; - self.src_addr_end - .assign(region, offset, Some(F::from(aux.src_addr_end())))?; - self.from_tx - .assign(region, offset, Some(F::from(from_tx as u64)))?; - let src_id = if call.is_root { tx.id } else { call.caller_id }; - self.src_id - .assign(region, offset, Some(F::from(src_id as u64)))?; - - // Fill in selectors and bytes - let mut rw_idx = 0; - let mut bytes = vec![0u8; MAX_COPY_BYTES]; - let mut selectors = vec![false; MAX_COPY_BYTES]; - for idx in 0..std::cmp::min(aux.bytes_left() as usize, MAX_COPY_BYTES) { - let src_addr = aux.src_addr() as usize + idx; - selectors[idx] = true; - bytes[idx] = if selectors[idx] && src_addr < aux.src_addr_end() as usize { - if from_tx { - tx.call_data[src_addr] - } else { - rw_idx += 1; - block.rws[step.rw_indices[rw_idx]].memory_value() - } - } else { - 0 - }; - // increase rw_idx for writing back to memory - rw_idx += 1 - } - - self.buffer_reader.assign( - region, - offset, - aux.src_addr(), - aux.src_addr_end(), - &bytes, - &selectors, - )?; - - let num_bytes_copied = std::cmp::min(aux.bytes_left(), MAX_COPY_BYTES as u64); - self.finish_gadget.assign( - region, - offset, - F::from(num_bytes_copied), - F::from(aux.bytes_left()), - )?; - - Ok(()) - } -} - -#[cfg(test)] -pub mod test { - use crate::evm_circuit::{ - execution::memory_copy::MAX_COPY_BYTES, - step::ExecutionState, - table::RwTableTag, - test::{rand_bytes, run_test_circuit_incomplete_fixed_table}, - witness::{Block, Bytecode, Call, ExecStep, Rw, RwMap, Transaction}, - }; - use bus_mapping::circuit_input_builder::{CopyDetails, StepAuxiliaryData}; - use eth_types::evm_types::OpcodeId; - use halo2_proofs::arithmetic::BaseExt; - use halo2_proofs::pairing::bn256::Fr; - use std::collections::HashMap; - - pub(crate) const CALL_ID: usize = 1; - pub(crate) const CALLER_ID: usize = 0; - pub(crate) const TX_ID: usize = 1; - - #[allow(clippy::too_many_arguments)] - fn make_memory_copy_step( - src_addr: u64, - dst_addr: u64, - src_addr_end: u64, - bytes_left: usize, - from_tx: bool, - program_counter: u64, - stack_pointer: usize, - memory_size: u64, - rw_counter: usize, - rws: &mut RwMap, - bytes_map: &HashMap, - ) -> (ExecStep, usize) { - let mut rw_offset: usize = 0; - let memory_rws: &mut Vec<_> = rws.0.entry(RwTableTag::Memory).or_insert_with(Vec::new); - let rw_idx_start = memory_rws.len(); - for idx in 0..std::cmp::min(bytes_left, MAX_COPY_BYTES) { - let addr = src_addr + idx as u64; - let byte = if addr < src_addr_end { - assert!(bytes_map.contains_key(&addr)); - if !from_tx { - memory_rws.push(Rw::Memory { - rw_counter: rw_counter + rw_offset, - is_write: false, - call_id: CALLER_ID, - memory_address: src_addr + idx as u64, - byte: bytes_map[&addr], - }); - rw_offset += 1; - } - bytes_map[&addr] - } else { - 0 - }; - // write to destination memory - memory_rws.push(Rw::Memory { - rw_counter: rw_counter + rw_offset, - is_write: true, - call_id: CALL_ID, - memory_address: dst_addr + idx as u64, - byte, - }); - rw_offset += 1; - } - let rw_idx_end = rws.0[&RwTableTag::Memory].len(); - let aux_data = StepAuxiliaryData::new( - src_addr, - dst_addr, - bytes_left as u64, - src_addr_end, - CopyDetails::TxCallData(from_tx), - ); - let step = ExecStep { - execution_state: ExecutionState::CopyToMemory, - rw_indices: (rw_idx_start..rw_idx_end) - .map(|idx| (RwTableTag::Memory, idx)) - .collect(), - rw_counter, - program_counter, - stack_pointer, - memory_size, - gas_cost: 0, - aux_data: Some(aux_data), - ..Default::default() - }; - (step, rw_offset) - } - - #[allow(clippy::too_many_arguments)] - pub(crate) fn make_memory_copy_steps( - buffer: &[u8], - buffer_addr: u64, // buffer base address, use 0 for tx calldata - src_addr: u64, - dst_addr: u64, - length: usize, - from_tx: bool, - program_counter: u64, - stack_pointer: usize, - memory_size: u64, - rw_counter: &mut usize, - rws: &mut RwMap, - steps: &mut Vec, - ) { - let buffer_addr_end = buffer_addr + buffer.len() as u64; - let bytes_map = (buffer_addr..buffer_addr_end) - .zip(buffer.iter().copied()) - .collect(); - - let mut copied = 0; - while copied < length { - let (step, rw_offset) = make_memory_copy_step( - src_addr + copied as u64, - dst_addr + copied as u64, - buffer_addr_end, - length - copied, - from_tx, - program_counter, - stack_pointer, - memory_size, - *rw_counter, - rws, - &bytes_map, - ); - steps.push(step); - *rw_counter += rw_offset; - copied += MAX_COPY_BYTES; - } - } - - fn test_ok_from_memory(src_addr: u64, dst_addr: u64, src_addr_end: u64, length: usize) { - let randomness = Fr::rand(); - let bytecode = Bytecode::new(vec![OpcodeId::RETURN.as_u8()]); - let mut rws = RwMap(Default::default()); - let mut rw_counter = 1; - let mut steps = Vec::new(); - let buffer = rand_bytes((src_addr_end - src_addr) as usize); - let memory_size = (dst_addr + length as u64 + 31) / 32 * 32; - - make_memory_copy_steps( - &buffer, - src_addr, - src_addr, - dst_addr, - length, - false, - 0, - 1024, - memory_size, - &mut rw_counter, - &mut rws, - &mut steps, - ); - - steps.push(ExecStep { - execution_state: ExecutionState::RETURN, - rw_counter, - program_counter: 0, - stack_pointer: 1024, - memory_size, - opcode: Some(OpcodeId::RETURN), - ..Default::default() - }); - - let block = Block { - randomness, - txs: vec![Transaction { - id: TX_ID, - calls: vec![Call { - id: CALL_ID, - is_root: false, - is_create: false, - code_hash: bytecode.hash, - caller_id: CALLER_ID, - ..Default::default() - }], - steps, - ..Default::default() - }], - rws, - bytecodes: HashMap::from_iter([(bytecode.hash, bytecode)]), - ..Default::default() - }; - assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); - } - - fn test_ok_from_tx(calldata_length: usize, src_addr: u64, dst_addr: u64, length: usize) { - let randomness = Fr::rand(); - let bytecode = Bytecode::new(vec![OpcodeId::RETURN.as_u8(), OpcodeId::RETURN.as_u8()]); - let mut rws = RwMap(Default::default()); - let mut rw_counter = 1; - let calldata: Vec = rand_bytes(calldata_length); - let mut steps = Vec::new(); - let memory_size = (dst_addr + length as u64 + 31) / 32 * 32; - - make_memory_copy_steps( - &calldata, - 0, - src_addr, - dst_addr, - length, - true, - 0, - 1024, - memory_size, - &mut rw_counter, - &mut rws, - &mut steps, - ); - - steps.push(ExecStep { - execution_state: ExecutionState::RETURN, - rw_counter, - program_counter: 0, - stack_pointer: 1024, - memory_size, - opcode: Some(OpcodeId::RETURN), - ..Default::default() - }); - - let block = Block { - randomness, - txs: vec![Transaction { - id: TX_ID, - call_data: calldata, - call_data_length: calldata_length, - calls: vec![Call { - id: CALL_ID, - is_root: true, - is_create: false, - code_hash: bytecode.hash, - ..Default::default() - }], - steps, - ..Default::default() - }], - rws, - bytecodes: HashMap::from_iter([(bytecode.hash, bytecode)]), - ..Default::default() - }; - assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); - } - - #[test] - fn copy_to_memory_simple() { - test_ok_from_memory(0x40, 0xA0, 0x70, 5); - test_ok_from_tx(32, 5, 0x40, 5); - } - - #[test] - fn copy_to_memory_multi_step() { - test_ok_from_memory(0x20, 0xA0, 0x80, 80); - test_ok_from_tx(128, 10, 0x40, 90); - } - - #[test] - fn copy_to_memory_out_of_bound() { - test_ok_from_memory(0x40, 0xA0, 0x60, 45); - test_ok_from_tx(32, 5, 0x40, 45); - test_ok_from_tx(32, 40, 0x40, 5); - } -} diff --git a/zkevm-circuits/src/evm_circuit/execution/push.rs b/zkevm-circuits/src/evm_circuit/execution/push.rs index 85bc56bad9..aeb6103d0e 100644 --- a/zkevm-circuits/src/evm_circuit/execution/push.rs +++ b/zkevm-circuits/src/evm_circuit/execution/push.rs @@ -156,7 +156,7 @@ mod test { .write_op(opcode) }; for b in bytes { - bytecode.write(*b); + bytecode.write(*b, false); } bytecode.write_op(OpcodeId::STOP); diff --git a/zkevm-circuits/src/evm_circuit/param.rs b/zkevm-circuits/src/evm_circuit/param.rs index 5250605f0d..f6189878b4 100644 --- a/zkevm-circuits/src/evm_circuit/param.rs +++ b/zkevm-circuits/src/evm_circuit/param.rs @@ -14,6 +14,7 @@ pub(crate) const LOOKUP_CONFIG: &[(Table, usize)] = &[ (Table::Bytecode, 4), (Table::Block, 1), (Table::Byte, 24), + (Table::Copy, 1), ]; /// Maximum number of bytes that an integer can fit in field without wrapping diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index d55f98c74c..a7b8d616ac 100644 --- a/zkevm-circuits/src/evm_circuit/step.rs +++ b/zkevm-circuits/src/evm_circuit/step.rs @@ -23,9 +23,6 @@ pub enum ExecutionState { BeginTx, EndTx, EndBlock, - CopyCodeToMemory, - CopyToMemory, - CopyToLog, // Opcode successful cases STOP, ADD_SUB, // ADD, SUB diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index 80d7429f3f..62e2c327bf 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -255,6 +255,7 @@ pub(crate) enum Table { Bytecode, Block, Byte, + Copy, } #[derive(Clone, Debug)] @@ -324,6 +325,34 @@ pub(crate) enum Lookup { /// Value of the field. value: Expression, }, + /// Lookup to copy table. + CopyTable { + /// Whether the row is the first row of the copy event. + is_first: Expression, + /// The source ID for the copy event. + src_id: Expression, + /// The source tag for the copy event. + src_tag: Expression, + /// The destination ID for the copy event. + dst_id: Expression, + /// The destination tag for the copy event. + dst_tag: Expression, + /// The source address where bytes are copied from. + src_addr: Expression, + /// The source address where all source-side bytes have been copied. + /// This does not necessarily mean there no more bytes to be copied, but + /// any bytes following this address will indicating padding. + src_addr_end: Expression, + /// The destination address at which bytes are copied. + dst_addr: Expression, + /// The number of bytes to be copied in this copy event. + length: Expression, + /// The RW counter at the start of the copy event. + rw_counter: Expression, + /// The RW counter that is incremented by the time all bytes have been + /// copied specific to this copy event. + rwc_inc: Expression, + }, /// Conditional lookup enabled by the first element. Conditional(Expression, Box>), } @@ -341,6 +370,7 @@ impl Lookup { Self::Bytecode { .. } => Table::Bytecode, Self::Block { .. } => Table::Block, Self::Byte { .. } => Table::Byte, + Self::CopyTable { .. } => Table::Copy, Self::Conditional(_, lookup) => lookup.table(), } } @@ -389,6 +419,31 @@ impl Lookup { Self::Byte { value } => { vec![value.clone()] } + Self::CopyTable { + is_first, + src_id, + src_tag, + dst_id, + dst_tag, + src_addr, + src_addr_end, + dst_addr, + length, + rw_counter, + rwc_inc, + } => vec![ + is_first.clone(), + src_id.clone(), + src_tag.clone(), + dst_id.clone(), + dst_tag.clone(), + src_addr.clone(), + src_addr_end.clone(), + dst_addr.clone(), + length.clone(), + rw_counter.clone(), + rwc_inc.clone(), + ], Self::Conditional(condition, lookup) => lookup .input_exprs() .into_iter() diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index a19235c0ca..84d49f1a40 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -1074,6 +1074,40 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { ); } + // Copy Table + + #[allow(clippy::too_many_arguments)] + pub(crate) fn copy_table_lookup( + &mut self, + src_id: Expression, + src_tag: Expression, + dst_id: Expression, + dst_tag: Expression, + src_addr: Expression, + src_addr_end: Expression, + dst_addr: Expression, + length: Expression, + rw_counter: Expression, + rwc_inc: Expression, + ) { + self.add_lookup( + "copy lookup", + Lookup::CopyTable { + is_first: 1.expr(), // is_first + src_id, + src_tag, + dst_id, + dst_tag, + src_addr, + src_addr_end, + dst_addr, + length, + rw_counter, + rwc_inc, + }, + ); + } + // Validation pub(crate) fn validate_degree(&self, degree: usize, name: &'static str) { diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index 97b1a411cd..d90396f168 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -10,7 +10,7 @@ use crate::evm_circuit::{ }; use bus_mapping::{ - circuit_input_builder::{self, StepAuxiliaryData}, + circuit_input_builder::{self, CopyEvent}, error::{ExecError, OogError}, operation::{self, AccountField, CallContextField, TxLogField, TxReceiptField}, }; @@ -36,6 +36,9 @@ pub struct Block { pub bytecodes: HashMap, /// The block context pub context: BlockContext, + /// Copy events for the EVM circuit's Copy Table, a mapping from (tx_id || + /// call_id || pc) to the corresponding copy event. + pub copy_events: HashMap<(usize, usize, usize), CopyEvent>, } #[derive(Debug, Default, Clone)] @@ -304,8 +307,6 @@ pub struct ExecStep { pub log_id: usize, /// The opcode corresponds to the step pub opcode: Option, - /// Step auxiliary data - pub aux_data: Option, } impl ExecStep { @@ -1282,9 +1283,6 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { } circuit_input_builder::ExecState::BeginTx => ExecutionState::BeginTx, circuit_input_builder::ExecState::EndTx => ExecutionState::EndTx, - circuit_input_builder::ExecState::CopyToMemory => ExecutionState::CopyToMemory, - circuit_input_builder::ExecState::CopyToLog => ExecutionState::CopyToLog, - circuit_input_builder::ExecState::CopyCodeToMemory => ExecutionState::CopyCodeToMemory, } } } @@ -1333,7 +1331,6 @@ fn step_convert(step: &circuit_input_builder::ExecStep) -> ExecStep { memory_size: step.memory_size as u64, reversible_write_counter: step.reversible_write_counter, log_id: step.log_id, - aux_data: step.aux_data.map(Into::into), } } @@ -1427,5 +1424,15 @@ pub fn block_convert( }) }) .collect(), + copy_events: block + .copy_events + .iter() + .map(|copy_event| { + ( + (copy_event.tx_id, copy_event.call_id, copy_event.pc.0), + copy_event.clone(), + ) + }) + .collect(), } } diff --git a/zkevm-circuits/src/lib.rs b/zkevm-circuits/src/lib.rs index 31b01b6338..3ece83aa54 100644 --- a/zkevm-circuits/src/lib.rs +++ b/zkevm-circuits/src/lib.rs @@ -17,6 +17,7 @@ #![deny(clippy::debug_assert_with_mut_call)] pub mod bytecode_circuit; +pub mod copy_circuit; pub mod evm_circuit; pub mod rw_table; pub mod state_circuit; diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 8ac3043a7b..9cf51b923f 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -12,10 +12,12 @@ use crate::evm_circuit::{ table::RwTableTag, witness::{Rw, RwMap}, }; -use crate::util::Expr; use constraint_builder::{ConstraintBuilder, Queries}; use eth_types::{Address, Field}; -use gadgets::binary_number::{BinaryNumberChip, BinaryNumberConfig}; +use gadgets::{ + binary_number::{BinaryNumberChip, BinaryNumberConfig}, + util::Expr, +}; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, plonk::{