diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index cc94351f49..90a3bd30f3 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -186,7 +186,7 @@ pub enum BytecodeFieldTag { Padding, } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, EnumIter)] pub enum TxLogFieldTag { Address = 1, Topic, diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index 32943e5cd3..29ce39fd88 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -1043,7 +1043,7 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { &mut self, tx_id: Expression, log_id: Expression, - tag: TxLogFieldTag, + field_tag: TxLogFieldTag, index: Expression, value: Expression, ) { @@ -1053,8 +1053,8 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { RwTableTag::TxLog, [ tx_id, - index + (1u64 << 8).expr() * log_id, - tag.expr(), + index + (1u64 << 32).expr() * field_tag.expr() + (1u64 << 48).expr() * log_id, + 0.expr(), 0.expr(), value, 0.expr(), diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index 4d8b28ae5e..083a68fe05 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -762,8 +762,19 @@ impl Rw { Self::Stack { stack_pointer, .. } => { Some(U256::from(*stack_pointer as u64).to_address()) } - Self::TxLog { log_id, index, .. } => { - Some((U256::from(*index as u64) + (U256::from(*log_id) << 8)).to_address()) + Self::TxLog { + log_id, + field_tag, + index, + .. + } => { + // make field_tag fit into one limb (16 bits) + Some( + (U256::from(*index as u64) + + (U256::from(*field_tag as u64) << 32) + + (U256::from(*log_id) << 48)) + .to_address(), + ) } Self::Start { .. } | Self::CallContext { .. } @@ -776,7 +787,6 @@ impl Rw { match self { Self::Account { field_tag, .. } => Some(*field_tag as u64), Self::CallContext { field_tag, .. } => Some(*field_tag as u64), - Self::TxLog { field_tag, .. } => Some(*field_tag as u64), Self::TxReceipt { field_tag, .. } => Some(*field_tag as u64), Self::Start { .. } | Self::Memory { .. } @@ -785,6 +795,7 @@ impl Rw { | Self::TxAccessListAccount { .. } | Self::TxAccessListAccountStorage { .. } | Self::TxRefund { .. } + | Self::TxLog { .. } | Self::AccountDestructed { .. } => None, } } diff --git a/zkevm-circuits/src/state_circuit/constraint_builder.rs b/zkevm-circuits/src/state_circuit/constraint_builder.rs index 79b8b8ab8f..096038c6d4 100644 --- a/zkevm-circuits/src/state_circuit/constraint_builder.rs +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -96,6 +96,9 @@ impl ConstraintBuilder { self.condition(q.tag_matches(RwTableTag::CallContext), |cb| { cb.build_call_context_constraints(q) }); + self.condition(q.tag_matches(RwTableTag::TxLog), |cb| { + cb.build_tx_log_constraints(q) + }); } fn build_general_constraints(&mut self, q: &Queries) { @@ -234,10 +237,92 @@ impl ConstraintBuilder { // TODO: Missing constraints } + fn build_tx_log_constraints(&mut self, q: &Queries) { + self.require_equal( + "is_write is always true for TxLog", + q.is_write.clone(), + 1.expr(), + ); + + // Comment out the following field_tag-related constraints as it is + // duplicated between state circuit and evm circuit. For more information, please refer to https://github.com/privacy-scaling-explorations/zkevm-specs/issues/221 + // cb.require_zero( + // "reset log_id to one when tx_id increases", + // q.tx_log_id() - 1.expr(), + // ); + + // constrain first field_tag is Address when tx id increases + // cb.require_equal( + // "first field_tag is Address when tx changes", + // q.field_tag_matches(TxLogFieldTag::Address), + // 1.expr(), + // ); + + // increase log_id when tag changes to Address within same tx + // self.condition( + // q.is_id_unchanged.clone() + // * q.is_tag_unchanged.clone() + // * q.field_tag_matches(TxLogFieldTag::Address), + // |cb| { + // cb.require_equal( + // "log_id = pre_log_id + 1", + // q.tx_log_id(), + // q.tx_log_id_prev() + 1.expr(), + // ) + // }, + // ); + + // within same tx, log_id will not change if field_tag != Address + // self.condition( + // q.is_id_unchanged.clone() + // * q.is_tag_unchanged.clone() + // * (1.expr() - q.field_tag_matches(TxLogFieldTag::Address)), + // |cb| { + // cb.require_equal( + // "log_id will not change if field_tag != Address within + // tx", q.tx_log_id(), + // q.tx_log_id_prev(), + // ) + // }, + // ); + + // constrain index is increasing by 1 when field_tag stay same + // self.condition( + // q.is_tag_unchanged.clone() * q.is_field_tag_unchanged.clone(), + // |cb| { + // cb.require_equal( + // "index = pre_index + 1", + // q.tx_log_index(), + // q.tx_log_index_prev() + 1.expr(), + // ) + // }, + // ); + + // self.condition(q.field_tag_matches(TxLogFieldTag::Address), |cb| { + // cb.require_zero("index is zero for address ", q.tx_log_index()) + // }); + + // if tag Topic appear, topic_index in range [0,4) + // self.condition(q.field_tag_matches(TxLogFieldTag::Topic), |cb| { + // let topic_index = q.tx_log_index(); + // cb.require_zero( + // "topic_index in range [0,4) ", + // topic_index.clone() + // * (1.expr() - topic_index.clone()) + // * (2.expr() - topic_index.clone()) + // * (3.expr() - topic_index), + // ) + // }); + } + fn require_zero(&mut self, name: &'static str, e: Expression) { self.constraints.push((name, self.condition.clone() * e)); } + fn require_equal(&mut self, name: &'static str, left: Expression, right: Expression) { + self.require_zero(name, left - right) + } + fn require_boolean(&mut self, name: &'static str, e: Expression) { self.require_zero(name, e.clone() * (1.expr() - e)) } @@ -313,11 +398,28 @@ impl Queries { fn rw_counter_change(&self) -> Expression { self.rw_counter.value.clone() - self.rw_counter.value_prev.clone() } + + fn tx_log_index(&self) -> Expression { + from_digits(&self.address.limbs[0..2], (1u64 << 16).expr()) + } + + fn tx_log_index_prev(&self) -> Expression { + from_digits(&self.address.limbs_prev[0..2], (1u64 << 16).expr()) + } + + fn tx_log_id(&self) -> Expression { + from_digits(&self.address.limbs[3..5], (1u64 << 16).expr()) + } + + fn tx_log_id_prev(&self) -> Expression { + from_digits(&self.address.limbs_prev[3..5], (1u64 << 16).expr()) + } } fn from_digits(digits: &[Expression], base: Expression) -> Expression { digits .iter() + .rev() .fold(Expression::Constant(F::zero()), |result, digit| { digit.clone() + result * base.clone() }) diff --git a/zkevm-circuits/src/state_circuit/multiple_precision_integer.rs b/zkevm-circuits/src/state_circuit/multiple_precision_integer.rs index a31b16c726..f727d27e2e 100644 --- a/zkevm-circuits/src/state_circuit/multiple_precision_integer.rs +++ b/zkevm-circuits/src/state_circuit/multiple_precision_integer.rs @@ -49,6 +49,7 @@ pub struct Queries { pub value: Expression, pub value_prev: Expression, // move this up, as it's not always needed. pub limbs: [Expression; N], + pub limbs_prev: [Expression; N], } impl Queries { @@ -57,6 +58,9 @@ impl Queries { value: meta.query_advice(c.value, Rotation::cur()), value_prev: meta.query_advice(c.value, Rotation::prev()), limbs: c.limbs.map(|limb| meta.query_advice(limb, Rotation::cur())), + limbs_prev: c + .limbs + .map(|limb| meta.query_advice(limb, Rotation::prev())), } } } diff --git a/zkevm-circuits/src/state_circuit/test.rs b/zkevm-circuits/src/state_circuit/test.rs index 891d88b977..2978f67980 100644 --- a/zkevm-circuits/src/state_circuit/test.rs +++ b/zkevm-circuits/src/state_circuit/test.rs @@ -1,6 +1,6 @@ use super::{StateCircuit, StateConfig}; use crate::evm_circuit::{ - table::{AccountFieldTag, CallContextFieldTag, RwTableTag}, + table::{AccountFieldTag, CallContextFieldTag, RwTableTag, TxLogFieldTag}, witness::{Rw, RwMap}, }; use crate::state_circuit::binary_number::AsBits; @@ -335,6 +335,82 @@ fn storage_key_rlc() { assert_eq!(verify(rows), Ok(())); } +#[test] +fn tx_log_ok() { + let rows = vec![ + Rw::Stack { + rw_counter: 1, + is_write: true, + call_id: 1, + stack_pointer: 1023, + value: U256::from(394500u64), + }, + Rw::TxLog { + rw_counter: 2, + is_write: true, + tx_id: 1, + log_id: 1, + field_tag: TxLogFieldTag::Address, + index: 0usize, + value: U256::one(), + }, + Rw::TxLog { + rw_counter: 3, + is_write: true, + tx_id: 1, + log_id: 1, + field_tag: TxLogFieldTag::Topic, + index: 0usize, + value: U256::one(), + }, + Rw::TxLog { + rw_counter: 4, + is_write: true, + tx_id: 1, + log_id: 1, + field_tag: TxLogFieldTag::Topic, + index: 1usize, + value: U256::from(2u64), + }, + Rw::TxLog { + rw_counter: 5, + is_write: true, + tx_id: 1, + log_id: 1, + field_tag: TxLogFieldTag::Data, + index: 0usize, + value: U256::from(3u64), + }, + Rw::TxLog { + rw_counter: 6, + is_write: true, + tx_id: 1, + log_id: 1, + field_tag: TxLogFieldTag::Data, + index: 1usize, + value: U256::from(3u64), + }, + ]; + + assert_eq!(verify(rows), Ok(())); +} + +#[test] +fn tx_log_bad() { + // is_write is false + let rows = vec![Rw::TxLog { + rw_counter: 2, + is_write: false, + tx_id: 1, + log_id: 1, + field_tag: TxLogFieldTag::Address, + index: 0usize, + value: U256::one(), + }]; + + assert_error_matches(verify(rows), "is_write is always true for TxLog"); +} + #[test] fn address_limb_mismatch() { let rows = vec![Rw::Account {