|
| 1 | +use std::marker::PhantomData; |
| 2 | + |
| 3 | +use crate::{ |
| 4 | + evm_circuit::{ |
| 5 | + param::{MAX_STEP_HEIGHT, N_PHASE2_COLUMNS, STEP_WIDTH}, |
| 6 | + step::{ExecutionState, Step}, |
| 7 | + util::{ |
| 8 | + constraint_builder::EVMConstraintBuilder, CachedRegion, StoredExpression, LOOKUP_CONFIG, |
| 9 | + }, |
| 10 | + Advice, Column, |
| 11 | + }, |
| 12 | + util::Challenges, |
| 13 | +}; |
| 14 | +use eth_types::{Field, Word}; |
| 15 | +pub(crate) use halo2_proofs::circuit::Layouter; |
| 16 | +use halo2_proofs::{ |
| 17 | + circuit::SimpleFloorPlanner, |
| 18 | + dev::MockProver, |
| 19 | + plonk::{Circuit, ConstraintSystem, Error, FirstPhase, SecondPhase, Selector, ThirdPhase}, |
| 20 | +}; |
| 21 | + |
| 22 | +pub(crate) trait MemoryGadgetContainer<F: Field>: Clone { |
| 23 | + fn configure_gadget_container(cb: &mut EVMConstraintBuilder<F>) -> Self |
| 24 | + where |
| 25 | + Self: Sized; |
| 26 | + |
| 27 | + fn assign_gadget_container( |
| 28 | + &self, |
| 29 | + witnesses: &[Word], |
| 30 | + region: &mut CachedRegion<'_, '_, F>, |
| 31 | + ) -> Result<(), Error>; |
| 32 | +} |
| 33 | + |
| 34 | +#[derive(Debug, Clone)] |
| 35 | +pub(crate) struct UnitTestMemoryGadgetBaseCircuitConfig<F: Field, G> |
| 36 | +where |
| 37 | + G: MemoryGadgetContainer<F>, |
| 38 | +{ |
| 39 | + q_usable: Selector, |
| 40 | + advices: [Column<Advice>; STEP_WIDTH], |
| 41 | + step: Step<F>, |
| 42 | + stored_expressions: Vec<StoredExpression<F>>, |
| 43 | + memory_gadget_container: G, |
| 44 | + _marker: PhantomData<F>, |
| 45 | +} |
| 46 | + |
| 47 | +pub(crate) struct UnitTestMemoryGadgetBaseCircuit<G> { |
| 48 | + witnesses: Vec<Word>, |
| 49 | + _marker: PhantomData<G>, |
| 50 | +} |
| 51 | + |
| 52 | +impl<G> UnitTestMemoryGadgetBaseCircuit<G> { |
| 53 | + fn new(witnesses: Vec<Word>) -> Self { |
| 54 | + UnitTestMemoryGadgetBaseCircuit { |
| 55 | + witnesses, |
| 56 | + _marker: PhantomData, |
| 57 | + } |
| 58 | + } |
| 59 | +} |
| 60 | + |
| 61 | +impl<F: Field, G: MemoryGadgetContainer<F>> Circuit<F> for UnitTestMemoryGadgetBaseCircuit<G> { |
| 62 | + type Config = (UnitTestMemoryGadgetBaseCircuitConfig<F, G>, Challenges); |
| 63 | + type FloorPlanner = SimpleFloorPlanner; |
| 64 | + type Params = (); |
| 65 | + |
| 66 | + fn without_witnesses(&self) -> Self { |
| 67 | + UnitTestMemoryGadgetBaseCircuit { |
| 68 | + witnesses: vec![], |
| 69 | + _marker: PhantomData, |
| 70 | + } |
| 71 | + } |
| 72 | + |
| 73 | + fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config { |
| 74 | + let challenges = Challenges::construct(meta); |
| 75 | + let challenges_exprs = challenges.exprs(meta); |
| 76 | + |
| 77 | + let q_usable = meta.selector(); |
| 78 | + |
| 79 | + let lookup_column_count: usize = LOOKUP_CONFIG.iter().map(|(_, count)| *count).sum(); |
| 80 | + let advices = [(); STEP_WIDTH] |
| 81 | + .iter() |
| 82 | + .enumerate() |
| 83 | + .map(|(n, _)| { |
| 84 | + if n < lookup_column_count { |
| 85 | + meta.advice_column_in(ThirdPhase) |
| 86 | + } else if n < lookup_column_count + N_PHASE2_COLUMNS { |
| 87 | + meta.advice_column_in(SecondPhase) |
| 88 | + } else { |
| 89 | + meta.advice_column_in(FirstPhase) |
| 90 | + } |
| 91 | + }) |
| 92 | + .collect::<Vec<_>>() |
| 93 | + .try_into() |
| 94 | + .unwrap(); |
| 95 | + |
| 96 | + let step_curr = Step::new(meta, advices, 0); |
| 97 | + let step_next = Step::new(meta, advices, MAX_STEP_HEIGHT); |
| 98 | + let mut cb = EVMConstraintBuilder::new( |
| 99 | + meta, |
| 100 | + step_curr.clone(), |
| 101 | + step_next, |
| 102 | + &challenges_exprs, |
| 103 | + ExecutionState::STOP, |
| 104 | + ); |
| 105 | + let memory_gadget_container = G::configure_gadget_container(&mut cb); |
| 106 | + let (constraints, stored_expressions, _, _) = cb.build(); |
| 107 | + |
| 108 | + if !constraints.step.is_empty() { |
| 109 | + let step_constraints = constraints.step; |
| 110 | + meta.create_gate("MemoryGadgetTestContainer", |meta| { |
| 111 | + let q_usable = meta.query_selector(q_usable); |
| 112 | + step_constraints |
| 113 | + .into_iter() |
| 114 | + .map(move |(name, constraint)| (name, q_usable.clone() * constraint)) |
| 115 | + }); |
| 116 | + } |
| 117 | + |
| 118 | + ( |
| 119 | + UnitTestMemoryGadgetBaseCircuitConfig::<F, G> { |
| 120 | + q_usable, |
| 121 | + advices, |
| 122 | + step: step_curr, |
| 123 | + stored_expressions, |
| 124 | + memory_gadget_container, |
| 125 | + _marker: PhantomData, |
| 126 | + }, |
| 127 | + challenges, |
| 128 | + ) |
| 129 | + } |
| 130 | + |
| 131 | + fn synthesize( |
| 132 | + &self, |
| 133 | + config: Self::Config, |
| 134 | + mut layouter: impl Layouter<F>, |
| 135 | + ) -> Result<(), Error> { |
| 136 | + let (config, challenges) = config; |
| 137 | + let challenge_values = challenges.values(&mut layouter); |
| 138 | + layouter.assign_region( |
| 139 | + || "assign test container", |
| 140 | + |mut region| { |
| 141 | + let offset = 0; |
| 142 | + config.q_usable.enable(&mut region, offset)?; |
| 143 | + let cached_region = &mut CachedRegion::<'_, '_, F>::new( |
| 144 | + &mut region, |
| 145 | + &challenge_values, |
| 146 | + config.advices.to_vec(), |
| 147 | + MAX_STEP_HEIGHT * 3, |
| 148 | + offset, |
| 149 | + ); |
| 150 | + config.step.state.execution_state.assign( |
| 151 | + cached_region, |
| 152 | + offset, |
| 153 | + ExecutionState::STOP as usize, |
| 154 | + )?; |
| 155 | + config |
| 156 | + .memory_gadget_container |
| 157 | + .assign_gadget_container(&self.witnesses, cached_region)?; |
| 158 | + for stored_expr in &config.stored_expressions { |
| 159 | + stored_expr.assign(cached_region, offset)?; |
| 160 | + } |
| 161 | + Ok(()) |
| 162 | + }, |
| 163 | + )?; |
| 164 | + |
| 165 | + Ok(()) |
| 166 | + } |
| 167 | +} |
| 168 | + |
| 169 | +/// This fn test_math_gadget_container takes math gadget container and run a |
| 170 | +/// container based circuit. All test logic should be included in the container, |
| 171 | +/// and witness words are used for both input & output data. How to deal with |
| 172 | +/// the witness words is left to each container. |
| 173 | +pub(crate) fn test_memory_gadget_container<F: Field, G: MemoryGadgetContainer<F>>( |
| 174 | + witnesses: Vec<Word>, |
| 175 | + expected_success: bool, |
| 176 | +) { |
| 177 | + const K: usize = 12; |
| 178 | + let circuit = UnitTestMemoryGadgetBaseCircuit::<G>::new(witnesses); |
| 179 | + |
| 180 | + let prover = MockProver::<F>::run(K as u32, &circuit, vec![]).unwrap(); |
| 181 | + if expected_success { |
| 182 | + assert_eq!(prover.verify(), Ok(())); |
| 183 | + } else { |
| 184 | + assert_ne!(prover.verify(), Ok(())); |
| 185 | + } |
| 186 | +} |
| 187 | + |
| 188 | +pub(crate) fn required_bytes(value: Word) -> usize { |
| 189 | + for i in (0..32).rev() { |
| 190 | + let byte = (value >> (i * 8)) & Word::from(0xff); |
| 191 | + if byte != Word::zero() { |
| 192 | + return i + 1; |
| 193 | + } |
| 194 | + } |
| 195 | + 0 |
| 196 | +} |
| 197 | + |
| 198 | +/// A simple macro for less code & better readability |
| 199 | +macro_rules! try_test { |
| 200 | + ($base_class:ty, $witnesses:expr, $expect_success:expr $(,)?) => {{ |
| 201 | + test_memory_gadget_container::<Fr, $base_class>($witnesses.to_vec(), $expect_success) |
| 202 | + }}; |
| 203 | +} |
| 204 | + |
| 205 | +#[cfg(test)] |
| 206 | +pub(crate) use try_test; |
0 commit comments