From 4eee161d7d3f43b7c88d5ae4c2ab45533b12a600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Wed, 15 Feb 2023 07:37:09 +0100 Subject: [PATCH 1/3] test/keccak-normalize-table: unittest for normalize functions --- zkevm-circuits/src/keccak_circuit/util.rs | 156 +++++++++++++++++++++- 1 file changed, 150 insertions(+), 6 deletions(-) diff --git a/zkevm-circuits/src/keccak_circuit/util.rs b/zkevm-circuits/src/keccak_circuit/util.rs index 5879f98535..880cb7ca1b 100644 --- a/zkevm-circuits/src/keccak_circuit/util.rs +++ b/zkevm-circuits/src/keccak_circuit/util.rs @@ -336,29 +336,50 @@ pub fn get_degree() -> usize { } /// Returns how many bits we can process in a single lookup given the range of -/// values the bit can have and the height of the circuit. +/// values the bit can have and the height of the circuit (via KECCAK_DEGREE). pub fn get_num_bits_per_lookup(range: usize) -> usize { + let log_height = get_degree(); + get_num_bits_per_lookup_impl(range, log_height) +} + +// Implementation of the above without environment dependency. +fn get_num_bits_per_lookup_impl(range: usize, log_height: usize) -> usize { let num_unusable_rows = 31; - let degree = get_degree() as u32; + let height = 2usize.pow(log_height as u32); let mut num_bits = 1; - while range.pow(num_bits + 1) + num_unusable_rows <= 2usize.pow(degree) { + while range.pow(num_bits + 1) + num_unusable_rows <= height { num_bits += 1; } num_bits as usize } -/// Loads a normalization table with the given parameters +/// Loads a normalization table with the given parameters and KECCAK_DEGREE. pub fn load_normalize_table( layouter: &mut impl Layouter, name: &str, tables: &[TableColumn; 2], range: u64, ) -> Result<(), Error> { - let part_size = get_num_bits_per_lookup(range as usize); + let log_height = get_degree(); + load_normalize_table_impl(layouter, name, tables, range, log_height) +} + +// Implementation of the above without environment dependency. +fn load_normalize_table_impl( + layouter: &mut impl Layouter, + name: &str, + tables: &[TableColumn; 2], + range: u64, + log_height: usize, +) -> Result<(), Error> { + assert!(range <= BIT_SIZE as u64); + let num_bits = get_num_bits_per_lookup_impl(range as usize, log_height); + layouter.assign_table( || format!("{} table", name), |mut table| { - for (offset, perm) in (0..part_size) + // Iterate over all combinations of "bits", each taking values in the range. + for (offset, perm) in (0..num_bits) .map(|_| 0u64..range) .multi_cartesian_product() .enumerate() @@ -462,3 +483,126 @@ pub(crate) fn extract_field(value: Value) -> F { }); field } + +#[cfg(test)] +mod tests { + use super::*; + use halo2_proofs::circuit::SimpleFloorPlanner; + use halo2_proofs::dev::{CellValue, MockProver}; + use halo2_proofs::halo2curves::bn256::Fr as F; + use halo2_proofs::plonk::{Circuit, ConstraintSystem}; + use itertools::Itertools; + use std::iter::zip; + + #[test] + fn pack_table() { + for (idx, expected) in [(0, 0), (128, 1 << 7 * 3), (129, (1 << 7 * 3) | 1)] { + let packed: F = pack(&into_bits(&[idx as u8])); + assert_eq!(packed, F::from(expected)); + } + } + + #[test] + fn num_bits_per_lookup() { + // Typical values. + assert_eq!(get_num_bits_per_lookup_impl(3, 19), 11); + assert_eq!(get_num_bits_per_lookup_impl(4, 19), 9); + assert_eq!(get_num_bits_per_lookup_impl(6, 19), 7); + // The largest imaginable value does not overflow u64. + assert_eq!(get_num_bits_per_lookup_impl(3, 32) * BIT_COUNT, 60); + } + + #[test] + fn normalize_table() { + normalize_table_impl(3, 10); + normalize_table_impl(4, 10); + normalize_table_impl(6, 10); + normalize_table_impl(6, 19); + } + + fn normalize_table_impl(range: usize, log_height: usize) { + let circuit = NormalizeTestCircuit { range, log_height }; + let prover = MockProver::::run(log_height as u32, &circuit, vec![]).unwrap(); + let unused_rows = 6; + let used_rows = (1 << log_height) - unused_rows; + + // Get the generated lookup table with the form: table[row] = (input, output). + let table = { + let t = prover.fixed(); + assert_eq!(t.len(), 2); + zip(&t[0], &t[1]) + .take(used_rows) + .map(|(inp, out)| (unwrap_u64(inp), unwrap_u64(out))) + .collect::>() + }; + + // On all rows, all inputs/outputs are correct, i.e. they have the same low bit. + assert_eq!(BIT_COUNT, 3); + for (inp, out) in table.iter() { + for pos in (0..64).step_by(BIT_COUNT) { + assert_eq!((inp >> pos) & 1, (out >> pos) & (4 + 2 + 1)); + } + } + + // All possible combinations of inputs are there. + let unique_rows = table.iter().unique().count(); + let num_bits = get_num_bits_per_lookup_impl(range, log_height); + let num_entries = range.pow(num_bits as u32); + assert_eq!(unique_rows, num_entries); + + // Check the unused rows. + for column in 0..2 { + for row in used_rows..prover.fixed()[0].len() { + assert_eq!(prover.fixed()[column][row], CellValue::Unassigned); + } + } + } + + // ---- Helpers ---- + + #[derive(Clone)] + struct NormalizeTestCircuit { + range: usize, + log_height: usize, + } + + impl Circuit for NormalizeTestCircuit { + type Config = [TableColumn; 2]; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + self.clone() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let normalize_table = array_init::array_init(|_| meta.lookup_table_column()); + normalize_table + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + load_normalize_table_impl( + &mut layouter, + "normalize", + &config, + self.range as u64, + self.log_height, + )?; + Ok(()) + } + } + + fn unwrap_u64(cv: &CellValue) -> u64 { + match *cv { + CellValue::Assigned(f) => { + let f = f.get_lower_128(); + assert_eq!(f >> 64, 0); + f as u64 + } + _ => panic!("the cell should be assigned"), + } + } +} From 8f764f33d02a215ad4588c65159e142fcb944b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Wed, 15 Feb 2023 09:47:37 +0100 Subject: [PATCH 2/3] test/keccak-normalize-table: test for the Chi table --- zkevm-circuits/src/keccak_circuit/util.rs | 132 ++++++++++++++++------ 1 file changed, 97 insertions(+), 35 deletions(-) diff --git a/zkevm-circuits/src/keccak_circuit/util.rs b/zkevm-circuits/src/keccak_circuit/util.rs index 880cb7ca1b..54a9dc4ab5 100644 --- a/zkevm-circuits/src/keccak_circuit/util.rs +++ b/zkevm-circuits/src/keccak_circuit/util.rs @@ -496,7 +496,8 @@ mod tests { #[test] fn pack_table() { - for (idx, expected) in [(0, 0), (128, 1 << 7 * 3), (129, (1 << 7 * 3) | 1)] { + let msb = 1 << (7 * 3); + for (idx, expected) in [(0, 0), (1, 1), (128, msb), (129, msb | 1)] { let packed: F = pack(&into_bits(&[idx as u8])); assert_eq!(packed, F::from(expected)); } @@ -507,6 +508,7 @@ mod tests { // Typical values. assert_eq!(get_num_bits_per_lookup_impl(3, 19), 11); assert_eq!(get_num_bits_per_lookup_impl(4, 19), 9); + assert_eq!(get_num_bits_per_lookup_impl(5, 19), 8); assert_eq!(get_num_bits_per_lookup_impl(6, 19), 7); // The largest imaginable value does not overflow u64. assert_eq!(get_num_bits_per_lookup_impl(3, 32) * BIT_COUNT, 60); @@ -521,20 +523,11 @@ mod tests { } fn normalize_table_impl(range: usize, log_height: usize) { - let circuit = NormalizeTestCircuit { range, log_height }; - let prover = MockProver::::run(log_height as u32, &circuit, vec![]).unwrap(); - let unused_rows = 6; - let used_rows = (1 << log_height) - unused_rows; - - // Get the generated lookup table with the form: table[row] = (input, output). - let table = { - let t = prover.fixed(); - assert_eq!(t.len(), 2); - zip(&t[0], &t[1]) - .take(used_rows) - .map(|(inp, out)| (unwrap_u64(inp), unwrap_u64(out))) - .collect::>() - }; + let table = build_table(&TableTestCircuit { + range, + log_height, + normalize_else_chi: true, + }); // On all rows, all inputs/outputs are correct, i.e. they have the same low bit. assert_eq!(BIT_COUNT, 3); @@ -543,30 +536,89 @@ mod tests { assert_eq!((inp >> pos) & 1, (out >> pos) & (4 + 2 + 1)); } } + } - // All possible combinations of inputs are there. - let unique_rows = table.iter().unique().count(); - let num_bits = get_num_bits_per_lookup_impl(range, log_height); - let num_entries = range.pow(num_bits as u32); - assert_eq!(unique_rows, num_entries); + #[test] + fn chi_table() { + // Check the base pattern for all combinations of bits. + for i in 0..16_usize { + let (a, b, c, d) = (i & 1, (i >> 1) & 1, (i >> 2) & 1, (i >> 3) & 1); + assert_eq!( + CHI_BASE_LOOKUP_TABLE[3 - 2 * a + b - c], + (a ^ ((!b) & c)) as u8 + ); + assert_eq!( + CHI_EXT_LOOKUP_TABLE[5 - 2 * a - b + c - 2 * d], + (a ^ ((!b) & c) ^ d) as u8 + ); + } - // Check the unused rows. - for column in 0..2 { - for row in used_rows..prover.fixed()[0].len() { - assert_eq!(prover.fixed()[column][row], CellValue::Unassigned); + // Check the table with multiple parts per row. + chi_table_impl(10); + chi_table_impl(19); + } + + fn chi_table_impl(log_height: usize) { + let range = 5; // CHI_BASE_LOOKUP_RANGE + let table = build_table(&TableTestCircuit { + range, + log_height, + normalize_else_chi: false, + }); + + // On all rows, all input/output pairs match the base table. + for (inp, out) in table.iter() { + for pos in (0..64).step_by(BIT_COUNT) { + let inp = ((inp >> pos) & 7) as usize; + let out = ((out >> pos) & 7) as u8; + assert_eq!(out, CHI_BASE_LOOKUP_TABLE[inp]); } } } // ---- Helpers ---- + fn build_table(circuit: &TableTestCircuit) -> Vec<(u64, u64)> { + let prover = MockProver::::run(circuit.log_height as u32, circuit, vec![]).unwrap(); + + let columns = prover.fixed(); + assert_eq!(columns.len(), 2); + let unused_rows = 6; + let used_rows = (1 << circuit.log_height) - unused_rows; + + // Check the unused rows. + for io in zip(&columns[0], &columns[1]).skip(used_rows) { + assert_eq!(io, (&CellValue::Unassigned, &CellValue::Unassigned)); + } + + // Get the generated lookup table with the form: table[row] = (input, output). + let table = zip(&columns[0], &columns[1]) + .take(used_rows) + .map(|(inp, out)| (unwrap_u64(inp), unwrap_u64(out))) + .collect::>(); + + // All possible combinations of inputs are there. + let unique_rows = table.iter().unique().count(); + assert_eq!(unique_rows, circuit.expected_num_entries()); + + table + } + #[derive(Clone)] - struct NormalizeTestCircuit { + struct TableTestCircuit { range: usize, log_height: usize, + normalize_else_chi: bool, + } + + impl TableTestCircuit { + fn expected_num_entries(&self) -> usize { + let num_bits = get_num_bits_per_lookup_impl(self.range, self.log_height); + self.range.pow(num_bits as u32) + } } - impl Circuit for NormalizeTestCircuit { + impl Circuit for TableTestCircuit { type Config = [TableColumn; 2]; type FloorPlanner = SimpleFloorPlanner; @@ -575,8 +627,7 @@ mod tests { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let normalize_table = array_init::array_init(|_| meta.lookup_table_column()); - normalize_table + array_init::array_init(|_| meta.lookup_table_column()) } fn synthesize( @@ -584,13 +635,24 @@ mod tests { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { - load_normalize_table_impl( - &mut layouter, - "normalize", - &config, - self.range as u64, - self.log_height, - )?; + if self.normalize_else_chi { + load_normalize_table_impl( + &mut layouter, + "normalize", + &config, + self.range as u64, + self.log_height, + )?; + } else { + let num_bits = get_num_bits_per_lookup_impl(self.range, self.log_height); + load_lookup_table( + &mut layouter, + "chi base", + &config, + num_bits, + &CHI_BASE_LOOKUP_TABLE, + )?; + } Ok(()) } } From 62bf83b9ed7b2a0664713039c653b9fc8183189a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Sat, 18 Feb 2023 05:47:31 +0100 Subject: [PATCH 3/3] test/keccak-normalize-table: move test to the new table module --- zkevm-circuits/src/keccak_circuit/table.rs | 165 +++++++++++++++++++++ zkevm-circuits/src/keccak_circuit/util.rs | 163 +------------------- 2 files changed, 167 insertions(+), 161 deletions(-) diff --git a/zkevm-circuits/src/keccak_circuit/table.rs b/zkevm-circuits/src/keccak_circuit/table.rs index 0b29759721..3b3be3511e 100644 --- a/zkevm-circuits/src/keccak_circuit/table.rs +++ b/zkevm-circuits/src/keccak_circuit/table.rs @@ -127,3 +127,168 @@ pub(crate) fn load_lookup_table( }, ) } + +#[cfg(test)] +mod tests { + use super::*; + use halo2_proofs::circuit::SimpleFloorPlanner; + use halo2_proofs::dev::{CellValue, MockProver}; + use halo2_proofs::halo2curves::bn256::Fr as F; + use halo2_proofs::plonk::{Circuit, ConstraintSystem}; + use itertools::Itertools; + use std::iter::zip; + + #[test] + fn normalize_table() { + normalize_table_impl(3, 10); + normalize_table_impl(4, 10); + normalize_table_impl(6, 10); + normalize_table_impl(6, 19); + } + + fn normalize_table_impl(range: usize, log_height: usize) { + let table = build_table(&TableTestCircuit { + range, + log_height, + normalize_else_chi: true, + }); + + // On all rows, all inputs/outputs are correct, i.e. they have the same low bit. + assert_eq!(BIT_COUNT, 3); + for (inp, out) in table.iter() { + for pos in (0..64).step_by(BIT_COUNT) { + assert_eq!((inp >> pos) & 1, (out >> pos) & (4 + 2 + 1)); + } + } + } + + #[test] + fn chi_table() { + // Check the base pattern for all combinations of bits. + for i in 0..16_usize { + let (a, b, c, d) = (i & 1, (i >> 1) & 1, (i >> 2) & 1, (i >> 3) & 1); + assert_eq!( + CHI_BASE_LOOKUP_TABLE[3 - 2 * a + b - c], + (a ^ ((!b) & c)) as u8 + ); + assert_eq!( + CHI_EXT_LOOKUP_TABLE[5 - 2 * a - b + c - 2 * d], + (a ^ ((!b) & c) ^ d) as u8 + ); + } + + // Check the table with multiple parts per row. + chi_table_impl(10); + chi_table_impl(19); + } + + fn chi_table_impl(log_height: usize) { + let range = 5; // CHI_BASE_LOOKUP_RANGE + let table = build_table(&TableTestCircuit { + range, + log_height, + normalize_else_chi: false, + }); + + // On all rows, all input/output pairs match the base table. + for (inp, out) in table.iter() { + for pos in (0..64).step_by(BIT_COUNT) { + let inp = ((inp >> pos) & 7) as usize; + let out = ((out >> pos) & 7) as u8; + assert_eq!(out, CHI_BASE_LOOKUP_TABLE[inp]); + } + } + } + + // ---- Helpers ---- + + fn build_table(circuit: &TableTestCircuit) -> Vec<(u64, u64)> { + let prover = MockProver::::run(circuit.log_height as u32, circuit, vec![]).unwrap(); + + let columns = prover.fixed(); + assert_eq!(columns.len(), 2); + let unused_rows = 6; // What MockProver uses on this test circuit. + let used_rows = (1 << circuit.log_height) - unused_rows; + + // Check the unused rows. + for io in zip(&columns[0], &columns[1]).skip(used_rows) { + assert_eq!(io, (&CellValue::Unassigned, &CellValue::Unassigned)); + } + + // Get the generated lookup table with the form: table[row] = (input, output). + let table = zip(&columns[0], &columns[1]) + .take(used_rows) + .map(|(inp, out)| (unwrap_u64(inp), unwrap_u64(out))) + .collect::>(); + + // All possible combinations of inputs are there. + let unique_rows = table.iter().unique().count(); + assert_eq!(unique_rows, circuit.expected_num_entries()); + + table + } + + #[derive(Clone)] + struct TableTestCircuit { + range: usize, + log_height: usize, + normalize_else_chi: bool, + } + + impl TableTestCircuit { + fn expected_num_entries(&self) -> usize { + let num_bits = get_num_bits_per_lookup_impl(self.range, self.log_height); + self.range.pow(num_bits as u32) + } + } + + impl Circuit for TableTestCircuit { + type Config = [TableColumn; 2]; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + self.clone() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + array_init::array_init(|_| meta.lookup_table_column()) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + if self.normalize_else_chi { + load_normalize_table_impl( + &mut layouter, + "normalize", + &config, + self.range as u64, + self.log_height, + )?; + } else { + let num_bits = get_num_bits_per_lookup_impl(self.range, self.log_height); + load_lookup_table( + &mut layouter, + "chi base", + &config, + num_bits, + &CHI_BASE_LOOKUP_TABLE, + )?; + } + Ok(()) + } + } + + fn unwrap_u64(cv: &CellValue) -> u64 { + match *cv { + CellValue::Assigned(f) => { + let f = f.get_lower_128(); + assert_eq!(f >> 64, 0); + f as u64 + } + _ => panic!("the cell should be assigned"), + } + } +} diff --git a/zkevm-circuits/src/keccak_circuit/util.rs b/zkevm-circuits/src/keccak_circuit/util.rs index a110188db1..f19e5460c1 100644 --- a/zkevm-circuits/src/keccak_circuit/util.rs +++ b/zkevm-circuits/src/keccak_circuit/util.rs @@ -217,7 +217,7 @@ pub fn get_num_bits_per_lookup(range: usize) -> usize { } // Implementation of the above without environment dependency. -fn get_num_bits_per_lookup_impl(range: usize, log_height: usize) -> usize { +pub fn get_num_bits_per_lookup_impl(range: usize, log_height: usize) -> usize { let num_unusable_rows = 31; let height = 2usize.pow(log_height as u32); let mut num_bits = 1; @@ -301,15 +301,10 @@ pub(crate) mod to_bytes { #[cfg(test)] mod tests { use super::*; - use halo2_proofs::circuit::SimpleFloorPlanner; - use halo2_proofs::dev::{CellValue, MockProver}; use halo2_proofs::halo2curves::bn256::Fr as F; - use halo2_proofs::plonk::{Circuit, ConstraintSystem}; - use itertools::Itertools; - use std::iter::zip; #[test] - fn pack_table() { + fn pack_into_bits() { let msb = 1 << (7 * 3); for (idx, expected) in [(0, 0), (1, 1), (128, msb), (129, msb | 1)] { let packed: F = pack(&into_bits(&[idx as u8])); @@ -327,158 +322,4 @@ mod tests { // The largest imaginable value does not overflow u64. assert_eq!(get_num_bits_per_lookup_impl(3, 32) * BIT_COUNT, 60); } - - #[test] - fn normalize_table() { - normalize_table_impl(3, 10); - normalize_table_impl(4, 10); - normalize_table_impl(6, 10); - normalize_table_impl(6, 19); - } - - fn normalize_table_impl(range: usize, log_height: usize) { - let table = build_table(&TableTestCircuit { - range, - log_height, - normalize_else_chi: true, - }); - - // On all rows, all inputs/outputs are correct, i.e. they have the same low bit. - assert_eq!(BIT_COUNT, 3); - for (inp, out) in table.iter() { - for pos in (0..64).step_by(BIT_COUNT) { - assert_eq!((inp >> pos) & 1, (out >> pos) & (4 + 2 + 1)); - } - } - } - - #[test] - fn chi_table() { - // Check the base pattern for all combinations of bits. - for i in 0..16_usize { - let (a, b, c, d) = (i & 1, (i >> 1) & 1, (i >> 2) & 1, (i >> 3) & 1); - assert_eq!( - CHI_BASE_LOOKUP_TABLE[3 - 2 * a + b - c], - (a ^ ((!b) & c)) as u8 - ); - assert_eq!( - CHI_EXT_LOOKUP_TABLE[5 - 2 * a - b + c - 2 * d], - (a ^ ((!b) & c) ^ d) as u8 - ); - } - - // Check the table with multiple parts per row. - chi_table_impl(10); - chi_table_impl(19); - } - - fn chi_table_impl(log_height: usize) { - let range = 5; // CHI_BASE_LOOKUP_RANGE - let table = build_table(&TableTestCircuit { - range, - log_height, - normalize_else_chi: false, - }); - - // On all rows, all input/output pairs match the base table. - for (inp, out) in table.iter() { - for pos in (0..64).step_by(BIT_COUNT) { - let inp = ((inp >> pos) & 7) as usize; - let out = ((out >> pos) & 7) as u8; - assert_eq!(out, CHI_BASE_LOOKUP_TABLE[inp]); - } - } - } - - // ---- Helpers ---- - - fn build_table(circuit: &TableTestCircuit) -> Vec<(u64, u64)> { - let prover = MockProver::::run(circuit.log_height as u32, circuit, vec![]).unwrap(); - - let columns = prover.fixed(); - assert_eq!(columns.len(), 2); - let unused_rows = 6; // What MockProver uses on this test circuit. - let used_rows = (1 << circuit.log_height) - unused_rows; - - // Check the unused rows. - for io in zip(&columns[0], &columns[1]).skip(used_rows) { - assert_eq!(io, (&CellValue::Unassigned, &CellValue::Unassigned)); - } - - // Get the generated lookup table with the form: table[row] = (input, output). - let table = zip(&columns[0], &columns[1]) - .take(used_rows) - .map(|(inp, out)| (unwrap_u64(inp), unwrap_u64(out))) - .collect::>(); - - // All possible combinations of inputs are there. - let unique_rows = table.iter().unique().count(); - assert_eq!(unique_rows, circuit.expected_num_entries()); - - table - } - - #[derive(Clone)] - struct TableTestCircuit { - range: usize, - log_height: usize, - normalize_else_chi: bool, - } - - impl TableTestCircuit { - fn expected_num_entries(&self) -> usize { - let num_bits = get_num_bits_per_lookup_impl(self.range, self.log_height); - self.range.pow(num_bits as u32) - } - } - - impl Circuit for TableTestCircuit { - type Config = [TableColumn; 2]; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - self.clone() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - array_init::array_init(|_| meta.lookup_table_column()) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - if self.normalize_else_chi { - load_normalize_table_impl( - &mut layouter, - "normalize", - &config, - self.range as u64, - self.log_height, - )?; - } else { - let num_bits = get_num_bits_per_lookup_impl(self.range, self.log_height); - load_lookup_table( - &mut layouter, - "chi base", - &config, - num_bits, - &CHI_BASE_LOOKUP_TABLE, - )?; - } - Ok(()) - } - } - - fn unwrap_u64(cv: &CellValue) -> u64 { - match *cv { - CellValue::Assigned(f) => { - let f = f.get_lower_128(); - assert_eq!(f >> 64, 0); - f as u64 - } - _ => panic!("the cell should be assigned"), - } - } }