diff --git a/bus-mapping/src/circuit_input_builder/execution.rs b/bus-mapping/src/circuit_input_builder/execution.rs index 90c91dabde..8a2566b166 100644 --- a/bus-mapping/src/circuit_input_builder/execution.rs +++ b/bus-mapping/src/circuit_input_builder/execution.rs @@ -49,6 +49,7 @@ impl ExecStep { call_index: usize, rwc: RWCounter, reversible_write_counter: usize, + log_id: usize, ) -> Self { ExecStep { exec_state: ExecState::Op(step.op), @@ -61,7 +62,7 @@ impl ExecStep { call_index, rwc, reversible_write_counter, - log_id: 0, + log_id, bus_mapping_instance: Vec::new(), error: None, aux_data: None, @@ -101,6 +102,8 @@ pub enum ExecState { EndTx, /// Virtual step Copy To Memory CopyToMemory, + /// Virtual step Copy To Log + CopyToLog, /// Virtal step Copy Code To Memory CopyCodeToMemory, } @@ -132,6 +135,15 @@ impl ExecState { false } } + + /// Returns `true` if `ExecState` is an opcode and the opcode is a `Logn`. + pub fn is_log(&self) -> bool { + if let ExecState::Op(op) = self { + op.is_log() + } else { + false + } + } } /// Provides specific details about the data copy for which an 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 0f5b77cff4..32d146796a 100644 --- a/bus-mapping/src/circuit_input_builder/input_state_ref.rs +++ b/bus-mapping/src/circuit_input_builder/input_state_ref.rs @@ -9,7 +9,7 @@ use crate::{ exec_trace::OperationRef, operation::{ AccountField, AccountOp, CallContextField, CallContextOp, MemoryOp, Op, OpEnum, Operation, - StackOp, Target, RW, + StackOp, Target, TxLogField, TxLogOp, RW, }, state_db::{CodeDB, StateDB}, Error, @@ -41,11 +41,19 @@ impl<'a> CircuitInputStateRef<'a> { /// Create a new step from a `GethExecStep` 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, )) } @@ -76,6 +84,7 @@ impl<'a> CircuitInputStateRef<'a> { } else { 0 }, + log_id: prev_step.log_id, ..Default::default() } } @@ -271,6 +280,29 @@ impl<'a> CircuitInputStateRef<'a> { Ok(()) } + /// Push a write type [`TxLogOp`] into the + /// [`OperationContainer`](crate::operation::OperationContainer) with the + /// next [`RWCounter`](crate::operation::RWCounter), and then + /// adds a reference to the stored operation ([`OperationRef`]) inside + /// the bus-mapping instance of the current [`ExecStep`]. Then increase + /// the `block_ctx` [`RWCounter`](crate::operation::RWCounter) by one. + pub fn tx_log_write( + &mut self, + step: &mut ExecStep, + tx_id: usize, + log_id: usize, + field: TxLogField, + index: usize, + value: Word, + ) -> Result<(), Error> { + self.push_op( + step, + RW::WRITE, + TxLogOp::new(tx_id, log_id, field, index, value), + ); + Ok(()) + } + /// Push 2 reversible [`AccountOp`] to update `sender` and `receiver`'s /// balance by `value`, with `sender` being extraly charged with `fee`. pub fn transfer_with_fee( diff --git a/bus-mapping/src/circuit_input_builder/tracer_tests.rs b/bus-mapping/src/circuit_input_builder/tracer_tests.rs index 5d6ed6fbf2..f795a79b7c 100644 --- a/bus-mapping/src/circuit_input_builder/tracer_tests.rs +++ b/bus-mapping/src/circuit_input_builder/tracer_tests.rs @@ -47,11 +47,18 @@ impl CircuitInputBuilderTx { false, ) .unwrap(); + + let prev_log_id = if tx.is_steps_empty() { + 0 + } else { + tx.last_step().log_id + }; + Self { builder, tx, tx_ctx, - step: ExecStep::new(geth_step, 0, RWCounter::new(), 0), + step: ExecStep::new(geth_step, 0, RWCounter::new(), 0, prev_log_id), } } diff --git a/bus-mapping/src/circuit_input_builder/transaction.rs b/bus-mapping/src/circuit_input_builder/transaction.rs index f1d212f296..09f5d55ba0 100644 --- a/bus-mapping/src/circuit_input_builder/transaction.rs +++ b/bus-mapping/src/circuit_input_builder/transaction.rs @@ -282,4 +282,18 @@ impl Transaction { pub(crate) fn push_call(&mut self, call: Call) { self.calls.push(call); } + + /// Return last step in this transaction. + pub fn last_step(&self) -> &ExecStep { + if self.steps().is_empty() { + panic!("there is no steps in tx"); + } + + &self.steps[self.steps.len() - 1] + } + + /// Return whether the steps in this transaction is empty + pub fn is_steps_empty(&self) -> bool { + self.steps.is_empty() + } } diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index ceed372ffe..6352c74baf 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -28,6 +28,7 @@ mod codecopy; mod dup; mod extcodehash; mod gasprice; +mod logs; mod mload; mod mstore; mod number; @@ -49,6 +50,7 @@ use codecopy::Codecopy; use dup::Dup; use extcodehash::Extcodehash; use gasprice::GasPrice; +use logs::Log; use mload::Mload; use mstore::Mstore; use origin::Origin; @@ -188,11 +190,11 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { OpcodeId::SWAP14 => Swap::<14>::gen_associated_ops, OpcodeId::SWAP15 => Swap::<15>::gen_associated_ops, OpcodeId::SWAP16 => Swap::<16>::gen_associated_ops, - // OpcodeId::LOG0 => {}, - // OpcodeId::LOG1 => {}, - // OpcodeId::LOG2 => {}, - // OpcodeId::LOG3 => {}, - // OpcodeId::LOG4 => {}, + OpcodeId::LOG0 => Log::gen_associated_ops, + OpcodeId::LOG1 => Log::gen_associated_ops, + OpcodeId::LOG2 => Log::gen_associated_ops, + OpcodeId::LOG3 => Log::gen_associated_ops, + OpcodeId::LOG4 => Log::gen_associated_ops, // OpcodeId::CREATE => {}, OpcodeId::CALL => Call::gen_associated_ops, // OpcodeId::CALLCODE => {}, diff --git a/bus-mapping/src/evm/opcodes/logs.rs b/bus-mapping/src/evm/opcodes/logs.rs new file mode 100644 index 0000000000..22590d6d8c --- /dev/null +++ b/bus-mapping/src/evm/opcodes/logs.rs @@ -0,0 +1,446 @@ +use super::Opcode; +use crate::operation::{CallContextField, TxLogField}; +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::Word; +use eth_types::{GethExecStep, ToBigEndian, ToWord}; + +#[derive(Clone, Copy, Debug)] +pub(crate) struct Log; + +impl Opcode for Log { + fn gen_associated_ops( + state: &mut CircuitInputStateRef, + 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) + } +} + +fn gen_log_step( + state: &mut CircuitInputStateRef, + geth_step: &GethExecStep, +) -> Result { + let mut exec_step = state.new_step(geth_step)?; + + let mstart = geth_step.stack.nth_last(0)?; + let msize = geth_step.stack.nth_last(1)?; + + let call_id = state.call()?.call_id; + let mut stack_index = 0; + state.stack_read( + &mut exec_step, + geth_step.stack.nth_last_filled(stack_index), + mstart, + )?; + state.stack_read( + &mut exec_step, + geth_step.stack.nth_last_filled(stack_index + 1), + msize, + )?; + + stack_index += 1; + + state.call_context_read( + &mut exec_step, + call_id, + CallContextField::TxId, + state.tx_ctx.id().into(), + ); + state.call_context_read( + &mut exec_step, + call_id, + CallContextField::IsStatic, + Word::from(state.call()?.is_static as u8), + ); + state.call_context_read( + &mut exec_step, + call_id, + CallContextField::CalleeAddress, + state.call()?.address.to_word(), + ); + state.call_context_read( + &mut exec_step, + call_id, + CallContextField::IsPersistent, + 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, + TxLogField::Address, + 0, + state.call()?.address.to_word(), + )?; + } + + // generates topic operation dynamically + let topic_count = match exec_step.exec_state { + ExecState::Op(op_id) => (op_id.as_u8() - OpcodeId::LOG0.as_u8()) as usize, + _ => panic!("currently only handle succeful log state"), + }; + + for i in 0..topic_count { + let topic = geth_step.stack.nth_last(2 + i)?; + state.stack_read( + &mut exec_step, + geth_step.stack.nth_last_filled(stack_index + 1), + topic, + )?; + stack_index += 1; + + if state.call()?.is_persistent { + state.tx_log_write( + &mut exec_step, + state.tx_ctx.id(), + log_id + 1, + TxLogField::Topic, + i, + topic, + )?; + } + } + + Ok(exec_step) +} + +fn gen_log_copy_step( + state: &mut CircuitInputStateRef, + geth_steps: &[GethExecStep], + exec_step: &mut ExecStep, + src_addr: u64, + src_addr_end: u64, + bytes_left: usize, + data_start_index: usize, +) -> 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 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 + } else { + 0 + }; + // 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, + state.tx_ctx.id(), + data_start_index, + )), + )); + + Ok(()) +} + +fn gen_log_copy_steps( + 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; + } + + Ok(steps) +} + +#[cfg(test)] +mod log_tests { + use crate::{ + circuit_input_builder::ExecState, + mock::BlockData, + operation::{CallContextField, CallContextOp, MemoryOp, StackOp, TxLogField, TxLogOp, RW}, + }; + use eth_types::{ + bytecode, + evm_types::{OpcodeId, StackAddress}, + geth_types::GethData, + Bytecode, ToWord, Word, + }; + + use mock::test_ctx::{helpers::*, TestContext}; + use pretty_assertions::assert_eq; + + #[test] + fn logs_opcode_ok() { + // zero topics + test_logs_opcode(&[]); + // one topics + test_logs_opcode(&[Word::from(0xA0)]); + // two topics + test_logs_opcode(&[Word::from(0xA0), Word::from(0xef)]); + // three topics + test_logs_opcode(&[Word::from(0xA0), Word::from(0xef), Word::from(0xb0)]); + // four topics + test_logs_opcode(&[ + Word::from(0xA0), + Word::from(0xef), + Word::from(0xb0), + Word::from(0x37), + ]); + } + + fn test_logs_opcode(topics: &[Word]) { + let log_codes = [ + OpcodeId::LOG0, + OpcodeId::LOG1, + OpcodeId::LOG2, + OpcodeId::LOG3, + OpcodeId::LOG4, + ]; + + let topic_count = topics.len(); + let cur_op_code = log_codes[topic_count]; + + let mstart = 0x00usize; + let msize = 0x40usize; + let mut code = Bytecode::default(); + // make dynamic topics push operations + for topic in topics { + code.push(32, *topic); + } + + code.push(32, Word::from(msize)); + code.push(32, Word::from(mstart)); + code.write_op(cur_op_code); + code.write_op(OpcodeId::STOP); + + // prepare memory data + let pushdata = hex::decode("1234567890abcdef1234567890abcdef").unwrap(); + let mut memory_data = std::iter::repeat(0) + .take(16) + .chain(pushdata.clone()) + .collect::>(); + // construct 64 bytes + memory_data.append(&mut memory_data.clone()); + + let mut code_prepare: Bytecode = bytecode! { + // populate memory. + PUSH16(Word::from_big_endian(&pushdata)) + PUSH1(0x00) // offset + MSTORE + PUSH16(Word::from_big_endian(&pushdata)) + PUSH1(0x20) // offset + MSTORE + }; + + code_prepare.append(&code); + + // Get the execution steps from the external tracer + let block: GethData = TestContext::<2, 1>::new( + None, + account_0_code_account_1_no_code(code_prepare), + tx_from_1_to_0, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap() + .into(); + + let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + + let is_persistent = builder.block.txs()[0].calls()[0].is_persistent; + let callee_address = builder.block.txs()[0].to; + + let step = builder.block.txs()[0] + .steps() + .iter() + .find(|step| step.exec_state == ExecState::Op(cur_op_code)) + .unwrap(); + + assert_eq!( + [0, 1] + .map(|idx| &builder.block.container.stack[step.bus_mapping_instance[idx].as_usize()]) + .map(|operation| (operation.rw(), operation.op())), + [ + ( + RW::READ, + &StackOp::new(1, StackAddress::from((1022 - topic_count) as u32), Word::from(mstart)) + ), + ( + RW::READ, + &StackOp::new(1, StackAddress::from((1023 - topic_count) as u32), Word::from(msize)) + ) + ] + ); + + // assert call context is right + assert_eq!( + [2, 3, 4, 5] + .map(|idx| &builder.block.container.call_context + [step.bus_mapping_instance[idx].as_usize()]) + .map(|operation| (operation.rw(), operation.op())), + [ + ( + RW::READ, + &CallContextOp { + call_id: 1, + field: CallContextField::TxId, + value: Word::from(1), + }, + ), + ( + RW::READ, + &CallContextOp { + call_id: 1, + field: CallContextField::IsStatic, + value: Word::from(0), + }, + ), + ( + RW::READ, + &CallContextOp { + call_id: 1, + field: CallContextField::CalleeAddress, + value: callee_address.to_word(), + }, + ), + ( + RW::READ, + &CallContextOp { + call_id: 1, + field: CallContextField::IsPersistent, + value: Word::from(1), + }, + ), + ] + ); + + // TODO: handle is_persistent = false conditions + if is_persistent { + assert_eq!( + [6].map(|idx| &builder.block.container.tx_log + [step.bus_mapping_instance[idx].as_usize()]) + .map(|operation| (operation.rw(), operation.op())), + [( + RW::WRITE, + &TxLogOp { + tx_id: 1, + log_id: step.log_id + 1, + field: TxLogField::Address, + index: 0, + value: callee_address.to_word(), + } + ),] + ); + } + // memory reads. + let mut log_data_ops = Vec::with_capacity(msize); + assert_eq!( + // skip first 32 writes of MSTORE ops + (mstart + 64..(mstart + 64 + msize)) + .map(|idx| &builder.block.container.memory[idx]) + .map(|op| (op.rw(), op.op().clone())) + .collect::>(), + { + let mut memory_ops = Vec::with_capacity(msize); + (mstart..msize).for_each(|idx| { + memory_ops.push(( + RW::READ, + MemoryOp::new(1, (mstart + idx).into(), memory_data[mstart + idx]), + )); + // tx log addition + log_data_ops.push(( + RW::WRITE, + TxLogOp::new( + 1, + step.log_id + 1, // because it is in next CopyToLog step + TxLogField::Data, + idx - mstart, + Word::from(memory_data[mstart + idx]), + ), + )); + }); + + 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) + .map(|idx| &builder.block.container.tx_log[idx]) + .map(|op| (op.rw(), op.op().clone())) + .collect::>(), + { log_topic_ops }, + ); + + // log data writes + 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 }, + ); + } +} diff --git a/bus-mapping/src/exec_trace.rs b/bus-mapping/src/exec_trace.rs index 6d82f01358..0e6d931b47 100644 --- a/bus-mapping/src/exec_trace.rs +++ b/bus-mapping/src/exec_trace.rs @@ -23,6 +23,7 @@ impl fmt::Debug for OperationRef { Target::AccountDestructed => "AccountDestructed", Target::CallContext => "CallContext", Target::TxReceipt => "TxReceipt", + Target::TxLog => "TxLog", }, self.1 )) diff --git a/bus-mapping/src/operation.rs b/bus-mapping/src/operation.rs index eca1f080c0..6aa0b199b1 100644 --- a/bus-mapping/src/operation.rs +++ b/bus-mapping/src/operation.rs @@ -108,6 +108,8 @@ pub enum Target { CallContext, /// Means the target of the operation is the TxReceipt. TxReceipt, + /// Means the target of the operation is the TxLog. + TxLog, } /// Trait used for Operation Kinds. @@ -779,6 +781,93 @@ impl CallContextOp { } } +/// Represents a field parameter of the TxLog that can be accessed via EVM +/// execution. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum TxLogField { + /// contract address + Address, + /// topic of log entry + Topic, + /// data of log entry + Data, + /* TODO: Add `TopicLength` and `DataLength`, which will be used for the RLP encoding of the + * Tx Receipt */ +} + +/// Represents TxLog read/write operation. +#[derive(Clone, PartialEq, Eq)] +pub struct TxLogOp { + /// tx_id of TxLog, starts with 1 in rw table, and it's unique per Tx + pub tx_id: usize, + /// id of log entry, starts with 1 in rw table, it's unique within Tx, + /// currently it is also field of execution step, As field of execution + /// step, it resets to zero (in begin_tx), and increases with each Log* step + /// the reason why rw table's `log_id` start with 1 instead of zero is that + /// zero `log_id` represents no log steps(no any logs inserting) executed + pub log_id: usize, + /// field of TxLogField + pub field: TxLogField, + /// topic index if field is Topic + /// byte index if field is Data + /// it would be zero for other field tags + pub index: usize, + /// value + pub value: Word, +} + +impl TxLogOp { + /// Create a new instance of a `TxLogOp` from it's components. + pub fn new( + tx_id: usize, + log_id: usize, + field: TxLogField, + index: usize, + value: Word, + ) -> TxLogOp { + TxLogOp { + tx_id, + log_id, + field, + index, + value, + } + } +} + +impl fmt::Debug for TxLogOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("TxLogOp { ")?; + f.write_fmt(format_args!( + "tx_id: {:?}, log_id: {:?}, field: {:?}, index: {:?}, value: {:?}", + self.tx_id, self.log_id, self.field, self.index, self.value, + ))?; + f.write_str(" }") + } +} + +impl PartialOrd for TxLogOp { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for TxLogOp { + fn cmp(&self, other: &Self) -> Ordering { + (&self.tx_id, &self.log_id, &self.field).cmp(&(&other.tx_id, &other.log_id, &other.field)) + } +} + +impl Op for TxLogOp { + fn into_enum(self) -> OpEnum { + OpEnum::TxLog(self) + } + + fn reverse(&self) -> Self { + unreachable!("TxLog can't be reverted") + } +} + /// Represents a field parameter of the TxReceipt that can be accessed via EVM /// execution. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -860,6 +949,8 @@ pub enum OpEnum { CallContext(CallContextOp), /// TxReceipt TxReceipt(TxReceiptOp), + /// TxLog + TxLog(TxLogOp), } /// Operation is a Wrapper over a type that implements Op with a RWCounter. diff --git a/bus-mapping/src/operation/container.rs b/bus-mapping/src/operation/container.rs index 7a52fb1fd3..5f30ace074 100644 --- a/bus-mapping/src/operation/container.rs +++ b/bus-mapping/src/operation/container.rs @@ -1,7 +1,7 @@ use super::{ AccountDestructedOp, AccountOp, CallContextOp, MemoryOp, Op, OpEnum, Operation, RWCounter, - StackOp, StorageOp, Target, TxAccessListAccountOp, TxAccessListAccountStorageOp, TxReceiptOp, - TxRefundOp, RW, + StackOp, StorageOp, Target, TxAccessListAccountOp, TxAccessListAccountStorageOp, TxLogOp, + TxReceiptOp, TxRefundOp, RW, }; use crate::exec_trace::OperationRef; use itertools::Itertools; @@ -42,6 +42,8 @@ pub struct OperationContainer { pub call_context: Vec>, /// Operations of TxReceiptOp pub tx_receipt: Vec>, + /// Operations of TxLogOp + pub tx_log: Vec>, } impl Default for OperationContainer { @@ -65,6 +67,7 @@ impl OperationContainer { account_destructed: Vec::new(), call_context: Vec::new(), tx_receipt: Vec::new(), + tx_log: Vec::new(), } } @@ -161,6 +164,10 @@ impl OperationContainer { self.tx_receipt.push(Operation::new(rwc, rw, op)); OperationRef::from((Target::TxReceipt, self.tx_receipt.len() - 1)) } + OpEnum::TxLog(op) => { + self.tx_log.push(Operation::new(rwc, rw, op)); + OperationRef::from((Target::TxLog, self.tx_log.len() - 1)) + } } } diff --git a/eth-types/src/evm_types/opcode_ids.rs b/eth-types/src/evm_types/opcode_ids.rs index 509d24c5a6..511092e192 100644 --- a/eth-types/src/evm_types/opcode_ids.rs +++ b/eth-types/src/evm_types/opcode_ids.rs @@ -4,6 +4,7 @@ use core::fmt::Debug; use lazy_static::lazy_static; use regex::Regex; use serde::{de, Deserialize, Serialize}; +use std::fmt; use std::str::FromStr; /// Opcode enum. One-to-one corresponding to an `u8` value. @@ -968,3 +969,9 @@ impl<'de> Deserialize<'de> for OpcodeId { OpcodeId::from_str(&s).map_err(de::Error::custom) } } + +impl fmt::Display for OpcodeId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs b/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs index 6d8ccc8590..bd09dac195 100644 --- a/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs +++ b/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs @@ -70,6 +70,7 @@ impl ExecutionGadget for CopyToLogGadget { 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), @@ -192,9 +193,14 @@ impl ExecutionGadget for CopyToLogGadget { 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 indice = step.rw_indices[rw_idx]; - rw_idx += 1; - block.rws[indice].memory_value() + let index = step.rw_indices[rw_idx]; + if is_persistent { + rw_idx += 2; + } else { + rw_idx += 1; + } + + block.rws[index].memory_value() } else { 0 }; @@ -259,8 +265,10 @@ pub mod test { 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 memory_idx_start = memory_rws.len(); 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 { @@ -275,6 +283,8 @@ pub mod test { 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 { @@ -290,12 +300,13 @@ pub mod test { 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); - let log_idx_start = log_rws.len(); log_rws.extend(txlog_rws); let aux_data = StepAuxiliaryData::new( @@ -306,17 +317,9 @@ pub mod test { CopyDetails::Log((is_persistent, tx_id, data_start_index)), ); - let memory_indices: Vec<(RwTableTag, usize)> = (memory_idx_start - ..memory_idx_start + bytes_left) - .map(|idx| (RwTableTag::Memory, idx)) - .collect(); - let log_indices: Vec<(RwTableTag, usize)> = (log_idx_start..log_idx_start + bytes_left) - .map(|idx| (RwTableTag::TxLog, idx)) - .collect(); - let step = ExecStep { execution_state: ExecutionState::CopyToLog, - rw_indices: [memory_indices.as_slice(), log_indices.as_slice()].concat(), + rw_indices, rw_counter, program_counter, stack_pointer, diff --git a/zkevm-circuits/src/evm_circuit/execution/logs.rs b/zkevm-circuits/src/evm_circuit/execution/logs.rs index f1f0a62182..1fbcbefe93 100644 --- a/zkevm-circuits/src/evm_circuit/execution/logs.rs +++ b/zkevm-circuits/src/evm_circuit/execution/logs.rs @@ -50,10 +50,8 @@ impl ExecutionGadget for LogGadget { // Pop mstart_address, msize from stack cb.stack_pop(mstart.clone().expr()); cb.stack_pop(msize.expr()); - // read tx id let tx_id = cb.call_context(None, CallContextFieldTag::TxId); - // constrain not in static call let is_static_call = cb.call_context(None, CallContextFieldTag::IsStatic); cb.require_zero("is_static_call is false", is_static_call.expr()); @@ -62,9 +60,12 @@ impl ExecutionGadget for LogGadget { // use call context's callee address as contract address let contract_address = cb.call_context(None, CallContextFieldTag::CalleeAddress); let is_persistent = cb.call_context(None, CallContextFieldTag::IsPersistent); + cb.require_boolean("is_persistent is bool", is_persistent.expr()); + cb.condition(is_persistent.expr(), |cb| { cb.tx_log_lookup( tx_id.expr(), + cb.curr.state.log_id.expr() + 1.expr(), TxLogFieldTag::Address, 0.expr(), contract_address.expr(), @@ -79,7 +80,13 @@ impl ExecutionGadget for LogGadget { cb.stack_pop(topic.expr()); }); cb.condition(topic_selectors[idx].expr() * is_persistent.expr(), |cb| { - cb.tx_log_lookup(tx_id.expr(), TxLogFieldTag::Topic, idx.expr(), topic.expr()); + cb.tx_log_lookup( + tx_id.expr(), + cb.curr.state.log_id.expr() + 1.expr(), + TxLogFieldTag::Topic, + idx.expr(), + topic.expr(), + ); }); } @@ -165,6 +172,7 @@ impl ExecutionGadget for LogGadget { + 8.expr() * from_bytes::expr(&msize.cells) + memory_expansion.gas_cost(); // State transition + let step_state_transition = StepStateTransition { rw_counter: Delta(cb.rw_counter_offset()), program_counter: Delta(1.expr()), @@ -203,6 +211,7 @@ impl ExecutionGadget for LogGadget { let [memory_start, msize] = [step.rw_indices[0], step.rw_indices[1]].map(|idx| block.rws[idx].stack_value()); + let memory_address = self.memory_address .assign(region, offset, memory_start, msize, block.randomness)?; @@ -215,18 +224,23 @@ impl ExecutionGadget for LogGadget { let topic_count = (opcode.as_u8() - OpcodeId::LOG0.as_u8()) as usize; assert!(topic_count <= 4); - let mut rws_stack_index = 1; + let is_persistent = call.is_persistent as u64; + let mut topic_stack_entry = if topic_count > 0 { + step.rw_indices[6 + call.is_persistent as usize] + } else { + // if topic_count == 0, this value will be no used anymore + (RwTableTag::Stack, 0usize) + }; + for i in 0..4 { let mut topic = Word::random_linear_combine([0; 32], block.randomness); if i < topic_count { - rws_stack_index += 1; topic = Word::random_linear_combine( - block.rws[(RwTableTag::Stack, rws_stack_index)] - .stack_value() - .to_le_bytes(), + block.rws[topic_stack_entry].stack_value().to_le_bytes(), block.randomness, ); self.topic_selectors[i].assign(region, offset, Some(F::one()))?; + topic_stack_entry.1 += 1; } else { self.topic_selectors[i].assign(region, offset, Some(F::zero()))?; } @@ -239,7 +253,7 @@ impl ExecutionGadget for LogGadget { self.is_static_call .assign(region, offset, Some(F::from(call.is_static as u64)))?; self.is_persistent - .assign(region, offset, Some(F::from(call.is_persistent as u64)))?; + .assign(region, offset, Some(F::from(is_persistent)))?; self.tx_id .assign(region, offset, Some(F::from(tx.id as u64)))?; @@ -249,373 +263,155 @@ impl ExecutionGadget for LogGadget { #[cfg(test)] mod test { - use crate::evm_circuit::{ - execution::copy_to_log::test::make_log_copy_steps, - step::ExecutionState, - table::{CallContextFieldTag, RwTableTag, TxLogFieldTag}, - test::{rand_bytes, run_test_circuit_incomplete_fixed_table}, - witness::{Block, Bytecode, Call, CodeSource, ExecStep, Rw, RwMap, Transaction}, - }; - use eth_types::{ - evm_types::{gas_utils::memory_expansion_gas_cost, GasCost, OpcodeId}, - ToBigEndian, Word, - }; - use halo2_proofs::arithmetic::BaseExt; - use halo2_proofs::pairing::bn256::Fr; - use std::convert::TryInto; - - // make dynamic byte code sequence base on topics - fn make_log_byte_code(memory_start: Word, msize: Word, topics: &[Word]) -> Bytecode { - let mut bytes: Vec = Vec::new(); - for topic in [topics, &[msize, memory_start]].concat() { - bytes.push(OpcodeId::PUSH32.as_u8()); - bytes = [bytes, topic.to_be_bytes().to_vec()].concat(); - } + use eth_types::{bytecode, evm_types::OpcodeId, Bytecode, Word}; + use mock::TestContext; + + use crate::test_util::run_test_circuits; + + //TODO:add is_persistent = false cases + #[test] + fn log_tests() { + // zero topic: log0 + test_log_ok(&[]); + // one topic: log1 + test_log_ok(&[Word::from(0xA0)]); + // two topics: log2 + test_log_ok(&[Word::from(0xA0), Word::from(0xef)]); + // three topics: log3 + test_log_ok(&[Word::from(0xA0), Word::from(0xef), Word::from(0xb0)]); + // four topics: log4 + test_log_ok(&[ + Word::from(0xA0), + Word::from(0xef), + Word::from(0xb0), + Word::from(0x37), + ]); + } - let codes = [ + #[test] + fn multi_log_tests() { + // zero topic: log0 + test_multi_log_ok(&[]); + // one topic: log1 + test_multi_log_ok(&[Word::from(0xA0)]); + // two topics: log2 + test_multi_log_ok(&[Word::from(0xA0), Word::from(0xef)]); + // three topics: log3 + test_multi_log_ok(&[Word::from(0xA0), Word::from(0xef), Word::from(0xb0)]); + // four topics: log4 + test_multi_log_ok(&[ + Word::from(0xA0), + Word::from(0xef), + Word::from(0xb0), + Word::from(0x37), + ]); + } + + // 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 log_codes = [ OpcodeId::LOG0, OpcodeId::LOG1, OpcodeId::LOG2, OpcodeId::LOG3, OpcodeId::LOG4, ]; - bytes.push(codes[topics.len()].as_u8()); - bytes.push(OpcodeId::STOP.as_u8()); - Bytecode::new(bytes) - } - - fn test_ok(memory_start: Word, msize: Word, topics: &[Word], is_persistent: bool) { - let randomness = Fr::rand(); - let bytecode = make_log_byte_code(memory_start, memory_start, topics); - let call_id = 1; - let tx_id = 1; - let memory_data = rand_bytes(msize.as_usize()); - let contract_address = Word::zero(); - let log_id: usize = 0; - let mut rws = RwMap( - [ - ( - RwTableTag::Stack, - vec![ - Rw::Stack { - rw_counter: 1, - is_write: false, - call_id, - stack_pointer: 1015, - value: memory_start, - }, - Rw::Stack { - rw_counter: 2, - is_write: false, - call_id, - stack_pointer: 1016, - value: msize, - }, - ], - ), - ( - RwTableTag::CallContext, - vec![ - Rw::CallContext { - rw_counter: 3, - is_write: false, - call_id, - field_tag: CallContextFieldTag::TxId, - value: Word::one(), - }, - Rw::CallContext { - rw_counter: 4, - is_write: false, - call_id, - field_tag: CallContextFieldTag::IsStatic, - value: Word::zero(), - }, - Rw::CallContext { - rw_counter: 5, - is_write: false, - call_id, - field_tag: CallContextFieldTag::CalleeAddress, - value: contract_address, - }, - Rw::CallContext { - rw_counter: 6, - is_write: false, - call_id, - field_tag: CallContextFieldTag::IsPersistent, - value: Word::from(is_persistent as u64), - }, - ], - ), - ] - .into(), - ); - let mut rw_counter = 6; - let mut stack_pointer = 1016; - // append dynamic length of topic reads from stack - let stack_rws: &mut Vec<_> = rws.0.entry(RwTableTag::Stack).or_insert_with(Vec::new); - let mut txlog_rws: Vec = Vec::new(); - - if is_persistent { - rw_counter += 1; - txlog_rws.push(Rw::TxLog { - rw_counter, - is_write: true, - tx_id, - log_id: log_id.try_into().unwrap(), - field_tag: TxLogFieldTag::Address, - index: 0, - value: contract_address, - }); - } - - // rw_counter += 1; - for (idx, topic) in topics.iter().enumerate() { - stack_pointer += 1; - rw_counter += 1; - stack_rws.push(Rw::Stack { - rw_counter, - is_write: false, - call_id, - stack_pointer, - value: *topic, - }); - if is_persistent { - rw_counter += 1; - txlog_rws.push(Rw::TxLog { - rw_counter, - is_write: true, - tx_id, - log_id: log_id.try_into().unwrap(), - field_tag: TxLogFieldTag::Topic, - index: idx, - value: *topic, - }); - } + let topic_count = topics.len(); + let cur_op_code = log_codes[topic_count]; + + let mstart = 0x00usize; + let msize = 0x20usize; + let mut code = Bytecode::default(); + // make dynamic topics push operations + for topic in topics { + code.push(32, *topic); } + code.push(32, Word::from(msize)); + code.push(32, Word::from(mstart)); + code.write_op(cur_op_code); + code.write_op(OpcodeId::STOP); + code_prepare.append(&code); + + assert_eq!( + run_test_circuits( + TestContext::<2, 1>::simple_ctx_with_bytecode(code_prepare).unwrap(), + None, + ), + Ok(()), + ); + } - rw_counter += 1; - let log_rws: &mut Vec<_> = rws.0.entry(RwTableTag::TxLog).or_insert_with(Vec::new); - log_rws.append(&mut txlog_rws); - - // dynamic length of topic writes to TxLog - let curr_memory_word_size = (memory_start.as_u64() + memory_start.as_u64() + 31) / 32; - let next_memory_word_size = if msize.is_zero() { - curr_memory_word_size - } else { - std::cmp::max( - curr_memory_word_size, - (memory_start.as_u64() + msize.as_u64() + 31) / 32, - ) - }; + // 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 memory_expension_gas = - memory_expansion_gas_cost(curr_memory_word_size, next_memory_word_size); - let topic_count = topics.len(); - // dynamic calculate topic_count - let gas_cost = GasCost::LOG.as_u64() - + GasCost::LOG.as_u64() * topic_count as u64 - + 8 * msize.as_u64() - + memory_expension_gas; - let codes = [ + let log_codes = [ OpcodeId::LOG0, OpcodeId::LOG1, OpcodeId::LOG2, OpcodeId::LOG3, OpcodeId::LOG4, ]; - let program_counter = ((2 + topic_count) * 33) as u64; - - let stack_indices: Vec<(RwTableTag, usize)> = (0..2 + topic_count) - .map(|idx| (RwTableTag::Stack, idx)) - .collect(); - let log_indices: Vec<(RwTableTag, usize)> = (0..1 + topic_count) - .map(|idx| (RwTableTag::TxLog, idx)) - .collect(); - - let mut steps = vec![ExecStep { - rw_indices: [ - stack_indices.as_slice(), - log_indices.as_slice(), - &[ - (RwTableTag::CallContext, 0), - (RwTableTag::CallContext, 1), - (RwTableTag::CallContext, 2), - (RwTableTag::CallContext, 3), - ], - ] - .concat(), - execution_state: ExecutionState::LOG, - rw_counter: 1, - program_counter, - stack_pointer: 1015, - gas_left: gas_cost, - gas_cost, - memory_size: curr_memory_word_size * 32, - opcode: Some(codes[topic_count]), - log_id, - ..Default::default() - }]; - - // memory rows - if !msize.is_zero() { - make_log_copy_steps( - call_id, - &memory_data, - memory_start.as_u64(), - memory_start.as_u64(), - msize.as_usize(), - program_counter + 1, - 1015 + (2 + topic_count), - next_memory_word_size * 32, - &mut rw_counter, - &mut rws, - &mut steps, - (log_id + 1).try_into().unwrap(), - is_persistent, - tx_id, - ); - } - steps.push(ExecStep { - execution_state: ExecutionState::STOP, - rw_counter, - program_counter: program_counter + 1, - stack_pointer: 1015 + (2 + topic_count), - opcode: Some(OpcodeId::STOP), - memory_size: next_memory_word_size * 32, - log_id: is_persistent as usize, - ..Default::default() - }); - - let block = Block { - randomness, - txs: vec![Transaction { - id: tx_id, - calls: vec![Call { - id: call_id, - is_root: false, - is_create: false, - is_persistent, - is_static: false, - code_source: CodeSource::Account(bytecode.hash), - ..Default::default() - }], - steps, - ..Default::default() - }], - rws, - bytecodes: vec![bytecode], - ..Default::default() - }; - assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); - } - - #[test] - fn log_gadget_simple() { - // is_persistent = true cases - // log1 - test_ok(Word::from(0x10), Word::from(2), &[Word::from(0xA0)], true); - // log2 - test_ok( - Word::from(0x10), - Word::from(2), - &[Word::from(0xA0), Word::from(0xef)], - true, - ); - // log3 - test_ok( - Word::from(0x10), - Word::from(2), - &[Word::from(0xA0), Word::from(0xef), Word::from(0xb0)], - true, - ); - // log4 - test_ok( - Word::from(0x10), - Word::from(2), - &[ - Word::from(0xA0), - Word::from(0xef), - Word::from(0xb0), - Word::from(0x37), - ], - true, - ); - // zero topic: log0 - test_ok(Word::from(0x10), Word::from(2), &[], true); - // is_persistent = false cases - // zero topic: log0 - test_ok(Word::from(0x10), Word::from(2), &[], false); - // log1 - test_ok(Word::from(0x10), Word::from(2), &[Word::from(0xA0)], false); - // log2 - test_ok( - Word::from(0x10), - Word::from(2), - &[Word::from(0xA0), Word::from(0xef)], - false, - ); - // log3 - test_ok( - Word::from(0x10), - Word::from(2), - &[Word::from(0xA0), Word::from(0xef), Word::from(0xb0)], - false, - ); - // log4 - test_ok( - Word::from(0x10), - Word::from(2), - &[ - Word::from(0xA0), - Word::from(0xef), - Word::from(0xb0), - Word::from(0x37), - ], - false, + let topic_count = topics.len(); + let cur_op_code = log_codes[topic_count]; + + let mut mstart = 0x00usize; + let mut msize = 0x10usize; + // first log op code + let mut code = Bytecode::default(); + // make dynamic topics push operations + for topic in topics { + code.push(32, *topic); + } + code.push(32, Word::from(msize)); + code.push(32, Word::from(mstart)); + code.write_op(cur_op_code); + + // second log op code + // prepare additinal bytes for memory reading + code.append(&prepare_code(pushdata, 0x20)); + mstart = 0x00usize; + // when mszie > 0x20 (32) needs multi copy steps + msize = 0x30usize; + for topic in topics { + code.push(32, *topic); + } + code.push(32, Word::from(msize)); + code.push(32, Word::from(mstart)); + code.write_op(cur_op_code); + + code.write_op(OpcodeId::STOP); + code_prepare.append(&code); + + assert_eq!( + run_test_circuits( + TestContext::<2, 1>::simple_ctx_with_bytecode(code_prepare).unwrap(), + None, + ), + Ok(()), ); } - #[test] - fn log_gadget_multi_step() { - // is_persistent = true cases - test_ok( - Word::from(0x10), - Word::from(128), - &[Word::from(0x100)], - true, - ); - test_ok( - Word::from(0x10), - Word::from(128), - &[Word::from(0xA0), Word::from(0xef)], - true, - ); - test_ok( - Word::from(0x10), - Word::from(128), - &[Word::from(0xA0), Word::from(0xef), Word::from(0xb0)], - true, - ); - // is_persistent = false cases - test_ok( - Word::from(0x10), - Word::from(128), - &[Word::from(0x100)], - false, - ); - test_ok( - Word::from(0x10), - Word::from(128), - &[Word::from(0xA0), Word::from(0xef)], - false, - ); - test_ok( - Word::from(0x10), - Word::from(128), - &[Word::from(0xA0), Word::from(0xef), Word::from(0xb0)], - false, - ); + /// prepare memory reading data + fn prepare_code(data: &str, offset: u32) -> Bytecode { + // data is in hex format + assert_eq!(data.bytes().len(), 32); + // prepare memory data + let pushdata = hex::decode(data).unwrap(); + return bytecode! { + // populate memory. + PUSH16(Word::from_big_endian(&pushdata)) + PUSH1(offset) // offset + MSTORE + }; } } diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index 65e2b244d8..32943e5cd3 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -1042,6 +1042,7 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { pub(crate) fn tx_log_lookup( &mut self, tx_id: Expression, + log_id: Expression, tag: TxLogFieldTag, index: Expression, value: Expression, @@ -1052,7 +1053,7 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { RwTableTag::TxLog, [ tx_id, - index + (1u64 << 8).expr() * self.curr.state.log_id.expr(), + index + (1u64 << 8).expr() * log_id, tag.expr(), 0.expr(), value, diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index cc230322b3..3e67029237 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -12,7 +12,7 @@ use crate::evm_circuit::{ use bus_mapping::{ circuit_input_builder::{self, StepAuxiliaryData}, error::{ExecError, OogError}, - operation::{self, AccountField, CallContextField, TxReceiptField}, + operation::{self, AccountField, CallContextField, TxLogField, TxReceiptField}, }; use eth_types::evm_types::OpcodeId; @@ -824,10 +824,19 @@ impl Rw { } Self::Account { value, .. } | Self::AccountStorage { value, .. } - | Self::Stack { value, .. } - | Self::TxLog { value, .. } => { + | Self::Stack { value, .. } => { RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness) } + + Self::TxLog { + field_tag, value, .. + } => match field_tag { + TxLogFieldTag::Topic => { + RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness) + } + _ => value.to_scalar().unwrap(), + }, + Self::TxAccessListAccount { is_warm, .. } | Self::TxAccessListAccountStorage { is_warm, .. } => F::from(*is_warm as u64), Self::AccountDestructed { is_destructed, .. } => F::from(*is_destructed as u64), @@ -1067,6 +1076,26 @@ impl From<&operation::OperationContainer> for RwMap { }) .collect(), ); + rws.insert( + RwTableTag::TxLog, + container + .tx_log + .iter() + .map(|op| Rw::TxLog { + rw_counter: op.rwc().into(), + is_write: op.rw().is_write(), + tx_id: op.op().tx_id, + log_id: op.op().log_id as u64, + field_tag: match op.op().field { + TxLogField::Address => TxLogFieldTag::Address, + TxLogField::Topic => TxLogFieldTag::Topic, + TxLogField::Data => TxLogFieldTag::Data, + }, + index: op.op().index, + value: op.op().value, + }) + .collect(), + ); rws.insert( RwTableTag::TxReceipt, container @@ -1202,6 +1231,7 @@ 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, } } @@ -1233,6 +1263,7 @@ fn step_convert(step: &circuit_input_builder::ExecStep) -> ExecStep { operation::Target::AccountDestructed => RwTableTag::AccountDestructed, operation::Target::CallContext => RwTableTag::CallContext, operation::Target::TxReceipt => RwTableTag::TxReceipt, + operation::Target::TxLog => RwTableTag::TxLog, }; (tag, x.as_usize()) })