diff --git a/zkevm-circuits/src/state_circuit/constraint_builder.rs b/zkevm-circuits/src/state_circuit/constraint_builder.rs index c8692726fe..f183931fd6 100644 --- a/zkevm-circuits/src/state_circuit/constraint_builder.rs +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -98,37 +98,31 @@ impl ConstraintBuilder { pub fn build(&mut self, q: &Queries) { self.build_general_constraints(q); - self.condition(q.tag_matches(RwTableTag::Start), |cb| { - cb.build_start_constraints(q) - }); - self.condition(q.tag_matches(RwTableTag::Memory), |cb| { - cb.build_memory_constraints(q) - }); - self.condition(q.tag_matches(RwTableTag::Stack), |cb| { - cb.build_stack_constraints(q) - }); - self.condition(q.tag_matches(RwTableTag::AccountStorage), |cb| { - cb.build_account_storage_constraints(q) - }); - self.condition(q.tag_matches(RwTableTag::TxAccessListAccount), |cb| { - cb.build_tx_access_list_account_constraints(q) - }); - self.condition( - q.tag_matches(RwTableTag::TxAccessListAccountStorage), - |cb| cb.build_tx_access_list_account_storage_constraints(q), - ); - self.condition(q.tag_matches(RwTableTag::TxRefund), |cb| { - cb.build_tx_refund_constraints(q) - }); - self.condition(q.tag_matches(RwTableTag::Account), |cb| { - cb.build_account_constraints(q) - }); - 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) - }); + self.build_conditional_constraints(q); + } + + fn build_conditional_constraints(&mut self, q: &Queries) { + for tag in RwTableTag::iter() { + let build = match tag { + RwTableTag::Start => Self::build_start_constraints, + RwTableTag::Memory => Self::build_memory_constraints, + RwTableTag::Stack => Self::build_stack_constraints, + RwTableTag::AccountStorage => Self::build_account_storage_constraints, + RwTableTag::TxAccessListAccount => Self::build_tx_access_list_account_constraints, + RwTableTag::TxAccessListAccountStorage => { + Self::build_tx_access_list_account_storage_constraints + } + RwTableTag::TxRefund => Self::build_tx_refund_constraints, + RwTableTag::Account => Self::build_account_constraints, + RwTableTag::CallContext => Self::build_call_context_constraints, + RwTableTag::TxLog => Self::build_tx_log_constraints, + RwTableTag::TxReceipt => Self::build_tx_receipt_constraints, + RwTableTag::AccountTransientStorage => { + Self::build_account_transient_storage_constraints + } + }; + self.condition(q.tag_matches(tag), |cb| build(cb, q)); + } } fn build_general_constraints(&mut self, q: &Queries) { @@ -509,9 +503,7 @@ impl ConstraintBuilder { } fn build_tx_receipt_constraints(&mut self, q: &Queries) { - // TODO: implement TxReceipt constraints - self.require_equal("TxReceipt rows not implemented", 1.expr(), 0.expr()); - + // TODO: finish TxReceipt constraints self.require_equal( "state_root is unchanged for TxReceipt", q.state_root(), @@ -523,6 +515,33 @@ impl ConstraintBuilder { ); } + fn build_account_transient_storage_constraints(&mut self, q: &Queries) { + // 5.0. `field_tag` is 0 + self.require_zero("field_tag is 0 for AccountTransientStorage", q.field_tag()); + + // 5.1. `initial_value` is 0 + self.require_zero( + "initial AccountTransientStorage value is 0", + q.initial_value(), + ); + + // 5.2. `value` column at previous rotation equals `value_prev` at current rotation + self.condition(q.not_first_access.clone(), |cb| { + cb.require_equal( + "value column at Rotation::prev() equals value_prev at Rotation::cur()", + q.rw_table.value_prev.clone(), + q.value_prev_column(), + ); + }); + + // 5.3. `state root` is the same + self.require_equal( + "state_root is unchanged for AccountTransientStorage", + q.state_root(), + q.state_root_prev(), + ); + } + fn require_zero(&mut self, name: &'static str, e: Expression) { self.constraints.push((name, self.condition.clone() * e)); } diff --git a/zkevm-circuits/src/state_circuit/dev.rs b/zkevm-circuits/src/state_circuit/dev.rs index 031bc33c5e..4a186f199f 100644 --- a/zkevm-circuits/src/state_circuit/dev.rs +++ b/zkevm-circuits/src/state_circuit/dev.rs @@ -88,6 +88,8 @@ pub enum AdviceColumn { // NonEmptyWitness is the BatchedIsZero chip witness that contains the // inverse of the non-zero value if any in [committed_value, value] NonEmptyWitness, + StateRoot, + FieldTag, } impl AdviceColumn { @@ -118,6 +120,8 @@ impl AdviceColumn { Self::InitialValue => config.initial_value, Self::IsZero => config.is_non_exist.is_zero, Self::NonEmptyWitness => config.is_non_exist.nonempty_witness, + Self::StateRoot => config.state_root, + Self::FieldTag => config.rw_table.field_tag, } } } diff --git a/zkevm-circuits/src/state_circuit/test.rs b/zkevm-circuits/src/state_circuit/test.rs index 2f5472db43..e658e44c6b 100644 --- a/zkevm-circuits/src/state_circuit/test.rs +++ b/zkevm-circuits/src/state_circuit/test.rs @@ -966,6 +966,129 @@ fn bad_initial_tx_log_value() { ); } +#[test] +fn account_transient_storage_nonzero_field_tag() { + let rows = vec![Rw::AccountTransientStorage { + rw_counter: 1, + is_write: false, + account_address: Address::default(), + storage_key: Word::from(2342), + tx_id: 800, + value: U256::zero(), + value_prev: U256::zero(), + }]; + + let nonzero_field_tag = Fr::one(); + let nonempty_witness = (nonzero_field_tag - Fr::from(AccountFieldTag::CodeHash as u64)) + .invert() + .unwrap(); + + let overrides = HashMap::from([ + ((AdviceColumn::FieldTag, 0), nonzero_field_tag), + ((AdviceColumn::IsZero, 0), Fr::zero()), + ((AdviceColumn::NonEmptyWitness, 0), nonempty_witness), + ]); + + assert_error_matches( + verify_with_overrides(rows, overrides), + "field_tag is 0 for AccountTransientStorage", + ); +} + +#[test] +fn account_transient_storage_nonzero_initial_value() { + let rows = vec![Rw::AccountTransientStorage { + rw_counter: 1, + is_write: true, + account_address: Address::default(), + storage_key: Word::from(2342), + tx_id: 800, + value: U256::one(), + value_prev: U256::zero(), + }]; + + let overrides = HashMap::from([ + ((AdviceColumn::InitialValue, 0), Fr::one()), + ((AdviceColumn::ValuePrev, 0), Fr::one()), + ]); + + assert_error_matches( + verify_with_overrides(rows, overrides), + "initial AccountTransientStorage value is 0", + ); +} + +#[test] +fn account_transient_storage_nonzero_value_first_access_read() { + let rows = vec![Rw::AccountTransientStorage { + rw_counter: 1, + is_write: false, + account_address: Address::default(), + storage_key: Word::from(2342), + tx_id: 800, + value: U256::zero(), + value_prev: U256::zero(), + }]; + + let overrides = HashMap::from([((AdviceColumn::Value, 0), Fr::from(244))]); + + assert_error_matches( + verify_with_overrides(rows, overrides), + "first access reads don't change value", + ); +} + +#[test] +fn account_transient_storage_value_prev_mismatch() { + let rows = vec![ + Rw::AccountTransientStorage { + rw_counter: 1, + is_write: true, + account_address: Address::default(), + storage_key: Word::from(2342), + tx_id: 800, + value: U256::one(), + value_prev: U256::zero(), + }, + Rw::AccountTransientStorage { + rw_counter: 2, + is_write: true, + account_address: Address::default(), + storage_key: Word::from(2342), + tx_id: 800, + value: U256::one(), + value_prev: U256::one(), + }, + ]; + + let overrides = HashMap::from([((AdviceColumn::ValuePrev, 1), Fr::from(3))]); + + assert_error_matches( + verify_with_overrides(rows, overrides), + "value column at Rotation::prev() equals value_prev at Rotation::cur()", + ); +} + +#[test] +fn account_transient_storage_changed_state_root() { + let rows = vec![Rw::AccountTransientStorage { + rw_counter: 1, + is_write: false, + account_address: Address::default(), + storage_key: Word::from(2342), + tx_id: 800, + value: U256::zero(), + value_prev: U256::zero(), + }]; + + let overrides = HashMap::from([((AdviceColumn::StateRoot, 0), Fr::from(20))]); + + assert_error_matches( + verify_with_overrides(rows, overrides), + "state_root is unchanged for AccountTransientStorage", + ); +} + #[test] fn variadic_size_check() { let mut rows = vec![