diff --git a/mp2-common/src/digest.rs b/mp2-common/src/digest.rs index 9265af657..f55133a31 100644 --- a/mp2-common/src/digest.rs +++ b/mp2-common/src/digest.rs @@ -2,82 +2,17 @@ use crate::group_hashing::{ circuit_hashed_scalar_mul, cond_circuit_hashed_scalar_mul, cond_field_hashed_scalar_mul, field_hashed_scalar_mul, map_to_curve_point, }; -use crate::serialization::{deserialize, serialize}; use crate::types::CBuilder; use crate::utils::ToFields; use crate::{group_hashing::CircuitBuilderGroupHashing, utils::ToTargets}; -use crate::{D, F}; -use derive_more::{From, Into}; use plonky2::iop::target::BoolTarget; -use plonky2::iop::witness::{PartialWitness, WitnessWrite}; -use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2_ecgfp5::{ curve::curve::Point, gadgets::curve::{CircuitBuilderEcGFp5, CurveTarget}, }; -use serde::{Deserialize, Serialize}; pub type DigestTarget = CurveTarget; pub type Digest = Point; -/// Whether the table's digest is composed of a single row, or multiple rows. -/// For example when extracting mapping entries in one single sweep of the MPT, the digest contains -/// multiple rows inside. -/// When extracting single variables on one sweep, there is only a single row contained in the -/// digest. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub enum TableDimension { - /// Set to Single for types that only generate a single row at a given block. For example, a - /// uint256 or a bytes32 will only generate a single row per block. - Single, - /// Set to Compound for types that - /// 1. have multiple entries (like an mapping, unlike a single uin256 for example) - /// 2. don't need or have an associated length slot to combine with - /// - /// It happens contracts don't have a length slot associated with the mapping like ERC20 and - /// thus there is no proof circuits have looked at _all_ the entries due to limitations on EVM - /// (there is no mapping.len()). - Compound, -} - -impl TableDimension { - pub fn assign_wire(&self, pw: &mut PartialWitness, wire: &TableDimensionWire) { - match self { - TableDimension::Single => pw.set_bool_target(wire.0, false), - TableDimension::Compound => pw.set_bool_target(wire.0, true), - } - } - - pub fn conditional_row_digest(&self, digest: Digest) -> Digest { - match self { - TableDimension::Single => map_to_curve_point(&digest.to_fields()), - TableDimension::Compound => digest, - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, From, Into, Eq, PartialEq)] -pub struct TableDimensionWire( - #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] pub BoolTarget, -); - -impl TableDimensionWire { - pub fn conditional_row_digest( - &self, - c: &mut CircuitBuilder, - digest: CurveTarget, - ) -> CurveTarget { - let single = c.map_to_curve_point(&digest.to_targets()); - // if the table is a compound table, i.e. multiple rows accumulated in the digest, then - // there is no need to apply digest one more time. On the other hand, if it is not - // compounded, i.e. there is only a sum of cells digest, then we need to create the "row" - // digest, thus applying the digest one more time. - // - // TableDimension::Single => false, - // TableDimension::Compound => true, - c.curve_select(self.0, digest, single) - } -} - /// Generic struct that can either hold a digest in circuit (DigestTarget) or a digest outside /// circuit, useful for testing. #[derive(Clone, Debug)] @@ -183,10 +118,7 @@ impl SplitDigestTarget { mod test { use crate::{types::CBuilder, utils::FromFields, C, D, F}; - use super::{ - Digest, DigestTarget, SplitDigestPoint, SplitDigestTarget, TableDimension, - TableDimensionWire, - }; + use super::{Digest, DigestTarget, SplitDigestPoint, SplitDigestTarget}; use crate::utils::TryIntoBool; use mp2_test::circuit::{run_circuit, UserCircuit}; use plonky2::{field::types::Sample, iop::witness::PartialWitness}; @@ -260,48 +192,4 @@ mod test { assert_eq!(is_merge_case_circuit, is_merge_case_point); } } - - #[derive(Clone, Debug)] - struct TestTableDimension { - digest: Digest, - dimension: TableDimension, - } - - struct TestTableDimensionWire { - digest: DigestTarget, - dimension: TableDimensionWire, - } - - impl UserCircuit for TestTableDimension { - type Wires = TestTableDimensionWire; - - fn build(b: &mut CBuilder) -> Self::Wires { - let digest = b.add_virtual_curve_target(); - let dimension: TableDimensionWire = b.add_virtual_bool_target_safe().into(); - let final_digest = dimension.conditional_row_digest(b, digest); - b.register_curve_public_input(final_digest); - - TestTableDimensionWire { digest, dimension } - } - - fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { - pw.set_curve_target(wires.digest, self.digest.to_weierstrass()); - self.dimension.assign_wire(pw, &wires.dimension); - } - } - - #[test] - fn test_dimension_wire() { - let cases = vec![TableDimension::Single, TableDimension::Compound]; - for dimension in cases { - let circuit = TestTableDimension { - digest: Point::rand(), - dimension, - }; - let proof = run_circuit::(circuit.clone()); - let combined = Digest::from_fields(&proof.public_inputs); - let expected = dimension.conditional_row_digest(circuit.digest); - assert_eq!(combined, expected); - } - } } diff --git a/mp2-common/src/eth.rs b/mp2-common/src/eth.rs index 854e2cbb1..ca7254907 100644 --- a/mp2-common/src/eth.rs +++ b/mp2-common/src/eth.rs @@ -12,6 +12,7 @@ use anyhow::{bail, Result}; use eth_trie::{EthTrie, MemoryDB, Trie}; use ethereum_types::H256; use itertools::Itertools; +use log::debug; use log::warn; use rlp::Rlp; use serde::{Deserialize, Serialize}; @@ -214,6 +215,10 @@ impl StorageSlot { .checked_add(U256::from(*evm_offset)) .unwrap() .to_be_bytes(); + debug!( + "Storage slot struct: parent_location = {}, evm_offset = {}", + parent_location, evm_offset, + ); B256::from_slice(&location) } } @@ -235,8 +240,28 @@ impl StorageSlot { StorageSlot::Node(node) => node.parent().is_simple_slot(), } } + /// Get the mapping key path from the outer key to the inner. + pub fn mapping_keys(&self) -> Vec> { + match self { + StorageSlot::Simple(_) => vec![], + StorageSlot::Mapping(mapping_key, _) => { + vec![mapping_key.clone()] + } + StorageSlot::Node(StorageSlotNode::Mapping(parent, mapping_key)) => { + // [parent_mapping_keys || mapping_key] + let mut mapping_keys = parent.mapping_keys(); + mapping_keys.push(mapping_key.clone()); + + mapping_keys + } + StorageSlot::Node(StorageSlotNode::Struct(parent, _)) => parent.mapping_keys(), + } + } } impl ProofQuery { + pub fn new(contract: Address, slot: StorageSlot) -> Self { + Self { contract, slot } + } pub fn new_simple_slot(address: Address, slot: usize) -> Self { Self { contract: address, @@ -256,8 +281,14 @@ impl ProofQuery { ) -> Result { // Query the MPT proof with retries. for i in 0..RETRY_NUM { + let location = self.slot.location(); + debug!( + "Querying MPT proof:\n\tslot = {:?}, location = {:?}", + self.slot, + U256::from_be_slice(location.as_slice()), + ); match provider - .get_proof(self.contract, vec![self.slot.location()]) + .get_proof(self.contract, vec![location]) .block_id(block.into()) .await { diff --git a/mp2-common/src/types.rs b/mp2-common/src/types.rs index 1f57bb268..933116eb0 100644 --- a/mp2-common/src/types.rs +++ b/mp2-common/src/types.rs @@ -112,6 +112,12 @@ impl From> for HashOutput { } } +impl From<&HashOut> for HashOutput { + fn from(value: &HashOut) -> Self { + value.to_bytes().try_into().unwrap() + } +} + impl From for HashOut { fn from(value: HashOutput) -> Self { Self::from_bytes(&value.0) diff --git a/mp2-v1/src/api.rs b/mp2-v1/src/api.rs index 20774c9bf..e0863eaf3 100644 --- a/mp2-v1/src/api.rs +++ b/mp2-v1/src/api.rs @@ -21,6 +21,7 @@ use crate::{ use alloy::primitives::Address; use anyhow::Result; use itertools::Itertools; +use log::debug; use mp2_common::{ digest::Digest, group_hashing::map_to_curve_point, @@ -214,15 +215,12 @@ pub enum SlotInputs { MappingWithLength(Vec, u8), } -#[derive(Debug)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct SlotInput { /// Slot information of the variable pub(crate) slot: u8, /// The offset in bytes where to extract this column in a given EVM word pub(crate) byte_offset: usize, - /// The starting offset in `byte_offset` of the bits to be extracted for this column. - /// The column bits will start at `byte_offset * 8 + bit_offset`. - pub(crate) bit_offset: usize, /// The length (in bits) of the field to extract in the EVM word pub(crate) length: usize, /// At which EVM word is this column extracted from. For simple variables, @@ -234,30 +232,19 @@ pub struct SlotInput { impl From<&ColumnInfo> for SlotInput { fn from(column_info: &ColumnInfo) -> Self { let slot = u8::try_from(column_info.slot.to_canonical_u64()).unwrap(); - let [byte_offset, bit_offset, length] = [ - column_info.byte_offset, - column_info.bit_offset, - column_info.length, - ] - .map(|f| usize::try_from(f.to_canonical_u64()).unwrap()); + let [byte_offset, length] = [column_info.byte_offset, column_info.length] + .map(|f| usize::try_from(f.to_canonical_u64()).unwrap()); let evm_word = u32::try_from(column_info.evm_word.to_canonical_u64()).unwrap(); - SlotInput::new(slot, byte_offset, bit_offset, length, evm_word) + SlotInput::new(slot, byte_offset, length, evm_word) } } impl SlotInput { - pub fn new( - slot: u8, - byte_offset: usize, - bit_offset: usize, - length: usize, - evm_word: u32, - ) -> Self { + pub fn new(slot: u8, byte_offset: usize, length: usize, evm_word: u32) -> Self { Self { slot, byte_offset, - bit_offset, length, evm_word, } @@ -271,10 +258,6 @@ impl SlotInput { self.byte_offset } - pub fn bit_offset(&self) -> usize { - self.bit_offset - } - pub fn length(&self) -> usize { self.length } @@ -343,7 +326,7 @@ fn value_metadata( } /// Compute the table information for the value columns. -fn compute_table_info( +pub fn compute_table_info( inputs: Vec, address: &Address, chain_id: u64, @@ -358,7 +341,7 @@ fn compute_table_info( input.slot, id, input.byte_offset, - input.bit_offset, + 0, // bit_offset input.length, input.evm_word, ) @@ -443,8 +426,16 @@ pub fn metadata_hash( chain_id, extra, ); + // Correspond to the computation of final extraction base circuit. + let value_digest = map_to_curve_point(&value_digest.to_fields()); // add contract digest let contract_digest = contract_metadata_digest(contract_address); + debug!( + "METADATA_HASH ->\n\tvalues_ext_md = {:?}\n\tcontract_md = {:?}\n\tfinal_ex_md(contract + values_ex) = {:?}", + value_digest.to_weierstrass(), + contract_digest.to_weierstrass(), + (contract_digest + value_digest).to_weierstrass(), + ); // compute final hash combine_digest_and_block(contract_digest + value_digest) } diff --git a/mp2-v1/src/final_extraction/api.rs b/mp2-v1/src/final_extraction/api.rs index ef152d684..064ded1f4 100644 --- a/mp2-v1/src/final_extraction/api.rs +++ b/mp2-v1/src/final_extraction/api.rs @@ -1,4 +1,4 @@ -use mp2_common::{self, default_config, digest::TableDimension, proof::ProofWithVK, C, D, F}; +use mp2_common::{self, default_config, proof::ProofWithVK, C, D, F}; use plonky2::{iop::target::Target, plonk::circuit_data::VerifierCircuitData}; use recursion_framework::{ circuit_builder::{CircuitWithUniversalVerifier, CircuitWithUniversalVerifierBuilder}, @@ -108,8 +108,6 @@ impl PublicParameters { let merge = MergeTable { is_table_a_multiplier: input.is_table_a_multiplier, - dimension_a: input.table_a_dimension, - dimension_b: input.table_b_dimension, }; let merge_inputs = MergeCircuit { base, merge }; let proof = self @@ -124,14 +122,11 @@ impl PublicParameters { contract_circuit_set: &RecursiveCircuits, value_circuit_set: &RecursiveCircuits, ) -> Result> { - let simple_inputs = SimpleCircuit::new( - BaseCircuitProofInputs::new_from_proofs( - input.base, - contract_circuit_set.clone(), - value_circuit_set.clone(), - ), - input.dimension, - ); + let simple_inputs = SimpleCircuit::new(BaseCircuitProofInputs::new_from_proofs( + input.base, + contract_circuit_set.clone(), + value_circuit_set.clone(), + )); let proof = self .circuit_set .generate_proof(&self.simple, [], [], simple_inputs)?; @@ -167,7 +162,6 @@ impl PublicParameters { pub struct SimpleCircuitInput { base: BaseCircuitInput, - dimension: TableDimension, } pub struct LengthedCircuitInput { @@ -178,8 +172,6 @@ pub struct LengthedCircuitInput { pub struct MergeCircuitInput { base: BaseCircuitInput, is_table_a_multiplier: bool, - table_a_dimension: TableDimension, - table_b_dimension: TableDimension, } impl CircuitInput { @@ -202,8 +194,6 @@ impl CircuitInput { Ok(Self::MergeTable(MergeCircuitInput { base, is_table_a_multiplier: true, - table_a_dimension: TableDimension::Single, - table_b_dimension: TableDimension::Compound, })) } /// Instantiate inputs for simple variables circuit. Coumpound must be set to true @@ -214,10 +204,9 @@ impl CircuitInput { block_proof: Vec, contract_proof: Vec, value_proof: Vec, - dimension: TableDimension, ) -> Result { let base = BaseCircuitInput::new(block_proof, contract_proof, vec![value_proof])?; - Ok(Self::Simple(SimpleCircuitInput { base, dimension })) + Ok(Self::Simple(SimpleCircuitInput { base })) } /// Instantiate inputs for circuit dealing with compound types with a length slot pub fn new_lengthed_input( @@ -235,7 +224,6 @@ impl CircuitInput { #[cfg(test)] mod tests { use mp2_common::{ - digest::TableDimension, proof::{serialize_proof, ProofWithVK}, C, D, F, }; @@ -296,30 +284,27 @@ mod tests { ) .into(); // test generation of proof for simple circuit for both compound and simple types - for dimension in [TableDimension::Single, TableDimension::Compound] { - let circuit_input = CircuitInput::new_simple_input( - serialize_proof(&block_proof).unwrap(), - contract_proof.serialize().unwrap(), - value_proof.serialize().unwrap(), - dimension, - ) - .unwrap(); + let circuit_input = CircuitInput::new_simple_input( + serialize_proof(&block_proof).unwrap(), + contract_proof.serialize().unwrap(), + value_proof.serialize().unwrap(), + ) + .unwrap(); - let proof = ProofWithVK::deserialize( - ¶ms - .generate_simple_proof( - match circuit_input { - CircuitInput::Simple(input) => input, - _ => unreachable!(), - }, - contract_params.get_recursive_circuit_set(), - values_params.get_recursive_circuit_set(), - ) - .unwrap(), - ) - .unwrap(); - proof_pis.check_proof_public_inputs(proof.proof(), dimension, None); - } + let proof = ProofWithVK::deserialize( + ¶ms + .generate_simple_proof( + match circuit_input { + CircuitInput::Simple(input) => input, + _ => unreachable!(), + }, + contract_params.get_recursive_circuit_set(), + values_params.get_recursive_circuit_set(), + ) + .unwrap(), + ) + .unwrap(); + proof_pis.check_proof_public_inputs(proof.proof(), None); // test proof generation for types with length circuit let length_proof: ProofWithVK = ( length_proof.clone(), @@ -347,6 +332,6 @@ mod tests { .unwrap(), ) .unwrap(); - proof_pis.check_proof_public_inputs(proof.proof(), TableDimension::Compound, Some(len_dm)); + proof_pis.check_proof_public_inputs(proof.proof(), Some(len_dm)); } } diff --git a/mp2-v1/src/final_extraction/base_circuit.rs b/mp2-v1/src/final_extraction/base_circuit.rs index 6c99cd7df..e53d12c1d 100644 --- a/mp2-v1/src/final_extraction/base_circuit.rs +++ b/mp2-v1/src/final_extraction/base_circuit.rs @@ -239,7 +239,6 @@ pub(crate) mod test { use anyhow::Result; use itertools::Itertools; use mp2_common::{ - digest::TableDimension, group_hashing::map_to_curve_point, keccak::PACKED_HASH_LEN, rlp::MAX_KEY_NIBBLE_LEN, @@ -386,7 +385,6 @@ pub(crate) mod test { pub(crate) fn check_proof_public_inputs( &self, proof: &ProofWithPublicInputs, - dimension: TableDimension, length_dm: Option, ) { let proof_pis = PublicInputs::from_slice(&proof.public_inputs); @@ -398,13 +396,7 @@ pub(crate) mod test { // check digests let value_pi = values_extraction::PublicInputs::new(&self.values_pi); - if let TableDimension::Compound = dimension { - assert_eq!(proof_pis.value_point(), value_pi.values_digest()); - } else { - // in this case, dv is D(value_dv) - let exp_dv = map_to_curve_point(&value_pi.values_digest().to_fields()); - assert_eq!(proof_pis.value_point(), exp_dv.to_weierstrass()); - } + assert_eq!(proof_pis.value_point(), value_pi.values_digest()); // metadata is addition of contract and value // ToDo: make it a trait once we understand it's sound let weierstrass_to_point = |wp: WeierstrassPoint| { diff --git a/mp2-v1/src/final_extraction/lengthed_circuit.rs b/mp2-v1/src/final_extraction/lengthed_circuit.rs index f9e23fa68..f8a6a29eb 100644 --- a/mp2-v1/src/final_extraction/lengthed_circuit.rs +++ b/mp2-v1/src/final_extraction/lengthed_circuit.rs @@ -160,7 +160,6 @@ mod test { use super::*; use base_circuit::test::{ProofsPi, ProofsPiTarget}; - use mp2_common::digest::TableDimension; use mp2_test::circuit::{run_circuit, UserCircuit}; use plonky2::iop::witness::WitnessWrite; @@ -215,6 +214,6 @@ mod test { let len_pi = length_extraction::PublicInputs::::from_slice(&test_circuit.len_pi); let len_dm = len_pi.metadata_point(); let proof = run_circuit::(test_circuit); - pis.check_proof_public_inputs(&proof, TableDimension::Compound, Some(len_dm)); + pis.check_proof_public_inputs(&proof, Some(len_dm)); } } diff --git a/mp2-v1/src/final_extraction/merge_circuit.rs b/mp2-v1/src/final_extraction/merge_circuit.rs index 91c2d1151..01ede464a 100644 --- a/mp2-v1/src/final_extraction/merge_circuit.rs +++ b/mp2-v1/src/final_extraction/merge_circuit.rs @@ -6,7 +6,7 @@ use super::{ BaseCircuitProofInputs, PublicInputs, }; use mp2_common::{ - digest::{SplitDigestTarget, TableDimension, TableDimensionWire}, + digest::SplitDigestTarget, serialization::{deserialize, serialize}, types::CBuilder, utils::ToTargets, @@ -32,16 +32,12 @@ use verifiable_db::extraction::ExtractionPI; #[derive(Clone, Debug)] pub struct MergeTable { pub(crate) is_table_a_multiplier: bool, - pub(crate) dimension_a: TableDimension, - pub(crate) dimension_b: TableDimension, } #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct MergeTableWires { #[serde(deserialize_with = "deserialize", serialize_with = "serialize")] is_table_a_multiplier: BoolTarget, - dimension_a: TableDimensionWire, - dimension_b: TableDimensionWire, } impl MergeTable { @@ -60,17 +56,8 @@ impl MergeTable { let table_a = values_extraction::PublicInputs::new(table_a); let table_b = values_extraction::PublicInputs::new(table_b); - // prepare the table digest if they're compound or not - // At final extraction, if we're extracting a single type table, then we need to digest one - // more time the value proof digest. The value proof digest gives us SUM D(column) but at - // this stage we want D ( SUM D(column)). - // NOTE: in practice at first we only gonna have one table being the single table with a - // single row and the other one being a mapping. But this implementation should allow for - // mappings X mappings, or arrays X mappings etc. - let table_a_dimension = TableDimensionWire(b.add_virtual_bool_target_safe()); - let table_b_dimension = TableDimensionWire(b.add_virtual_bool_target_safe()); - let digest_a = table_a_dimension.conditional_row_digest(b, table_a.values_digest_target()); - let digest_b = table_b_dimension.conditional_row_digest(b, table_b.values_digest_target()); + let digest_a = table_a.values_digest_target(); + let digest_b = table_b.values_digest_target(); // Combine the two digest depending on which table is the multiplier let is_table_a_multiplier = b.add_virtual_bool_target_safe(); @@ -96,13 +83,9 @@ impl MergeTable { .register_args(b); MergeTableWires { is_table_a_multiplier, - dimension_a: table_a_dimension, - dimension_b: table_b_dimension, } } fn assign(&self, pw: &mut PartialWitness, wires: &MergeTableWires) { - self.dimension_a.assign_wire(pw, &wires.dimension_a); - self.dimension_b.assign_wire(pw, &wires.dimension_b); pw.set_bool_target(wires.is_table_a_multiplier, self.is_table_a_multiplier); } } @@ -215,8 +198,6 @@ mod test { fn test_final_merge_circuit() { let pis_a = ProofsPi::random(); let pis_b = pis_a.generate_new_random_value(); - let table_a_dimension = TableDimension::Single; - let table_b_dimension = TableDimension::Compound; let table_a_multiplier = true; let test_circuit = TestMergeCircuit { @@ -224,19 +205,14 @@ mod test { pis_b: pis_b.values_pi.clone(), circuit: MergeTable { is_table_a_multiplier: table_a_multiplier, - dimension_a: table_a_dimension, - dimension_b: table_b_dimension, }, }; let proof = run_circuit::(test_circuit); let pi = PublicInputs::from_slice(&proof.public_inputs); - // first compute the right digest for each table according to their dimension - let table_a_digest = - table_a_dimension.conditional_row_digest(wp(&pis_a.value_inputs().values_digest())); - let table_b_digest = - table_b_dimension.conditional_row_digest(wp(&pis_b.value_inputs().values_digest())); + let table_a_digest = wp(&pis_a.value_inputs().values_digest()); + let table_b_digest = wp(&pis_b.value_inputs().values_digest()); // then do the splitting according to how we want to merge them (i.e. which is the // multiplier) let split_a = diff --git a/mp2-v1/src/final_extraction/public_inputs.rs b/mp2-v1/src/final_extraction/public_inputs.rs index e0bb67c21..7244e7105 100644 --- a/mp2-v1/src/final_extraction/public_inputs.rs +++ b/mp2-v1/src/final_extraction/public_inputs.rs @@ -6,7 +6,7 @@ use mp2_common::{ public_inputs::{PublicInputCommon, PublicInputRange}, types::{CBuilder, CURVE_TARGET_LEN}, u256::{self, UInt256Target}, - utils::{FromFields, FromTargets, ToTargets}, + utils::{FromFields, FromTargets, ToTargets, TryIntoBool}, F, }; use plonky2::iop::target::{BoolTarget, Target}; @@ -110,6 +110,10 @@ impl PublicInputs<'_, F> { pub fn block_number(&self) -> u64 { U256::from_fields(self.bn).to() } + /// Get the merge flag + pub fn merge_flag(&self) -> bool { + self.merge[0].try_into_bool().unwrap() + } } impl<'a, T> PublicInputs<'a, T> { diff --git a/mp2-v1/src/final_extraction/simple_circuit.rs b/mp2-v1/src/final_extraction/simple_circuit.rs index 6bd039daf..f12801998 100644 --- a/mp2-v1/src/final_extraction/simple_circuit.rs +++ b/mp2-v1/src/final_extraction/simple_circuit.rs @@ -1,10 +1,5 @@ -use derive_more::{From, Into}; -use mp2_common::{ - digest::{TableDimension, TableDimensionWire}, - public_inputs::PublicInputCommon, - utils::ToTargets, - D, F, -}; +use derive_more::From; +use mp2_common::{public_inputs::PublicInputCommon, utils::ToTargets, D, F}; use plonky2::{ iop::{target::Target, witness::PartialWitness}, plonk::circuit_builder::CircuitBuilder, @@ -22,11 +17,8 @@ use super::{ /// This circuit contains the logic to prove the final extraction of a simple /// variable (like uint256) or a mapping without an associated length slot. -#[derive(Clone, Debug, From, Into)] -pub struct SimpleCircuit(TableDimension); - -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub struct SimpleWires(TableDimensionWire); +#[derive(Clone, Debug, From)] +pub struct SimpleCircuit; impl SimpleCircuit { fn build( @@ -34,15 +26,12 @@ impl SimpleCircuit { block_pi: &[Target], contract_pi: &[Target], value_pi: &[Target], - ) -> SimpleWires { + ) { // only one value proof to verify for this circuit let base_wires = base_circuit::BaseCircuit::build(b, block_pi, contract_pi, vec![value_pi]); let value_pi = values_extraction::PublicInputs::::new(value_pi); - let dv = value_pi.values_digest_target(); - // Compute the final value digest depending on the table dimension - let dimension: TableDimensionWire = b.add_virtual_bool_target_safe().into(); - let final_dv = dimension.conditional_row_digest(b, dv); + let final_dv = value_pi.values_digest_target(); PublicInputs::new( &base_wires.bh, &base_wires.prev_bh, @@ -52,11 +41,6 @@ impl SimpleCircuit { &[b._false().target], ) .register_args(b); - SimpleWires(dimension) - } - - fn assign(&self, pw: &mut PartialWitness, wires: &SimpleWires) { - self.0.assign_wire(pw, &wires.0); } } @@ -64,20 +48,15 @@ impl SimpleCircuit { pub(crate) struct SimpleCircuitRecursiveWires { /// NOTE: assumed to be containing a single value inside, in the vec. base: BaseCircuitProofWires, - simple_wires: SimpleWires, } pub struct SimpleCircuitInput { base: BaseCircuitProofInputs, - simple: SimpleCircuit, } impl SimpleCircuitInput { - pub(crate) fn new(base: BaseCircuitProofInputs, dimension: TableDimension) -> Self { - Self { - base, - simple: dimension.into(), - } + pub(crate) fn new(base: BaseCircuitProofInputs) -> Self { + Self { base } } } @@ -95,21 +74,17 @@ impl CircuitLogicWires for SimpleCircuitRecursiveWires { ) -> Self { // only one proof to verify for this simple circuit let base = BaseCircuitProofInputs::build(builder, &builder_parameters, 1); - let wires = SimpleCircuit::build( + SimpleCircuit::build( builder, base.get_block_public_inputs(), base.get_contract_public_inputs(), base.get_value_public_inputs(), ); - Self { - base, - simple_wires: wires, - } + Self { base } } fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness) -> anyhow::Result<()> { inputs.base.assign_proof_targets(pw, &self.base)?; - inputs.simple.assign(pw, &self.simple_wires); Ok(()) } } @@ -123,12 +98,10 @@ mod test { #[derive(Clone, Debug)] struct TestSimpleCircuit { - circuit: SimpleCircuit, pis: ProofsPi, } struct TestSimpleWires { - circuit: SimpleWires, pis: ProofsPiTarget, } @@ -136,33 +109,23 @@ mod test { type Wires = TestSimpleWires; fn build(c: &mut plonky2::plonk::circuit_builder::CircuitBuilder) -> Self::Wires { let pis = ProofsPiTarget::new(c); - let wires = SimpleCircuit::build(c, &pis.blocks_pi, &pis.contract_pi, &pis.values_pi); - TestSimpleWires { - circuit: wires, - pis, - } + SimpleCircuit::build(c, &pis.blocks_pi, &pis.contract_pi, &pis.values_pi); + TestSimpleWires { pis } } fn prove(&self, pw: &mut plonky2::iop::witness::PartialWitness, wires: &Self::Wires) { wires.pis.assign(pw, &self.pis); - self.circuit.assign(pw, &wires.circuit) } } #[test] fn test_final_simple_circuit() { let pis = ProofsPi::random(); - let test_circuit = TestSimpleCircuit { - pis: pis.clone(), - circuit: TableDimension::Compound.into(), - }; + let test_circuit = TestSimpleCircuit { pis: pis.clone() }; let proof = run_circuit::(test_circuit); - pis.check_proof_public_inputs(&proof, TableDimension::Compound, None); + pis.check_proof_public_inputs(&proof, None); - let test_circuit = TestSimpleCircuit { - pis: pis.clone(), - circuit: TableDimension::Single.into(), - }; + let test_circuit = TestSimpleCircuit { pis: pis.clone() }; let proof = run_circuit::(test_circuit); - pis.check_proof_public_inputs(&proof, TableDimension::Single, None); + pis.check_proof_public_inputs(&proof, None); } } diff --git a/mp2-v1/src/indexing/row.rs b/mp2-v1/src/indexing/row.rs index 04910f9ec..5d2879def 100644 --- a/mp2-v1/src/indexing/row.rs +++ b/mp2-v1/src/indexing/row.rs @@ -201,6 +201,9 @@ impl RowPayloa } } + pub fn column_value(&self, column_id: ColumnID) -> Option { + self.cells.get(&column_id).map(|c| c.value) + } pub fn secondary_index_value(&self) -> U256 { self.cells .get(&self.secondary_index_column) diff --git a/mp2-v1/src/values_extraction/api.rs b/mp2-v1/src/values_extraction/api.rs index 7199d9bfc..40646b685 100644 --- a/mp2-v1/src/values_extraction/api.rs +++ b/mp2-v1/src/values_extraction/api.rs @@ -3,11 +3,12 @@ use super::{ branch::{BranchCircuit, BranchWires}, extension::{ExtensionNodeCircuit, ExtensionNodeWires}, - gadgets::metadata_gadget::MetadataGadget, + gadgets::{column_gadget::filter_table_column_identifiers, metadata_gadget::ColumnsMetadata}, leaf_mapping::{LeafMappingCircuit, LeafMappingWires}, leaf_mapping_of_mappings::{LeafMappingOfMappingsCircuit, LeafMappingOfMappingsWires}, leaf_single::{LeafSingleCircuit, LeafSingleWires}, public_inputs::PublicInputs, + ColumnId, ColumnInfo, MappingKey, }; use crate::{api::InputNode, MAX_BRANCH_NODE_LEN}; use anyhow::{bail, ensure, Result}; @@ -67,8 +68,13 @@ where pub fn new_single_variable_leaf( node: Vec, slot: u8, - metadata: MetadataGadget, + evm_word: u32, + table_info: Vec, ) -> Self { + let extracted_column_identifiers = + filter_table_column_identifiers(&table_info, slot, evm_word); + let metadata = ColumnsMetadata::new(table_info, &extracted_column_identifiers, evm_word); + let slot = SimpleSlot::new(slot); CircuitInput::LeafSingle(LeafSingleCircuit { @@ -84,8 +90,13 @@ where slot: u8, mapping_key: Vec, key_id: u64, - metadata: MetadataGadget, + evm_word: u32, + table_info: Vec, ) -> Self { + let extracted_column_identifiers = + filter_table_column_identifiers(&table_info, slot, evm_word); + let metadata = ColumnsMetadata::new(table_info, &extracted_column_identifiers, evm_word); + let slot = MappingSlot::new(slot, mapping_key); let key_id = F::from_canonical_u64(key_id); @@ -102,19 +113,23 @@ where pub fn new_mapping_of_mappings_leaf( node: Vec, slot: u8, - outer_key: Vec, - inner_key: Vec, - outer_key_id: u64, - inner_key_id: u64, - metadata: MetadataGadget, + outer_key_data: (MappingKey, ColumnId), + inner_key_data: (MappingKey, ColumnId), + evm_word: u32, + table_info: Vec, ) -> Self { - let slot = MappingSlot::new(slot, outer_key); - let [outer_key_id, inner_key_id] = [outer_key_id, inner_key_id].map(F::from_canonical_u64); + let extracted_column_identifiers = + filter_table_column_identifiers(&table_info, slot, evm_word); + let metadata = ColumnsMetadata::new(table_info, &extracted_column_identifiers, evm_word); + + let slot = MappingSlot::new(slot, outer_key_data.0); + let [outer_key_id, inner_key_id] = + [outer_key_data.1, inner_key_data.1].map(F::from_canonical_u64); CircuitInput::LeafMappingOfMappings(LeafMappingOfMappingsCircuit { node, slot, - inner_key, + inner_key: inner_key_data.0, outer_key_id, inner_key_id, metadata, @@ -483,21 +498,25 @@ where #[cfg(test)] mod tests { - use super::{super::public_inputs, *}; + use super::{ + super::{public_inputs, StorageSlotInfo}, + *, + }; use crate::{ tests::{TEST_MAX_COLUMNS, TEST_MAX_FIELD_PER_EVM}, values_extraction::{ compute_leaf_mapping_metadata_digest, compute_leaf_mapping_of_mappings_metadata_digest, compute_leaf_mapping_of_mappings_values_digest, compute_leaf_mapping_values_digest, compute_leaf_single_metadata_digest, compute_leaf_single_values_digest, + identifier_raw_extra, }, MAX_LEAF_NODE_LEN, }; + use alloy::primitives::Address; use eth_trie::{EthTrie, MemoryDB, Trie}; use itertools::Itertools; use log::info; use mp2_common::{ - array::ToField, eth::{StorageSlot, StorageSlotNode}, group_hashing::weierstrass_to_point, mpt_sequential::utils::bytes_to_nibbles, @@ -507,9 +526,8 @@ mod tests { use plonky2::field::types::Field; use plonky2_ecgfp5::curve::curve::Point; use rand::{thread_rng, Rng}; - use std::{slice, sync::Arc}; + use std::{str::FromStr, sync::Arc}; - type StorageSlotInfo = super::super::StorageSlotInfo; type CircuitInput = super::CircuitInput; type PublicParameters = @@ -530,22 +548,20 @@ mod tests { let storage_slot1 = StorageSlot::Simple(TEST_SLOTS[0] as usize); let storage_slot2 = StorageSlot::Simple(TEST_SLOTS[1] as usize); - let mut metadata1 = MetadataGadget::sample(TEST_SLOTS[0], 0); - // We only extract the first column for simple slot. - metadata1.num_extracted_columns = 1; - // Set the second test slot and EVM word. - metadata1.table_info[1].slot = TEST_SLOTS[1].to_field(); - metadata1.table_info[1].evm_word = F::ZERO; - // Initialize the second metadata with second column identifier. - let metadata2 = MetadataGadget::new( - metadata1.table_info[..metadata1.num_actual_columns].to_vec(), - slice::from_ref(&metadata1.table_info[1].identifier.to_canonical_u64()), - 0, - ); + let table_info = TEST_SLOTS + .into_iter() + .map(|slot| { + let mut col_info = ColumnInfo::sample(); + col_info.slot = F::from_canonical_u8(slot); + col_info.evm_word = F::ZERO; + + col_info + }) + .collect_vec(); let test_slots = [ - StorageSlotInfo::new(storage_slot1, metadata1, None, None), - StorageSlotInfo::new(storage_slot2, metadata2, None, None), + StorageSlotInfo::new(storage_slot1, table_info.clone()), + StorageSlotInfo::new(storage_slot2, table_info), ]; test_api(test_slots); @@ -566,22 +582,20 @@ mod tests { let storage_slot2 = StorageSlot::Node(StorageSlotNode::new_struct(parent_slot, TEST_EVM_WORDS[1])); - let mut metadata1 = MetadataGadget::sample(TEST_SLOT, TEST_EVM_WORDS[0]); - // We only extract the first column for simple slot. - metadata1.num_extracted_columns = 1; - // Set the second test slot and EVM word. - metadata1.table_info[1].slot = TEST_SLOT.to_field(); - metadata1.table_info[1].evm_word = TEST_EVM_WORDS[1].to_field(); - // Initialize the second metadata with second column identifier. - let metadata2 = MetadataGadget::new( - metadata1.table_info[..metadata1.num_actual_columns].to_vec(), - slice::from_ref(&metadata1.table_info[1].identifier.to_canonical_u64()), - TEST_EVM_WORDS[1], - ); + let table_info = TEST_EVM_WORDS + .into_iter() + .map(|evm_word| { + let mut col_info = ColumnInfo::sample(); + col_info.slot = F::from_canonical_u8(TEST_SLOT); + col_info.evm_word = F::from_canonical_u32(evm_word); + + col_info + }) + .collect_vec(); let test_slots = [ - StorageSlotInfo::new(storage_slot1, metadata1, None, None), - StorageSlotInfo::new(storage_slot2, metadata2, None, None), + StorageSlotInfo::new(storage_slot1, table_info.clone()), + StorageSlotInfo::new(storage_slot2, table_info), ]; test_api(test_slots); @@ -593,26 +607,26 @@ mod tests { let _ = env_logger::try_init(); - let rng = &mut thread_rng(); - let mapping_key1 = vec![10]; let mapping_key2 = vec![20]; let storage_slot1 = StorageSlot::Mapping(mapping_key1, TEST_SLOT as usize); let storage_slot2 = StorageSlot::Mapping(mapping_key2, TEST_SLOT as usize); - let mut metadata1 = MetadataGadget::sample(TEST_SLOT, 0); - // We only extract the first column for simple slot. - metadata1.num_extracted_columns = 1; - // Set the second test slot and EVM word. - metadata1.table_info[1].slot = TEST_SLOT.to_field(); - metadata1.table_info[1].evm_word = F::ZERO; // The first and second column infos are same (only for testing). - let metadata2 = metadata1.clone(); + let table_info = [0; 2] + .into_iter() + .map(|_| { + let mut col_info = ColumnInfo::sample(); + col_info.slot = F::from_canonical_u8(TEST_SLOT); + col_info.evm_word = F::ZERO; + + col_info + }) + .collect_vec(); - let key_id = Some(rng.gen()); let test_slots = [ - StorageSlotInfo::new(storage_slot1, metadata1, key_id, None), - StorageSlotInfo::new(storage_slot2, metadata2, key_id, None), + StorageSlotInfo::new(storage_slot1, table_info.clone()), + StorageSlotInfo::new(storage_slot2, table_info), ]; test_api(test_slots); @@ -625,8 +639,6 @@ mod tests { let _ = env_logger::try_init(); - let rng = &mut thread_rng(); - let parent_slot = StorageSlot::Mapping(vec![10, 20], TEST_SLOT as usize); let storage_slot1 = StorageSlot::Node(StorageSlotNode::new_struct( parent_slot.clone(), @@ -635,23 +647,20 @@ mod tests { let storage_slot2 = StorageSlot::Node(StorageSlotNode::new_struct(parent_slot, TEST_EVM_WORDS[1])); - let mut metadata1 = MetadataGadget::sample(TEST_SLOT, TEST_EVM_WORDS[0]); - // We only extract the first column for simple slot. - metadata1.num_extracted_columns = 1; - // Set the second test slot and EVM word. - metadata1.table_info[1].slot = TEST_SLOT.to_field(); - metadata1.table_info[1].evm_word = TEST_EVM_WORDS[1].to_field(); - // Initialize the second metadata with second column identifier. - let metadata2 = MetadataGadget::new( - metadata1.table_info[..metadata1.num_actual_columns].to_vec(), - slice::from_ref(&metadata1.table_info[1].identifier.to_canonical_u64()), - TEST_EVM_WORDS[1], - ); + let table_info = TEST_EVM_WORDS + .into_iter() + .map(|evm_word| { + let mut col_info = ColumnInfo::sample(); + col_info.slot = F::from_canonical_u8(TEST_SLOT); + col_info.evm_word = F::from_canonical_u32(evm_word); + + col_info + }) + .collect_vec(); - let key_id = Some(rng.gen()); let test_slots = [ - StorageSlotInfo::new(storage_slot1, metadata1, key_id, None), - StorageSlotInfo::new(storage_slot2, metadata2, key_id, None), + StorageSlotInfo::new(storage_slot1, table_info.clone()), + StorageSlotInfo::new(storage_slot2, table_info), ]; test_api(test_slots); @@ -664,8 +673,6 @@ mod tests { let _ = env_logger::try_init(); - let rng = &mut thread_rng(); - let grand_slot = StorageSlot::Mapping(vec![10, 20], TEST_SLOT as usize); let parent_slot = StorageSlot::Node(StorageSlotNode::new_mapping(grand_slot, vec![30, 40]).unwrap()); @@ -676,22 +683,20 @@ mod tests { let storage_slot2 = StorageSlot::Node(StorageSlotNode::new_struct(parent_slot, TEST_EVM_WORDS[1])); - let mut metadata1 = MetadataGadget::sample(TEST_SLOT, TEST_EVM_WORDS[0]); - // We only extract the first column for simple slot. - metadata1.num_extracted_columns = 1; - // Set the second test slot and EVM word. - metadata1.table_info[1].slot = TEST_SLOT.to_field(); - metadata1.table_info[1].evm_word = TEST_EVM_WORDS[1].to_field(); - let mut metadata2 = metadata1.clone(); - metadata2.evm_word = TEST_EVM_WORDS[1]; - // Swap the column infos of the two test slots. - metadata2.table_info[0] = metadata1.table_info[1].clone(); - metadata2.table_info[1] = metadata1.table_info[0].clone(); - - let [outer_key_id, inner_key_id] = array::from_fn(|_| Some(rng.gen())); + let table_info = TEST_EVM_WORDS + .into_iter() + .map(|evm_word| { + let mut col_info = ColumnInfo::sample(); + col_info.slot = F::from_canonical_u8(TEST_SLOT); + col_info.evm_word = F::from_canonical_u32(evm_word); + + col_info + }) + .collect_vec(); + let test_slots = [ - StorageSlotInfo::new(storage_slot1, metadata1, outer_key_id, inner_key_id), - StorageSlotInfo::new(storage_slot2, metadata2, outer_key_id, inner_key_id), + StorageSlotInfo::new(storage_slot1, table_info.clone()), + StorageSlotInfo::new(storage_slot2, table_info), ]; test_api(test_slots); @@ -705,8 +710,14 @@ mod tests { let _ = env_logger::try_init(); let storage_slot = StorageSlot::Simple(TEST_SLOT as usize); - let metadata = MetadataGadget::sample(TEST_SLOT, 0); - let test_slot = StorageSlotInfo::new(storage_slot, metadata, None, None); + let table_info = { + let mut col_info = ColumnInfo::sample(); + col_info.slot = F::from_canonical_u8(TEST_SLOT); + col_info.evm_word = F::ZERO; + + vec![col_info] + }; + let test_slot = StorageSlotInfo::new(storage_slot, table_info); test_branch_with_multiple_children(NUM_CHILDREN, test_slot); } @@ -742,22 +753,29 @@ mod tests { encoded_proof }; + // Construct the table info for testing. + let table_info = { + let mut col_info = ColumnInfo::sample(); + col_info.slot = F::from_canonical_u8(TEST_SLOT); + col_info.evm_word = F::from_canonical_u32(TEST_EVM_WORD); + + vec![col_info] + }; + // Test for single variable leaf. let parent_slot = StorageSlot::Simple(TEST_SLOT as usize); let storage_slot = StorageSlot::Node(StorageSlotNode::new_struct( parent_slot.clone(), TEST_EVM_WORD, )); - let mut metadata = MetadataGadget::sample(TEST_SLOT, 0); - // We only extract the first column for simple slot. - metadata.num_extracted_columns = 1; - let test_slot = StorageSlotInfo::new(storage_slot, metadata, None, None); + let test_slot = StorageSlotInfo::new(storage_slot, table_info.clone()); let mut test_trie = generate_test_trie(1, &test_slot); let proof = test_trie.trie.get_proof(&test_trie.mpt_keys[0]).unwrap(); test_circuit_input(CircuitInput::new_single_variable_leaf( proof.last().unwrap().to_vec(), TEST_SLOT, - test_slot.metadata, + TEST_EVM_WORD, + table_info.clone(), )); // Test for mapping variable leaf. @@ -766,19 +784,17 @@ mod tests { parent_slot.clone(), TEST_EVM_WORD, )); - let mut metadata = MetadataGadget::sample(TEST_SLOT, TEST_EVM_WORD); - // We only extract the first column. - metadata.num_extracted_columns = 1; - let key_id = rng.gen(); - let test_slot = StorageSlotInfo::new(storage_slot, metadata, Some(key_id), None); + let test_slot = StorageSlotInfo::new(storage_slot, table_info.clone()); let mut test_trie = generate_test_trie(1, &test_slot); let proof = test_trie.trie.get_proof(&test_trie.mpt_keys[0]).unwrap(); + let key_id = rng.gen(); test_circuit_input(CircuitInput::new_mapping_variable_leaf( proof.last().unwrap().to_vec(), TEST_SLOT, TEST_OUTER_KEY.to_vec(), key_id, - test_slot.metadata, + TEST_EVM_WORD, + table_info.clone(), )); // Test for mapping of mappings leaf. @@ -788,27 +804,18 @@ mod tests { ); let storage_slot = StorageSlot::Node(StorageSlotNode::new_struct(parent_slot, TEST_EVM_WORD)); - let mut metadata = MetadataGadget::sample(TEST_SLOT, TEST_EVM_WORD); - // We only extract the first column. - metadata.num_extracted_columns = 1; - let outer_key_id = rng.gen(); - let inner_key_id = rng.gen(); - let test_slot = StorageSlotInfo::new( - storage_slot, - metadata, - Some(outer_key_id), - Some(inner_key_id), - ); + let test_slot = StorageSlotInfo::new(storage_slot, table_info.clone()); let mut test_trie = generate_test_trie(2, &test_slot); let proof = test_trie.trie.get_proof(&test_trie.mpt_keys[0]).unwrap(); + let outer_key_id = rng.gen(); + let inner_key_id = rng.gen(); let encoded = test_circuit_input(CircuitInput::new_mapping_of_mappings_leaf( proof.last().unwrap().to_vec(), TEST_SLOT, - TEST_OUTER_KEY.to_vec(), - TEST_INNER_KEY.to_vec(), - outer_key_id, - inner_key_id, - test_slot.metadata, + (TEST_OUTER_KEY.to_vec(), outer_key_id), + (TEST_INNER_KEY.to_vec(), inner_key_id), + TEST_EVM_WORD, + table_info, )); // Test for branch. @@ -880,102 +887,123 @@ mod tests { assert_eq!(leaf_tuple.len(), 2); let value = leaf_tuple[1][1..].to_vec().try_into().unwrap(); - let metadata = test_slot.metadata().clone(); - let evm_word = metadata.evm_word; - let table_info = metadata.actual_table_info().to_vec(); + let evm_word = test_slot.evm_word(); + let table_info = test_slot.table_info(); + let metadata = test_slot.metadata::(); let extracted_column_identifiers = metadata.extracted_column_identifiers(); - let (expected_metadata_digest, expected_values_digest, circuit_input) = match test_slot.slot + // Build the identifier extra data, it's used to compute the key IDs. + const TEST_CONTRACT_ADDRESS: &str = "0x105dD0eF26b92a3698FD5AaaF688577B9Cafd970"; + const TEST_CHAIN_ID: u64 = 1000; + let id_extra = identifier_raw_extra( + &Address::from_str(TEST_CONTRACT_ADDRESS).unwrap(), + TEST_CHAIN_ID, + vec![], + ); + + let (expected_metadata_digest, expected_values_digest, circuit_input) = match &test_slot + .slot { // Simple variable slot StorageSlot::Simple(slot) => { let metadata_digest = compute_leaf_single_metadata_digest::< TEST_MAX_COLUMNS, TEST_MAX_FIELD_PER_EVM, - >(table_info.clone()); + >(table_info.to_vec()); let values_digest = compute_leaf_single_values_digest::( - table_info, + table_info.to_vec(), &extracted_column_identifiers, value, ); - let circuit_input = - CircuitInput::new_single_variable_leaf(node, slot as u8, metadata); + let circuit_input = CircuitInput::new_single_variable_leaf( + node, + *slot as u8, + evm_word, + table_info.to_vec(), + ); (metadata_digest, values_digest, circuit_input) } // Mapping variable StorageSlot::Mapping(mapping_key, slot) => { + let outer_key_id = test_slot.outer_key_id_raw(id_extra).unwrap(); let metadata_digest = compute_leaf_mapping_metadata_digest::< TEST_MAX_COLUMNS, TEST_MAX_FIELD_PER_EVM, >( - table_info.clone(), slot as u8, test_slot.outer_key_id + table_info.to_vec(), *slot as u8, outer_key_id ); let values_digest = compute_leaf_mapping_values_digest::( - table_info, + table_info.to_vec(), &extracted_column_identifiers, value, mapping_key.clone(), evm_word, - test_slot.outer_key_id, + outer_key_id, ); let circuit_input = CircuitInput::new_mapping_variable_leaf( node, - slot as u8, - mapping_key, - test_slot.outer_key_id, - metadata, + *slot as u8, + mapping_key.clone(), + outer_key_id, + evm_word, + table_info.to_vec(), ); (metadata_digest, values_digest, circuit_input) } - StorageSlot::Node(StorageSlotNode::Struct(parent, _)) => match *parent { + StorageSlot::Node(StorageSlotNode::Struct(parent, _)) => match *parent.clone() { // Simple Struct StorageSlot::Simple(slot) => { let metadata_digest = compute_leaf_single_metadata_digest::< TEST_MAX_COLUMNS, TEST_MAX_FIELD_PER_EVM, - >(table_info.clone()); + >(table_info.to_vec()); let values_digest = compute_leaf_single_values_digest::( - table_info, + table_info.to_vec(), &extracted_column_identifiers, value, ); - let circuit_input = - CircuitInput::new_single_variable_leaf(node, slot as u8, metadata); + let circuit_input = CircuitInput::new_single_variable_leaf( + node, + slot as u8, + evm_word, + table_info.to_vec(), + ); (metadata_digest, values_digest, circuit_input) } // Mapping Struct StorageSlot::Mapping(mapping_key, slot) => { - let metadata_digest = compute_leaf_mapping_metadata_digest::< - TEST_MAX_COLUMNS, - TEST_MAX_FIELD_PER_EVM, - >( - table_info.clone(), slot as u8, test_slot.outer_key_id - ); + let outer_key_id = test_slot.outer_key_id_raw(id_extra).unwrap(); + let metadata_digest = + compute_leaf_mapping_metadata_digest::< + TEST_MAX_COLUMNS, + TEST_MAX_FIELD_PER_EVM, + >(table_info.to_vec(), slot as u8, outer_key_id); let values_digest = compute_leaf_mapping_values_digest::( - table_info, + table_info.to_vec(), &extracted_column_identifiers, value, mapping_key.clone(), evm_word, - test_slot.outer_key_id, + outer_key_id, ); let circuit_input = CircuitInput::new_mapping_variable_leaf( node, slot as u8, mapping_key, - test_slot.outer_key_id, - metadata, + outer_key_id, + evm_word, + table_info.to_vec(), ); (metadata_digest, values_digest, circuit_input) @@ -984,35 +1012,35 @@ mod tests { StorageSlot::Node(StorageSlotNode::Mapping(grand, inner_mapping_key)) => { match *grand { StorageSlot::Mapping(outer_mapping_key, slot) => { - let metadata_digest = compute_leaf_mapping_of_mappings_metadata_digest::< - TEST_MAX_COLUMNS, - TEST_MAX_FIELD_PER_EVM, - >( - table_info.clone(), - slot as u8, - test_slot.outer_key_id, - test_slot.inner_key_id, - ); + let outer_key_id = + test_slot.outer_key_id_raw(id_extra.clone()).unwrap(); + let inner_key_id = test_slot.inner_key_id_raw(id_extra).unwrap(); + let metadata_digest = + compute_leaf_mapping_of_mappings_metadata_digest::< + TEST_MAX_COLUMNS, + TEST_MAX_FIELD_PER_EVM, + >( + table_info.to_vec(), slot as u8, outer_key_id, inner_key_id + ); let values_digest = compute_leaf_mapping_of_mappings_values_digest::< TEST_MAX_FIELD_PER_EVM, >( - table_info, + table_info.to_vec(), &extracted_column_identifiers, value, evm_word, - (outer_mapping_key.clone(), test_slot.outer_key_id), - (inner_mapping_key.clone(), test_slot.inner_key_id), + (outer_mapping_key.clone(), outer_key_id), + (inner_mapping_key.clone(), inner_key_id), ); let circuit_input = CircuitInput::new_mapping_of_mappings_leaf( node, slot as u8, - outer_mapping_key, - inner_mapping_key, - test_slot.outer_key_id, - test_slot.inner_key_id, - metadata, + (outer_mapping_key, outer_key_id), + (inner_mapping_key, inner_key_id), + evm_word, + table_info.to_vec(), ); (metadata_digest, values_digest, circuit_input) diff --git a/mp2-v1/src/values_extraction/gadgets/column_gadget.rs b/mp2-v1/src/values_extraction/gadgets/column_gadget.rs index e0aff079d..291b8a56c 100644 --- a/mp2-v1/src/values_extraction/gadgets/column_gadget.rs +++ b/mp2-v1/src/values_extraction/gadgets/column_gadget.rs @@ -55,12 +55,17 @@ impl<'a, const MAX_FIELD_PER_EVM: usize> ColumnGadget<'a, MAX_FIELD_PER_EVM> { // as a big-endian integer. let bytes = &(0..=u8::MAX as u16).collect_vec(); let mut lookup_inputs = [bytes; NUM_BITS_LOOKUP_TABLES]; - let last_bits_lookup_indexes = add_last_bits_lookup_tables(b, lookup_inputs); - // The maxiumn lookup value is `u8::MAX + 8`, since the maxiumn `info.length` is 256, + // This maxiumn lookup value is `u8::MAX + 8`, since the maxiumn `info.length` is 256, // and we need to compute `first_bits_5(info.length + 7)`. let first_bits_5_input = (0..=u8::MAX as u16 + 8).collect_vec(); lookup_inputs[4] = &first_bits_5_input; let first_bits_lookup_indexes = add_first_bits_lookup_tables(b, lookup_inputs); + lookup_inputs[4] = bytes; + // This maxiumn lookup value is `256`, since the maxiumn `info.length` is 256, + // and we need to compute `last_bits_3(info.length)`. + let last_bits_3_input = (0..=u8::MAX as u16 + 1).collect_vec(); + lookup_inputs[2] = &last_bits_3_input; + let last_bits_lookup_indexes = add_last_bits_lookup_tables(b, lookup_inputs); // Accumulate to compute the value digest. let mut value_digest = b.curve_zero(); @@ -71,7 +76,7 @@ impl<'a, const MAX_FIELD_PER_EVM: usize> ColumnGadget<'a, MAX_FIELD_PER_EVM> { let is_extracted = self.is_extracted_columns[i]; // Extract the value by column info. - let extracted_value = extract_value( + let extracted_value = extract_value_target( b, info, self.value, @@ -149,7 +154,7 @@ fn add_last_bits_lookup_tables( } /// Extract the value by the column info. -fn extract_value( +fn extract_value_target( b: &mut CBuilder, info: &ColumnInfoTarget, value_bytes: &[Target; MAPPING_LEAF_VALUE_LEN], @@ -294,65 +299,93 @@ impl ColumnGadgetData { /// Compute the values digest. pub fn digest(&self) -> Point { + let value = self + .value + .map(|f| u8::try_from(f.to_canonical_u64()).unwrap()); self.table_info[..self.num_extracted_columns] .iter() .fold(Point::NEUTRAL, |acc, info| { - let extracted_value = self.extract_value(info); + let extracted_value = extract_value(&value, info); // digest = D(info.identifier || pack(extracted_value)) let inputs = once(info.identifier) - .chain(extracted_value.pack(Endianness::Big)) + .chain( + extracted_value + .pack(Endianness::Big) + .into_iter() + .map(F::from_canonical_u32), + ) .collect_vec(); let digest = map_to_curve_point(&inputs); acc + digest }) } +} - fn extract_value(&self, info: &ColumnInfo) -> [F; MAPPING_LEAF_VALUE_LEN] { - let bit_offset = u8::try_from(info.bit_offset.to_canonical_u64()).unwrap(); - assert!(bit_offset <= 8); - let [byte_offset, length] = - [info.byte_offset, info.length].map(|f| usize::try_from(f.to_canonical_u64()).unwrap()); +pub fn extract_value( + value_bytes: &[u8; MAPPING_LEAF_VALUE_LEN], + info: &ColumnInfo, +) -> [u8; MAPPING_LEAF_VALUE_LEN] { + let bit_offset = u8::try_from(info.bit_offset.to_canonical_u64()).unwrap(); + assert!(bit_offset <= 8); + let [byte_offset, length] = + [info.byte_offset, info.length].map(|f| usize::try_from(f.to_canonical_u64()).unwrap()); - let value_bytes = self - .value - .map(|f| u8::try_from(f.to_canonical_u64()).unwrap()); + // last_byte_offset = info.byte_offset + ceil(info.length / 8) - 1 + let last_byte_offset = byte_offset + length.div_ceil(8) - 1; - // last_byte_offset = info.byte_offset + ceil(info.length / 8) - 1 - let last_byte_offset = byte_offset + length.div_ceil(8) - 1; + // Extract all the bits of the field aligined with bytes. + let mut result_bytes = Vec::with_capacity(last_byte_offset - byte_offset + 1); + for i in byte_offset..=last_byte_offset { + // Get the current and next bytes. + let current_byte = u16::from(value_bytes[i]); + let next_byte = if i < MAPPING_LEAF_VALUE_LEN - 1 { + u16::from(value_bytes[i + 1]) + } else { + 0 + }; - // Extract all the bits of the field aligined with bytes. - let mut result_bytes = Vec::with_capacity(last_byte_offset - byte_offset + 1); - for i in byte_offset..=last_byte_offset { - // Get the current and next bytes. - let current_byte = u16::from(value_bytes[i]); - let next_byte = if i < MAPPING_LEAF_VALUE_LEN - 1 { - u16::from(value_bytes[i + 1]) - } else { - 0 - }; + // actual_byte = last_bits(current_byte, 8 - bit_offset) * 2^bit_offset + first_bits(next_byte, bit_offset) + let actual_byte = (last_bits(current_byte, 8 - bit_offset) << bit_offset) + + first_bits(next_byte, bit_offset); - // actual_byte = last_bits(current_byte, 8 - bit_offset) * 2^bit_offset + first_bits(next_byte, bit_offset) - let actual_byte = (last_bits(current_byte, 8 - bit_offset) << bit_offset) - + first_bits(next_byte, bit_offset); + result_bytes.push(u8::try_from(actual_byte).unwrap()); + } - result_bytes.push(u8::try_from(actual_byte).unwrap()); - } + // At last we need to retain only the first `info.length % 8` bits for + // the last byte of result. + let mut last_byte = u16::from(*result_bytes.last().unwrap()); + let length_mod_8 = length % 8; + if length_mod_8 > 0 { + // If length_mod_8 == 0, we don't need to cut any bit. + last_byte = first_bits(last_byte, u8::try_from(length_mod_8).unwrap()); + } + *result_bytes.last_mut().unwrap() = u8::try_from(last_byte).unwrap(); - // At last we need to retain only the first `info.length % 8` bits for - // the last byte of result. - let mut last_byte = u16::from(*result_bytes.last().unwrap()); - let length_mod_8 = length % 8; - if length_mod_8 > 0 { - // If length_mod_8 == 0, we don't need to cut any bit. - last_byte = first_bits(last_byte, u8::try_from(length_mod_8).unwrap()); - } - *result_bytes.last_mut().unwrap() = u8::try_from(last_byte).unwrap(); + // Normalize left. + left_pad32(&result_bytes) +} - // Normalize left. - left_pad32(&result_bytes).map(F::from_canonical_u8) - } +/// Filter to get the column identifiers of one table by the slot and EVM word. +/// We save multiple simple slots in one table, and only one mapping slot in one table. +pub fn filter_table_column_identifiers( + table_info: &[ColumnInfo], + slot: u8, + evm_word: u32, +) -> Vec { + table_info + .iter() + .filter_map(|col_info| { + if col_info.slot() == F::from_canonical_u8(slot) + && col_info.evm_word() == F::from_canonical_u32(evm_word) + { + Some(col_info.identifier().to_canonical_u64()) + } else { + None + } + }) + .collect() } #[cfg(test)] diff --git a/mp2-v1/src/values_extraction/gadgets/column_info.rs b/mp2-v1/src/values_extraction/gadgets/column_info.rs index b233a55be..8ef2c176c 100644 --- a/mp2-v1/src/values_extraction/gadgets/column_info.rs +++ b/mp2-v1/src/values_extraction/gadgets/column_info.rs @@ -1,5 +1,6 @@ //! Column information for values extraction +use crate::api::SlotInput; use itertools::{zip_eq, Itertools}; use mp2_common::{ group_hashing::map_to_curve_point, @@ -9,6 +10,7 @@ use mp2_common::{ }; use plonky2::{ field::types::{Field, Sample}, + hash::hash_types::HashOut, iop::{target::Target, witness::WitnessWrite}, plonk::config::Hasher, }; @@ -62,6 +64,18 @@ impl ColumnInfo { } } + pub fn new_from_slot_input(identifier: u64, slot_input: &SlotInput) -> Self { + Self::new( + slot_input.slot, + identifier, + slot_input.byte_offset, + // TODO: Will remove this bit_offset from the internal data structures and the circuit. + 0, + slot_input.length, + slot_input.evm_word, + ) + } + /// Create a sample column info. It could be used in integration tests. pub fn sample() -> Self { let rng = &mut thread_rng(); @@ -82,8 +96,8 @@ impl ColumnInfo { evm_word, } } - /// Compute the column information digest. - pub fn digest(&self) -> Point { + /// Compute the MPT metadata. + pub fn mpt_metadata(&self) -> HashOut { // metadata = H(info.slot || info.evm_word || info.byte_offset || info.bit_offset || info.length) let inputs = vec![ self.slot, @@ -92,7 +106,12 @@ impl ColumnInfo { self.bit_offset, self.length, ]; - let metadata = H::hash_no_pad(&inputs); + H::hash_no_pad(&inputs) + } + + /// Compute the column information digest. + pub fn digest(&self) -> Point { + let metadata = self.mpt_metadata(); // digest = D(mpt_metadata || info.identifier) let inputs = metadata diff --git a/mp2-v1/src/values_extraction/gadgets/metadata_gadget.rs b/mp2-v1/src/values_extraction/gadgets/metadata_gadget.rs index f031435ac..892be71dd 100644 --- a/mp2-v1/src/values_extraction/gadgets/metadata_gadget.rs +++ b/mp2-v1/src/values_extraction/gadgets/metadata_gadget.rs @@ -29,7 +29,7 @@ use serde::{Deserialize, Serialize}; use std::{array, iter::once}; #[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] -pub struct MetadataGadget { +pub struct ColumnsMetadata { #[serde( serialize_with = "serialize_long_array", deserialize_with = "deserialize_long_array" @@ -45,7 +45,7 @@ pub struct MetadataGadget - MetadataGadget + ColumnsMetadata { /// Create a new MPT metadata. pub fn new( @@ -145,7 +145,13 @@ impl pub fn evm_word(&self) -> u32 { self.evm_word } +} + +pub struct MetadataGadget; +impl + MetadataGadget +{ pub(crate) fn build(b: &mut CBuilder) -> MetadataTarget { let table_info = array::from_fn(|_| b.add_virtual_column_info()); let [is_actual_columns, is_extracted_columns] = @@ -161,24 +167,24 @@ impl } pub(crate) fn assign( - &self, pw: &mut PartialWitness, + columns_metadata: &ColumnsMetadata, metadata_target: &MetadataTarget, ) { - pw.set_column_info_target_arr(&metadata_target.table_info, &self.table_info); + pw.set_column_info_target_arr(&metadata_target.table_info, &columns_metadata.table_info); metadata_target .is_actual_columns .iter() .enumerate() - .for_each(|(i, t)| pw.set_bool_target(*t, i < self.num_actual_columns)); + .for_each(|(i, t)| pw.set_bool_target(*t, i < columns_metadata.num_actual_columns)); metadata_target .is_extracted_columns .iter() .enumerate() - .for_each(|(i, t)| pw.set_bool_target(*t, i < self.num_extracted_columns)); + .for_each(|(i, t)| pw.set_bool_target(*t, i < columns_metadata.num_extracted_columns)); pw.set_target( metadata_target.evm_word, - F::from_canonical_u32(self.evm_word), + F::from_canonical_u32(columns_metadata.evm_word), ); } } @@ -314,7 +320,7 @@ pub(crate) mod tests { #[derive(Clone, Debug)] struct TestMedataCircuit { - metadata_gadget: MetadataGadget, + columns_metadata: ColumnsMetadata, slot: u8, expected_num_actual_columns: usize, expected_metadata_digest: Point, @@ -348,7 +354,7 @@ pub(crate) mod tests { } fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { - self.metadata_gadget.assign(pw, &wires.0); + MetadataGadget::assign(pw, &self.columns_metadata, &wires.0); pw.set_target(wires.1, F::from_canonical_u8(self.slot)); pw.set_target( wires.2, @@ -365,12 +371,12 @@ pub(crate) mod tests { let slot = rng.gen(); let evm_word = rng.gen(); - let metadata_gadget = MetadataGadget::sample(slot, evm_word); + let metadata_gadget = ColumnsMetadata::sample(slot, evm_word); let expected_num_actual_columns = metadata_gadget.num_actual_columns(); let expected_metadata_digest = metadata_gadget.digest(); let test_circuit = TestMedataCircuit { - metadata_gadget, + columns_metadata: metadata_gadget, slot, expected_num_actual_columns, expected_metadata_digest, diff --git a/mp2-v1/src/values_extraction/gadgets/mod.rs b/mp2-v1/src/values_extraction/gadgets/mod.rs index 483f92142..08059cda0 100644 --- a/mp2-v1/src/values_extraction/gadgets/mod.rs +++ b/mp2-v1/src/values_extraction/gadgets/mod.rs @@ -1,3 +1,3 @@ -pub(crate) mod column_gadget; +pub mod column_gadget; pub mod column_info; pub mod metadata_gadget; diff --git a/mp2-v1/src/values_extraction/leaf_mapping.rs b/mp2-v1/src/values_extraction/leaf_mapping.rs index 8430c5132..ccb5660de 100644 --- a/mp2-v1/src/values_extraction/leaf_mapping.rs +++ b/mp2-v1/src/values_extraction/leaf_mapping.rs @@ -3,7 +3,7 @@ use crate::values_extraction::{ gadgets::{ column_gadget::ColumnGadget, - metadata_gadget::{MetadataGadget, MetadataTarget}, + metadata_gadget::{ColumnsMetadata, MetadataTarget}, }, public_inputs::{PublicInputs, PublicInputsArgs}, KEY_ID_PREFIX, @@ -38,6 +38,8 @@ use recursion_framework::circuit_builder::CircuitLogicWires; use serde::{Deserialize, Serialize}; use std::{iter, iter::once}; +use super::gadgets::metadata_gadget::MetadataGadget; + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct LeafMappingWires< const NODE_LEN: usize, @@ -72,7 +74,7 @@ pub struct LeafMappingCircuit< pub(crate) node: Vec, pub(crate) slot: MappingSlot, pub(crate) key_id: F, - pub(crate) metadata: MetadataGadget, + pub(crate) metadata: ColumnsMetadata, } impl @@ -150,9 +152,9 @@ where .collect(); let hash = b.hash_n_to_hash_no_pad::(inputs); let row_id = hash_to_int_target(b, hash); - let row_id = b.biguint_to_nonnative(&row_id); // values_digest = values_digest * row_id + let row_id = b.biguint_to_nonnative(&row_id); let values_digest = b.curve_scalar_mul(values_digest, &row_id); // Only one leaf in this node. @@ -194,7 +196,7 @@ where pw.set_target(wires.key_id, self.key_id); self.slot .assign_struct(pw, &wires.slot, self.metadata.evm_word); - self.metadata.assign(pw, &wires.metadata); + MetadataGadget::assign(pw, &self.metadata, &wires.metadata); } } @@ -301,7 +303,7 @@ mod tests { let evm_word = storage_slot.evm_offset(); let key_id = rng.gen(); let metadata = - MetadataGadget::::sample(slot, evm_word); + ColumnsMetadata::::sample(slot, evm_word); // Compute the metadata digest. let table_info = metadata.actual_table_info().to_vec(); let extracted_column_identifiers = metadata.extracted_column_identifiers(); diff --git a/mp2-v1/src/values_extraction/leaf_mapping_of_mappings.rs b/mp2-v1/src/values_extraction/leaf_mapping_of_mappings.rs index 5303751bc..95a879b98 100644 --- a/mp2-v1/src/values_extraction/leaf_mapping_of_mappings.rs +++ b/mp2-v1/src/values_extraction/leaf_mapping_of_mappings.rs @@ -5,7 +5,7 @@ use crate::values_extraction::{ gadgets::{ column_gadget::ColumnGadget, - metadata_gadget::{MetadataGadget, MetadataTarget}, + metadata_gadget::{ColumnsMetadata, MetadataTarget}, }, public_inputs::{PublicInputs, PublicInputsArgs}, INNER_KEY_ID_PREFIX, OUTER_KEY_ID_PREFIX, @@ -40,6 +40,8 @@ use recursion_framework::circuit_builder::CircuitLogicWires; use serde::{Deserialize, Serialize}; use std::{iter, iter::once}; +use super::gadgets::metadata_gadget::MetadataGadget; + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct LeafMappingOfMappingsWires< const NODE_LEN: usize, @@ -79,7 +81,7 @@ pub struct LeafMappingOfMappingsCircuit< pub(crate) inner_key: Vec, pub(crate) outer_key_id: F, pub(crate) inner_key_id: F, - pub(crate) metadata: MetadataGadget, + pub(crate) metadata: ColumnsMetadata, } impl @@ -234,7 +236,7 @@ where &self.inner_key, self.metadata.evm_word, ); - self.metadata.assign(pw, &wires.metadata); + MetadataGadget::assign(pw, &self.metadata, &wires.metadata); } } @@ -349,7 +351,7 @@ mod tests { let evm_word = storage_slot.evm_offset(); let [outer_key_id, inner_key_id] = array::from_fn(|_| rng.gen()); let metadata = - MetadataGadget::::sample(slot, evm_word); + ColumnsMetadata::::sample(slot, evm_word); // Compute the metadata digest. let table_info = metadata.actual_table_info().to_vec(); let extracted_column_identifiers = metadata.extracted_column_identifiers(); diff --git a/mp2-v1/src/values_extraction/leaf_single.rs b/mp2-v1/src/values_extraction/leaf_single.rs index fe730665b..9a7959f86 100644 --- a/mp2-v1/src/values_extraction/leaf_single.rs +++ b/mp2-v1/src/values_extraction/leaf_single.rs @@ -3,7 +3,7 @@ use crate::values_extraction::{ gadgets::{ column_gadget::ColumnGadget, - metadata_gadget::{MetadataGadget, MetadataTarget}, + metadata_gadget::{ColumnsMetadata, MetadataTarget}, }, public_inputs::{PublicInputs, PublicInputsArgs}, }; @@ -31,6 +31,8 @@ use recursion_framework::circuit_builder::CircuitLogicWires; use serde::{Deserialize, Serialize}; use std::iter::once; +use super::gadgets::metadata_gadget::MetadataGadget; + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct LeafSingleWires< const NODE_LEN: usize, @@ -60,7 +62,7 @@ pub struct LeafSingleCircuit< > { pub(crate) node: Vec, pub(crate) slot: SimpleSlot, - pub(crate) metadata: MetadataGadget, + pub(crate) metadata: ColumnsMetadata, } impl @@ -146,7 +148,7 @@ where ); self.slot .assign_struct(pw, &wires.slot, self.metadata.evm_word); - self.metadata.assign(pw, &wires.metadata); + MetadataGadget::assign(pw, &self.metadata, &wires.metadata); } } @@ -247,7 +249,7 @@ mod tests { let slot = storage_slot.slot(); let evm_word = storage_slot.evm_offset(); let metadata = - MetadataGadget::::sample(slot, evm_word); + ColumnsMetadata::::sample(slot, evm_word); // Compute the metadata digest. let metadata_digest = metadata.digest(); // Compute the values digest. diff --git a/mp2-v1/src/values_extraction/mod.rs b/mp2-v1/src/values_extraction/mod.rs index 6ed757900..adae77a54 100644 --- a/mp2-v1/src/values_extraction/mod.rs +++ b/mp2-v1/src/values_extraction/mod.rs @@ -1,19 +1,22 @@ use crate::api::SlotInput; use alloy::primitives::Address; use gadgets::{ - column_gadget::ColumnGadgetData, column_info::ColumnInfo, metadata_gadget::MetadataGadget, + column_gadget::{filter_table_column_identifiers, ColumnGadgetData}, + column_info::ColumnInfo, + metadata_gadget::ColumnsMetadata, }; use itertools::Itertools; use mp2_common::{ eth::{left_pad32, StorageSlot}, group_hashing::map_to_curve_point, poseidon::{empty_poseidon_hash, hash_to_int_value, H}, - types::MAPPING_LEAF_VALUE_LEN, + types::{HashOutput, MAPPING_LEAF_VALUE_LEN}, utils::{Endianness, Packer, ToFields}, F, }; use plonky2::{ field::types::{Field, PrimeField64}, + hash::hash_types::HashOut, plonk::config::Hasher, }; use plonky2_ecgfp5::curve::{curve::Point as Digest, scalar_field::Scalar}; @@ -42,48 +45,99 @@ pub(crate) const OUTER_KEY_ID_PREFIX: &[u8] = b"\0OUT_KEY"; pub(crate) const BLOCK_ID_DST: &[u8] = b"BLOCK_NUMBER"; -/// Storage slot information for generating the proof +/// Storage slot information for generating the extraction proof #[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] -pub struct StorageSlotInfo { +pub struct StorageSlotInfo { slot: StorageSlot, - metadata: MetadataGadget, - outer_key_id: ColumnId, - inner_key_id: ColumnId, + table_info: Vec, } -impl - StorageSlotInfo -{ - pub fn new( - slot: StorageSlot, - metadata: MetadataGadget, - outer_key_id: Option, - inner_key_id: Option, - ) -> Self { - let [outer_key_id, inner_key_id] = - [outer_key_id, inner_key_id].map(|key_id| key_id.unwrap_or_default()); - Self { - slot, - metadata, - outer_key_id, - inner_key_id, - } +impl StorageSlotInfo { + pub fn new(slot: StorageSlot, table_info: Vec) -> Self { + Self { slot, table_info } } pub fn slot(&self) -> &StorageSlot { &self.slot } - pub fn metadata(&self) -> &MetadataGadget { - &self.metadata + pub fn table_info(&self) -> &[ColumnInfo] { + &self.table_info + } + + pub fn evm_word(&self) -> u32 { + self.slot.evm_offset() + } + + pub fn metadata( + &self, + ) -> ColumnsMetadata { + let evm_word = self.evm_word(); + let extracted_column_identifiers = + filter_table_column_identifiers(&self.table_info, self.slot.slot(), evm_word); + + ColumnsMetadata::new( + self.table_info.clone(), + &extracted_column_identifiers, + evm_word, + ) } - pub fn outer_key_id(&self) -> ColumnId { - self.outer_key_id + pub fn outer_key_id( + &self, + contract_address: &Address, + chain_id: u64, + extra: Vec, + ) -> Option { + let extra = identifier_raw_extra(contract_address, chain_id, extra); + + self.outer_key_id_raw(extra) + } + + pub fn inner_key_id( + &self, + contract_address: &Address, + chain_id: u64, + extra: Vec, + ) -> Option { + let extra = identifier_raw_extra(contract_address, chain_id, extra); + + self.inner_key_id_raw(extra) } - pub fn inner_key_id(&self) -> ColumnId { - self.inner_key_id + pub fn outer_key_id_raw(&self, extra: Vec) -> Option { + let slot = self.slot().slot(); + let num_mapping_keys = self.slot().mapping_keys().len(); + match num_mapping_keys { + _ if num_mapping_keys == 0 => None, + _ if num_mapping_keys == 1 => Some(identifier_for_mapping_key_column_raw(slot, extra)), + _ if num_mapping_keys == 2 => { + Some(identifier_for_outer_mapping_key_column_raw(slot, extra)) + } + _ => panic!("Unsupport for the nested mapping keys of length greater than 2"), + } + } + + pub fn inner_key_id_raw(&self, extra: Vec) -> Option { + let slot = self.slot().slot(); + let num_mapping_keys = self.slot().mapping_keys().len(); + match num_mapping_keys { + _ if num_mapping_keys < 2 => None, + _ if num_mapping_keys == 2 => { + Some(identifier_for_inner_mapping_key_column_raw(slot, extra)) + } + _ => panic!("Unsupport for the nested mapping keys of length greater than 2"), + } + } + + pub fn slot_inputs( + &self, + ) -> Vec { + self.metadata::() + .extracted_table_info() + .iter() + .map(Into::into) + .collect_vec() } } @@ -93,21 +147,36 @@ pub fn identifier_block_column() -> ColumnId { } /// Compute identifier for value column. -/// The value column could be either simple value or mapping value. -/// `id = H(slot || byte_offset || bit_offset || length || evm_word || contract_address || chain_id)[0]` +/// +/// The value column could be either simple or mapping slot. +/// `id = H(slot || byte_offset || length || evm_word || contract_address || chain_id || extra)[0]` pub fn identifier_for_value_column( input: &SlotInput, contract_address: &Address, chain_id: u64, extra: Vec, ) -> ColumnId { + let extra = contract_address + .0 + .into_iter() + .chain(chain_id.to_be_bytes()) + .chain(extra) + .collect_vec(); + + identifier_for_value_column_raw(input, extra) +} + +/// Compute identifier for value column in raw mode. +/// The value column could be either simple or mapping slot. +/// `id = H(slot || byte_offset || length || evm_word || extra)[0]` +/// +/// We could custom the `extra` argument, if it's set to `(contract_address || chain_id || extra)`, +/// It's same with `identifier_for_mapping_key_column`. +pub fn identifier_for_value_column_raw(input: &SlotInput, extra: Vec) -> ColumnId { let inputs = once(input.slot) .chain(input.byte_offset.to_be_bytes()) - .chain(input.bit_offset.to_be_bytes()) .chain(input.length.to_be_bytes()) .chain(input.evm_word.to_be_bytes()) - .chain(contract_address.0.to_vec()) - .chain(chain_id.to_be_bytes()) .chain(extra) .map(F::from_canonical_u8) .collect_vec(); @@ -126,6 +195,15 @@ pub fn identifier_for_mapping_key_column( compute_id_with_prefix(KEY_ID_PREFIX, slot, contract_address, chain_id, extra) } +/// Compute key indetifier for mapping variable in raw mode. +/// `key_id = H(KEY || slot || contract_address || chain_id)[0]` +/// +/// We could custom the `extra` argument, if it's set to `(contract_address || chain_id || extra)`, +/// It's same with `identifier_for_mapping_key_column`. +pub fn identifier_for_mapping_key_column_raw(slot: u8, extra: Vec) -> u64 { + compute_id_with_prefix_raw(KEY_ID_PREFIX, slot, extra) +} + /// Compute outer key indetifier for mapping of mappings variable. /// `outer_key_id = H(OUT_KEY || slot || contract_address || chain_id)[0]` pub fn identifier_for_outer_mapping_key_column( @@ -137,6 +215,14 @@ pub fn identifier_for_outer_mapping_key_column( compute_id_with_prefix(OUTER_KEY_ID_PREFIX, slot, contract_address, chain_id, extra) } +/// Compute outer key indetifier for mapping of mappings variable in raw mode. +/// `outer_key_id = H(OUT_KEY || slot || contract_address || chain_id)[0]` +/// +/// We could custom the `extra` argument, if it's set to `(contract_address || chain_id || extra)`, +/// It's same with `identifier_for_outer_mapping_key_column`. +pub fn identifier_for_outer_mapping_key_column_raw(slot: u8, extra: Vec) -> u64 { + compute_id_with_prefix_raw(OUTER_KEY_ID_PREFIX, slot, extra) +} /// Compute inner key indetifier for mapping of mappings variable. /// `inner_key_id = H(IN_KEY || slot || contract_address || chain_id)[0]` pub fn identifier_for_inner_mapping_key_column( @@ -148,6 +234,15 @@ pub fn identifier_for_inner_mapping_key_column( compute_id_with_prefix(INNER_KEY_ID_PREFIX, slot, contract_address, chain_id, extra) } +/// Compute inner key indetifier for mapping of mappings variable in raw mode. +/// `inner_key_id = H(IN_KEY || slot || extra)[0]` +/// +/// We could custom the `extra` argument, if it's set to `(contract_address || chain_id || extra)`, +/// It's same with `identifier_for_inner_mapping_key_column`. +pub fn identifier_for_inner_mapping_key_column_raw(slot: u8, extra: Vec) -> u64 { + compute_id_with_prefix_raw(INNER_KEY_ID_PREFIX, slot, extra) +} + /// Calculate ID with prefix. fn compute_id_with_prefix( prefix: &[u8], @@ -156,19 +251,70 @@ fn compute_id_with_prefix( chain_id: u64, extra: Vec, ) -> ColumnId { + let extra = identifier_raw_extra(contract_address, chain_id, extra); + + compute_id_with_prefix_raw(prefix, slot, extra) +} + +/// Construct the raw extra by contract address, chain ID and extra data. +pub fn identifier_raw_extra(contract_address: &Address, chain_id: u64, extra: Vec) -> Vec { + contract_address + .0 + .into_iter() + .chain(chain_id.to_be_bytes()) + .chain(extra) + .collect() +} + +/// Calculate ID with prefix in raw mode. +/// +/// We could custom the `extra` argument, if it's set to `(contract_address || chain_id || extra)`, +/// It's same with `compute_id_with_prefix`. +fn compute_id_with_prefix_raw(prefix: &[u8], slot: u8, extra: Vec) -> ColumnId { let inputs: Vec = prefix .iter() .cloned() .chain(once(slot)) - .chain(contract_address.0) - .chain(chain_id.to_be_bytes()) .chain(extra) - .collect::>() + .collect_vec() .to_fields(); H::hash_no_pad(&inputs).elements[0].to_canonical_u64() } +/// Compute the row unique data for single leaf. +pub fn row_unique_data_for_single_leaf() -> HashOutput { + empty_poseidon_hash().into() +} + +/// Compute the row unique data for mapping leaf. +pub fn row_unique_data_for_mapping_leaf(mapping_key: &[u8]) -> HashOutput { + // row_unique_data = H(pack(left_pad32(key)) + let packed_mapping_key = left_pad32(mapping_key) + .pack(Endianness::Big) + .into_iter() + .map(F::from_canonical_u32) + .collect_vec(); + H::hash_no_pad(&packed_mapping_key).into() +} + +/// Compute the row unique data for mapping of mappings leaf. +pub fn row_unique_data_for_mapping_of_mappings_leaf( + outer_mapping_key: &[u8], + inner_mapping_key: &[u8], +) -> HashOutput { + let [packed_outer_key, packed_inner_key] = [outer_mapping_key, inner_mapping_key].map(|key| { + left_pad32(key) + .pack(Endianness::Big) + .into_iter() + .map(F::from_canonical_u32) + }); + // Compute the unique data to identify a row is the mapping key: + // row_unique_data = H(outer_key || inner_key) + let inputs = packed_outer_key.chain(packed_inner_key).collect_vec(); + H::hash_no_pad(&inputs).into() +} + /// Compute the metadata digest for single variable leaf. pub fn compute_leaf_single_metadata_digest< const MAX_COLUMNS: usize, @@ -177,7 +323,7 @@ pub fn compute_leaf_single_metadata_digest< table_info: Vec, ) -> Digest { // We don't need `extracted_column_identifiers` and `evm_word` to compute the metadata digest. - MetadataGadget::::new(table_info, &[], 0).digest() + ColumnsMetadata::::new(table_info, &[], 0).digest() } /// Compute the values digest for single variable leaf. @@ -192,7 +338,7 @@ pub fn compute_leaf_single_values_digest( .digest(); // row_id = H2int(H("") || num_actual_columns) - let inputs = empty_poseidon_hash() + let inputs = HashOut::from(row_unique_data_for_single_leaf()) .to_fields() .into_iter() .chain(once(num_actual_columns)) @@ -216,7 +362,7 @@ pub fn compute_leaf_mapping_metadata_digest< ) -> Digest { // We don't need `extracted_column_identifiers` and `evm_word` to compute the metadata digest. let metadata_digest = - MetadataGadget::::new(table_info, &[], 0).digest(); + ColumnsMetadata::::new(table_info, &[], 0).digest(); // key_column_md = H( "\0KEY" || slot) let key_id_prefix = u32::from_be_bytes(KEY_ID_PREFIX.try_into().unwrap()); @@ -263,8 +409,7 @@ pub fn compute_leaf_mapping_values_digest( let values_key_digest = map_to_curve_point(&inputs); values_digest += values_key_digest; } - // row_unique_data = H(pack(left_pad32(key)) - let row_unique_data = H::hash_no_pad(&packed_mapping_key.collect_vec()); + let row_unique_data = HashOut::from(row_unique_data_for_mapping_leaf(&mapping_key)); // row_id = H2int(row_unique_data || num_actual_columns) let inputs = row_unique_data .to_fields() @@ -291,7 +436,7 @@ pub fn compute_leaf_mapping_of_mappings_metadata_digest< ) -> Digest { // We don't need `extracted_column_identifiers` and `evm_word` to compute the metadata digest. let metadata_digest = - MetadataGadget::::new(table_info, &[], 0).digest(); + ColumnsMetadata::::new(table_info, &[], 0).digest(); // Compute the outer and inner key metadata digests. let [outer_key_digest, inner_key_digest] = [ @@ -338,8 +483,8 @@ pub fn compute_leaf_mapping_of_mappings_values_digest LargeStruct) public structMapping; - // Test mapping of mappings (slot 8) + // Test mapping of mappings (slot 9) mapping(uint256 => mapping(uint256 => LargeStruct)) public mappingOfMappings; @@ -82,4 +90,39 @@ contract Simple { function addToArray(uint256 value) public { arr1.push(value); } + + // Set simple struct. + function setSimpleStruct( + uint256 _field1, + uint128 _field2, + uint128 _field3 + ) public { + simpleStruct.field1 = _field1; + simpleStruct.field2 = _field2; + simpleStruct.field3 = _field3; + } + + // Set mapping struct. + function setMappingStruct( + uint256 _key, + uint256 _field1, + uint128 _field2, + uint128 _field3 + ) public { + structMapping[_key] = LargeStruct(_field1, _field2, _field3); + } + + function changeMappingStruct(MappingStructChange[] memory changes) public { + for (uint256 i = 0; i < changes.length; i++) { + if (changes[i].operation == MappingOperation.Deletion) { + delete structMapping[changes[i].key]; + } else if ( + changes[i].operation == MappingOperation.Insertion || + changes[i].operation == MappingOperation.Update + ) { + setMappingStruct(changes[i].key, changes[i].field1, changes[i].field2, changes[i].field3); + } + } + } + } diff --git a/mp2-v1/tests/common/bindings/simple.rs b/mp2-v1/tests/common/bindings/simple.rs index 654fbbb7e..db613b22e 100644 --- a/mp2-v1/tests/common/bindings/simple.rs +++ b/mp2-v1/tests/common/bindings/simple.rs @@ -9,10 +9,18 @@ interface Simple { address value; MappingOperation operation; } + struct MappingStructChange { + uint256 key; + uint256 field1; + uint128 field2; + uint128 field3; + MappingOperation operation; + } function addToArray(uint256 value) external; function arr1(uint256) external view returns (uint256); function changeMapping(MappingChange[] memory changes) external; + function changeMappingStruct(MappingStructChange[] memory changes) external; function m1(uint256) external view returns (address); function mappingOfMappings(uint256, uint256) external view returns (uint256 field1, uint128 field2, uint128 field3); function s1() external view returns (bool); @@ -20,7 +28,9 @@ interface Simple { function s3() external view returns (string memory); function s4() external view returns (address); function setMapping(uint256 key, address value) external; + function setMappingStruct(uint256 _key, uint256 _field1, uint128 _field2, uint128 _field3) external; function setS2(uint256 newS2) external; + function setSimpleStruct(uint256 _field1, uint128 _field2, uint128 _field3) external; function setSimples(bool newS1, uint256 newS2, string memory newS3, address newS4) external; function simpleStruct() external view returns (uint256 field1, uint128 field2, uint128 field3); function structMapping(uint256) external view returns (uint256 field1, uint128 field2, uint128 field3); @@ -92,6 +102,46 @@ interface Simple { "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "changeMappingStruct", + "inputs": [ + { + "name": "changes", + "type": "tuple[]", + "internalType": "struct Simple.MappingStructChange[]", + "components": [ + { + "name": "key", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "field1", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "field2", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "field3", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "operation", + "type": "uint8", + "internalType": "enum Simple.MappingOperation" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "m1", @@ -215,6 +265,34 @@ interface Simple { "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "setMappingStruct", + "inputs": [ + { + "name": "_key", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_field1", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_field2", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "_field3", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "setS2", @@ -228,6 +306,29 @@ interface Simple { "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "setSimpleStruct", + "inputs": [ + { + "name": "_field1", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_field2", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "_field3", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "setSimples", @@ -317,22 +418,22 @@ pub mod Simple { /// The creation / init bytecode of the contract. /// /// ```text - ///0x608060405234801561000f575f80fd5b506109c68061001d5f395ff3fe608060405234801561000f575f80fd5b50600436106100e5575f3560e01c806388dfddc611610088578063c8af3aa611610063578063c8af3aa614610268578063d15ec8511461027b578063ead18400146102bd578063f25d54f5146102df575f80fd5b806388dfddc614610210578063a314150f1461024a578063a5d666a914610253575f80fd5b80631c134315116100c35780631c134315146101805780632ae42686146101935780636987b1fb146101d35780636cc014de146101f4575f80fd5b80630200225c146100e95780630a4d04f7146100fe5780630c1616c91461016d575b5f80fd5b6100fc6100f73660046105d8565b6102f2565b005b61014361010c366004610697565b600960209081525f9283526040808420909152908252902080546001909101546001600160801b0380821691600160801b90041683565b604080519384526001600160801b0392831660208501529116908201526060015b60405180910390f35b6100fc61017b3660046106b7565b610336565b6100fc61018e366004610797565b610477565b6101bb6101a13660046107c1565b60046020525f90815260409020546001600160a01b031681565b6040516001600160a01b039091168152602001610164565b6101e66101e13660046107c1565b6104a4565b604051908152602001610164565b5f546102009060ff1681565b6040519015158152602001610164565b61014361021e3660046107c1565b60086020525f9081526040902080546001909101546001600160801b0380821691600160801b90041683565b6101e660015481565b61025b6104c3565b60405161016491906107d8565b6003546101bb906001600160a01b031681565b6100fc6102893660046107c1565b600580546001810182555f919091527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00155565b60065460075461014391906001600160801b0380821691600160801b90041683565b6100fc6102ed3660046107c1565b600155565b5f805460ff19168515151790556001839055600261031083826108a8565b50600380546001600160a01b0319166001600160a01b0392909216919091179055505050565b5f5b8151811015610473575f8282815181106103545761035461097c565b602002602001015160400151600281111561037157610371610968565b036103b85760045f83838151811061038b5761038b61097c565b6020908102919091018101515182528101919091526040015f2080546001600160a01b031916905561046b565b60028282815181106103cc576103cc61097c565b60200260200101516040015160028111156103e9576103e9610968565b1480610423575060018282815181106104045761040461097c565b602002602001015160400151600281111561042157610421610968565b145b1561046b5761046b82828151811061043d5761043d61097c565b60200260200101515f015183838151811061045a5761045a61097c565b602002602001015160200151610477565b600101610338565b5050565b5f9182526004602052604090912080546001600160a01b0319166001600160a01b03909216919091179055565b600581815481106104b3575f80fd5b5f91825260209091200154905081565b600280546104d090610824565b80601f01602080910402602001604051908101604052809291908181526020018280546104fc90610824565b80156105475780601f1061051e57610100808354040283529160200191610547565b820191905f5260205f20905b81548152906001019060200180831161052a57829003601f168201915b505050505081565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff811182821017156105865761058661054f565b60405290565b604051601f8201601f1916810167ffffffffffffffff811182821017156105b5576105b561054f565b604052919050565b80356001600160a01b03811681146105d3575f80fd5b919050565b5f805f80608085870312156105eb575f80fd5b843580151581146105fa575f80fd5b93506020858101359350604086013567ffffffffffffffff8082111561061e575f80fd5b818801915088601f830112610631575f80fd5b8135818111156106435761064361054f565b610655601f8201601f1916850161058c565b9150808252898482850101111561066a575f80fd5b80848401858401375f8482840101525080945050505061068c606086016105bd565b905092959194509250565b5f80604083850312156106a8575f80fd5b50508035926020909101359150565b5f60208083850312156106c8575f80fd5b823567ffffffffffffffff808211156106df575f80fd5b818501915085601f8301126106f2575f80fd5b8135818111156107045761070461054f565b610712848260051b0161058c565b81815284810192506060918202840185019188831115610730575f80fd5b938501935b8285101561078b5780858a03121561074b575f80fd5b610753610563565b853581526107628787016105bd565b8782015260408087013560038110610778575f80fd5b9082015284529384019392850192610735565b50979650505050505050565b5f80604083850312156107a8575f80fd5b823591506107b8602084016105bd565b90509250929050565b5f602082840312156107d1575f80fd5b5035919050565b5f602080835283518060208501525f5b81811015610804578581018301518582016040015282016107e8565b505f604082860101526040601f19601f8301168501019250505092915050565b600181811c9082168061083857607f821691505b60208210810361085657634e487b7160e01b5f52602260045260245ffd5b50919050565b601f8211156108a357805f5260205f20601f840160051c810160208510156108815750805b601f840160051c820191505b818110156108a0575f815560010161088d565b50505b505050565b815167ffffffffffffffff8111156108c2576108c261054f565b6108d6816108d08454610824565b8461085c565b602080601f831160018114610909575f84156108f25750858301515b5f19600386901b1c1916600185901b178555610960565b5f85815260208120601f198616915b8281101561093757888601518255948401946001909101908401610918565b508582101561095457878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52603260045260245ffdfea2646970667358221220ca66fade7918263b04a1bbf7b050789f811f5c17ad957facfc9651c3decb1cd564736f6c63430008180033 + ///0x608060405234801561000f575f80fd5b50610d998061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610106575f3560e01c80638026de311161009e578063c7bf4db51161006e578063c7bf4db5146102c9578063c8af3aa6146102dc578063d15ec851146102ef578063ead1840014610331578063f25d54f514610353575f80fd5b80638026de311461025e57806388dfddc614610271578063a314150f146102ab578063a5d666a9146102b4575f80fd5b80631c134315116100d95780631c134315146101ce5780632ae42686146101e15780636987b1fb146102215780636cc014de14610242575f80fd5b80630200225c1461010a5780630a4d04f71461011f5780630c1616c91461018e5780631417a4f0146101a1575b5f80fd5b61011d610118366004610835565b610366565b005b61016461012d3660046108f4565b600960209081525f9283526040808420909152908252902080546001909101546001600160801b0380821691600160801b90041683565b604080519384526001600160801b0392831660208501529116908201526060015b60405180910390f35b61011d61019c366004610945565b6103aa565b61011d6101af366004610a25565b6006929092556001600160801b03918216600160801b02911617600755565b61011d6101dc366004610a5e565b6104eb565b6102096101ef366004610a88565b60046020525f90815260409020546001600160a01b031681565b6040516001600160a01b039091168152602001610185565b61023461022f366004610a88565b610518565b604051908152602001610185565b5f5461024e9060ff1681565b6040519015158152602001610185565b61011d61026c366004610a9f565b610537565b61016461027f366004610a88565b60086020525f9081526040902080546001909101546001600160801b0380821691600160801b90041683565b61023460015481565b6102bc610589565b6040516101859190610ad7565b61011d6102d7366004610b23565b610615565b600354610209906001600160a01b031681565b61011d6102fd366004610a88565b600580546001810182555f919091527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00155565b60065460075461016491906001600160801b0380821691600160801b90041683565b61011d610361366004610a88565b600155565b5f805460ff1916851515179055600183905560026103848382610c7b565b50600380546001600160a01b0319166001600160a01b0392909216919091179055505050565b5f5b81518110156104e7575f8282815181106103c8576103c8610d4f565b60200260200101516040015160028111156103e5576103e5610d3b565b0361042c5760045f8383815181106103ff576103ff610d4f565b6020908102919091018101515182528101919091526040015f2080546001600160a01b03191690556104df565b600282828151811061044057610440610d4f565b602002602001015160400151600281111561045d5761045d610d3b565b14806104975750600182828151811061047857610478610d4f565b602002602001015160400151600281111561049557610495610d3b565b145b156104df576104df8282815181106104b1576104b1610d4f565b60200260200101515f01518383815181106104ce576104ce610d4f565b6020026020010151602001516104eb565b6001016103ac565b5050565b5f9182526004602052604090912080546001600160a01b0319166001600160a01b03909216919091179055565b60058181548110610527575f80fd5b5f91825260209091200154905081565b604080516060810182529384526001600160801b0392831660208086019182529284168583019081525f9687526008909352942092518355925192518116600160801b02921691909117600190910155565b6002805461059690610bf7565b80601f01602080910402602001604051908101604052809291908181526020018280546105c290610bf7565b801561060d5780601f106105e45761010080835404028352916020019161060d565b820191905f5260205f20905b8154815290600101906020018083116105f057829003601f168201915b505050505081565b5f5b81518110156104e7575f82828151811061063357610633610d4f565b602002602001015160800151600281111561065057610650610d3b565b036106925760085f83838151811061066a5761066a610d4f565b6020908102919091018101515182528101919091526040015f90812081815560010155610781565b60028282815181106106a6576106a6610d4f565b60200260200101516080015160028111156106c3576106c3610d3b565b14806106fd575060018282815181106106de576106de610d4f565b60200260200101516080015160028111156106fb576106fb610d3b565b145b156107815761078182828151811061071757610717610d4f565b60200260200101515f015183838151811061073457610734610d4f565b60200260200101516020015184848151811061075257610752610d4f565b60200260200101516040015185858151811061077057610770610d4f565b602002602001015160600151610537565b600101610617565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff811182821017156107c0576107c0610789565b60405290565b60405160a0810167ffffffffffffffff811182821017156107c0576107c0610789565b604051601f8201601f1916810167ffffffffffffffff8111828210171561081257610812610789565b604052919050565b80356001600160a01b0381168114610830575f80fd5b919050565b5f805f8060808587031215610848575f80fd5b84358015158114610857575f80fd5b93506020858101359350604086013567ffffffffffffffff8082111561087b575f80fd5b818801915088601f83011261088e575f80fd5b8135818111156108a0576108a0610789565b6108b2601f8201601f191685016107e9565b915080825289848285010111156108c7575f80fd5b80848401858401375f848284010152508094505050506108e96060860161081a565b905092959194509250565b5f8060408385031215610905575f80fd5b50508035926020909101359150565b5f67ffffffffffffffff82111561092d5761092d610789565b5060051b60200190565b803560038110610830575f80fd5b5f6020808385031215610956575f80fd5b823567ffffffffffffffff81111561096c575f80fd5b8301601f8101851361097c575f80fd5b803561098f61098a82610914565b6107e9565b818152606091820283018401918482019190888411156109ad575f80fd5b938501935b83851015610a035780858a0312156109c8575f80fd5b6109d061079d565b853581526109df87870161081a565b8782015260406109f0818801610937565b90820152835293840193918501916109b2565b50979650505050505050565b80356001600160801b0381168114610830575f80fd5b5f805f60608486031215610a37575f80fd5b83359250610a4760208501610a0f565b9150610a5560408501610a0f565b90509250925092565b5f8060408385031215610a6f575f80fd5b82359150610a7f6020840161081a565b90509250929050565b5f60208284031215610a98575f80fd5b5035919050565b5f805f8060808587031215610ab2575f80fd5b8435935060208501359250610ac960408601610a0f565b91506108e960608601610a0f565b5f602080835283518060208501525f5b81811015610b0357858101830151858201604001528201610ae7565b505f604082860101526040601f19601f8301168501019250505092915050565b5f6020808385031215610b34575f80fd5b823567ffffffffffffffff811115610b4a575f80fd5b8301601f81018513610b5a575f80fd5b8035610b6861098a82610914565b81815260a09182028301840191848201919088841115610b86575f80fd5b938501935b83851015610a035780858a031215610ba1575f80fd5b610ba96107c6565b8535815286860135878201526040610bc2818801610a0f565b908201526060610bd3878201610a0f565b908201526080610be4878201610937565b9082015283529384019391850191610b8b565b600181811c90821680610c0b57607f821691505b602082108103610c2957634e487b7160e01b5f52602260045260245ffd5b50919050565b601f821115610c7657805f5260205f20601f840160051c81016020851015610c545750805b601f840160051c820191505b81811015610c73575f8155600101610c60565b50505b505050565b815167ffffffffffffffff811115610c9557610c95610789565b610ca981610ca38454610bf7565b84610c2f565b602080601f831160018114610cdc575f8415610cc55750858301515b5f19600386901b1c1916600185901b178555610d33565b5f85815260208120601f198616915b82811015610d0a57888601518255948401946001909101908401610ceb565b5085821015610d2757878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52603260045260245ffdfea26469706673582212206590d836561d8340d2d83d5cd063d2c751fdea336b0e3302a990dd0600d171d464736f6c63430008180033 /// ``` #[rustfmt::skip] #[allow(clippy::all)] pub static BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( - b"`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[Pa\t\xC6\x80a\0\x1D_9_\xF3\xFE`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[P`\x046\x10a\0\xE5W_5`\xE0\x1C\x80c\x88\xDF\xDD\xC6\x11a\0\x88W\x80c\xC8\xAF:\xA6\x11a\0cW\x80c\xC8\xAF:\xA6\x14a\x02hW\x80c\xD1^\xC8Q\x14a\x02{W\x80c\xEA\xD1\x84\0\x14a\x02\xBDW\x80c\xF2]T\xF5\x14a\x02\xDFW_\x80\xFD[\x80c\x88\xDF\xDD\xC6\x14a\x02\x10W\x80c\xA3\x14\x15\x0F\x14a\x02JW\x80c\xA5\xD6f\xA9\x14a\x02SW_\x80\xFD[\x80c\x1C\x13C\x15\x11a\0\xC3W\x80c\x1C\x13C\x15\x14a\x01\x80W\x80c*\xE4&\x86\x14a\x01\x93W\x80ci\x87\xB1\xFB\x14a\x01\xD3W\x80cl\xC0\x14\xDE\x14a\x01\xF4W_\x80\xFD[\x80c\x02\0\"\\\x14a\0\xE9W\x80c\nM\x04\xF7\x14a\0\xFEW\x80c\x0C\x16\x16\xC9\x14a\x01mW[_\x80\xFD[a\0\xFCa\0\xF76`\x04a\x05\xD8V[a\x02\xF2V[\0[a\x01Ca\x01\x0C6`\x04a\x06\x97V[`\t` \x90\x81R_\x92\x83R`@\x80\x84 \x90\x91R\x90\x82R\x90 \x80T`\x01\x90\x91\x01T`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[`@\x80Q\x93\x84R`\x01`\x01`\x80\x1B\x03\x92\x83\x16` \x85\x01R\x91\x16\x90\x82\x01R``\x01[`@Q\x80\x91\x03\x90\xF3[a\0\xFCa\x01{6`\x04a\x06\xB7V[a\x036V[a\0\xFCa\x01\x8E6`\x04a\x07\x97V[a\x04wV[a\x01\xBBa\x01\xA16`\x04a\x07\xC1V[`\x04` R_\x90\x81R`@\x90 T`\x01`\x01`\xA0\x1B\x03\x16\x81V[`@Q`\x01`\x01`\xA0\x1B\x03\x90\x91\x16\x81R` \x01a\x01dV[a\x01\xE6a\x01\xE16`\x04a\x07\xC1V[a\x04\xA4V[`@Q\x90\x81R` \x01a\x01dV[_Ta\x02\0\x90`\xFF\x16\x81V[`@Q\x90\x15\x15\x81R` \x01a\x01dV[a\x01Ca\x02\x1E6`\x04a\x07\xC1V[`\x08` R_\x90\x81R`@\x90 \x80T`\x01\x90\x91\x01T`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[a\x01\xE6`\x01T\x81V[a\x02[a\x04\xC3V[`@Qa\x01d\x91\x90a\x07\xD8V[`\x03Ta\x01\xBB\x90`\x01`\x01`\xA0\x1B\x03\x16\x81V[a\0\xFCa\x02\x896`\x04a\x07\xC1V[`\x05\x80T`\x01\x81\x01\x82U_\x91\x90\x91R\x7F\x03kc\x84\xB5\xEC\xA7\x91\xC6'a\x15-\x0Cy\xBB\x06\x04\xC1\x04\xA5\xFBoN\xB0p?1T\xBB=\xB0\x01UV[`\x06T`\x07Ta\x01C\x91\x90`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[a\0\xFCa\x02\xED6`\x04a\x07\xC1V[`\x01UV[_\x80T`\xFF\x19\x16\x85\x15\x15\x17\x90U`\x01\x83\x90U`\x02a\x03\x10\x83\x82a\x08\xA8V[P`\x03\x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x92\x90\x92\x16\x91\x90\x91\x17\x90UPPPV[_[\x81Q\x81\x10\x15a\x04sW_\x82\x82\x81Q\x81\x10a\x03TWa\x03Ta\t|V[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x03qWa\x03qa\thV[\x03a\x03\xB8W`\x04_\x83\x83\x81Q\x81\x10a\x03\x8BWa\x03\x8Ba\t|V[` \x90\x81\x02\x91\x90\x91\x01\x81\x01QQ\x82R\x81\x01\x91\x90\x91R`@\x01_ \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16\x90Ua\x04kV[`\x02\x82\x82\x81Q\x81\x10a\x03\xCCWa\x03\xCCa\t|V[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x03\xE9Wa\x03\xE9a\thV[\x14\x80a\x04#WP`\x01\x82\x82\x81Q\x81\x10a\x04\x04Wa\x04\x04a\t|V[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x04!Wa\x04!a\thV[\x14[\x15a\x04kWa\x04k\x82\x82\x81Q\x81\x10a\x04=Wa\x04=a\t|V[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x04ZWa\x04Za\t|V[` \x02` \x01\x01Q` \x01Qa\x04wV[`\x01\x01a\x038V[PPV[_\x91\x82R`\x04` R`@\x90\x91 \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x90\x92\x16\x91\x90\x91\x17\x90UV[`\x05\x81\x81T\x81\x10a\x04\xB3W_\x80\xFD[_\x91\x82R` \x90\x91 \x01T\x90P\x81V[`\x02\x80Ta\x04\xD0\x90a\x08$V[\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80Ta\x04\xFC\x90a\x08$V[\x80\x15a\x05GW\x80`\x1F\x10a\x05\x1EWa\x01\0\x80\x83T\x04\x02\x83R\x91` \x01\x91a\x05GV[\x82\x01\x91\x90_R` _ \x90[\x81T\x81R\x90`\x01\x01\x90` \x01\x80\x83\x11a\x05*W\x82\x90\x03`\x1F\x16\x82\x01\x91[PPPPP\x81V[cNH{q`\xE0\x1B_R`A`\x04R`$_\xFD[`@Q``\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x05\x86Wa\x05\x86a\x05OV[`@R\x90V[`@Q`\x1F\x82\x01`\x1F\x19\x16\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x05\xB5Wa\x05\xB5a\x05OV[`@R\x91\x90PV[\x805`\x01`\x01`\xA0\x1B\x03\x81\x16\x81\x14a\x05\xD3W_\x80\xFD[\x91\x90PV[_\x80_\x80`\x80\x85\x87\x03\x12\x15a\x05\xEBW_\x80\xFD[\x845\x80\x15\x15\x81\x14a\x05\xFAW_\x80\xFD[\x93P` \x85\x81\x015\x93P`@\x86\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x82\x11\x15a\x06\x1EW_\x80\xFD[\x81\x88\x01\x91P\x88`\x1F\x83\x01\x12a\x061W_\x80\xFD[\x815\x81\x81\x11\x15a\x06CWa\x06Ca\x05OV[a\x06U`\x1F\x82\x01`\x1F\x19\x16\x85\x01a\x05\x8CV[\x91P\x80\x82R\x89\x84\x82\x85\x01\x01\x11\x15a\x06jW_\x80\xFD[\x80\x84\x84\x01\x85\x84\x017_\x84\x82\x84\x01\x01RP\x80\x94PPPPa\x06\x8C``\x86\x01a\x05\xBDV[\x90P\x92\x95\x91\x94P\x92PV[_\x80`@\x83\x85\x03\x12\x15a\x06\xA8W_\x80\xFD[PP\x805\x92` \x90\x91\x015\x91PV[_` \x80\x83\x85\x03\x12\x15a\x06\xC8W_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x82\x11\x15a\x06\xDFW_\x80\xFD[\x81\x85\x01\x91P\x85`\x1F\x83\x01\x12a\x06\xF2W_\x80\xFD[\x815\x81\x81\x11\x15a\x07\x04Wa\x07\x04a\x05OV[a\x07\x12\x84\x82`\x05\x1B\x01a\x05\x8CV[\x81\x81R\x84\x81\x01\x92P``\x91\x82\x02\x84\x01\x85\x01\x91\x88\x83\x11\x15a\x070W_\x80\xFD[\x93\x85\x01\x93[\x82\x85\x10\x15a\x07\x8BW\x80\x85\x8A\x03\x12\x15a\x07KW_\x80\xFD[a\x07Sa\x05cV[\x855\x81Ra\x07b\x87\x87\x01a\x05\xBDV[\x87\x82\x01R`@\x80\x87\x015`\x03\x81\x10a\x07xW_\x80\xFD[\x90\x82\x01R\x84R\x93\x84\x01\x93\x92\x85\x01\x92a\x075V[P\x97\x96PPPPPPPV[_\x80`@\x83\x85\x03\x12\x15a\x07\xA8W_\x80\xFD[\x825\x91Pa\x07\xB8` \x84\x01a\x05\xBDV[\x90P\x92P\x92\x90PV[_` \x82\x84\x03\x12\x15a\x07\xD1W_\x80\xFD[P5\x91\x90PV[_` \x80\x83R\x83Q\x80` \x85\x01R_[\x81\x81\x10\x15a\x08\x04W\x85\x81\x01\x83\x01Q\x85\x82\x01`@\x01R\x82\x01a\x07\xE8V[P_`@\x82\x86\x01\x01R`@`\x1F\x19`\x1F\x83\x01\x16\x85\x01\x01\x92PPP\x92\x91PPV[`\x01\x81\x81\x1C\x90\x82\x16\x80a\x088W`\x7F\x82\x16\x91P[` \x82\x10\x81\x03a\x08VWcNH{q`\xE0\x1B_R`\"`\x04R`$_\xFD[P\x91\x90PV[`\x1F\x82\x11\x15a\x08\xA3W\x80_R` _ `\x1F\x84\x01`\x05\x1C\x81\x01` \x85\x10\x15a\x08\x81WP\x80[`\x1F\x84\x01`\x05\x1C\x82\x01\x91P[\x81\x81\x10\x15a\x08\xA0W_\x81U`\x01\x01a\x08\x8DV[PP[PPPV[\x81Qg\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x08\xC2Wa\x08\xC2a\x05OV[a\x08\xD6\x81a\x08\xD0\x84Ta\x08$V[\x84a\x08\\V[` \x80`\x1F\x83\x11`\x01\x81\x14a\t\tW_\x84\x15a\x08\xF2WP\x85\x83\x01Q[_\x19`\x03\x86\x90\x1B\x1C\x19\x16`\x01\x85\x90\x1B\x17\x85Ua\t`V[_\x85\x81R` \x81 `\x1F\x19\x86\x16\x91[\x82\x81\x10\x15a\t7W\x88\x86\x01Q\x82U\x94\x84\x01\x94`\x01\x90\x91\x01\x90\x84\x01a\t\x18V[P\x85\x82\x10\x15a\tTW\x87\x85\x01Q_\x19`\x03\x88\x90\x1B`\xF8\x16\x1C\x19\x16\x81U[PP`\x01\x84`\x01\x1B\x01\x85U[PPPPPPV[cNH{q`\xE0\x1B_R`!`\x04R`$_\xFD[cNH{q`\xE0\x1B_R`2`\x04R`$_\xFD\xFE\xA2dipfsX\"\x12 \xCAf\xFA\xDEy\x18&;\x04\xA1\xBB\xF7\xB0Px\x9F\x81\x1F\\\x17\xAD\x95\x7F\xAC\xFC\x96Q\xC3\xDE\xCB\x1C\xD5dsolcC\0\x08\x18\x003", + b"`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[Pa\r\x99\x80a\0\x1D_9_\xF3\xFE`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[P`\x046\x10a\x01\x06W_5`\xE0\x1C\x80c\x80&\xDE1\x11a\0\x9EW\x80c\xC7\xBFM\xB5\x11a\0nW\x80c\xC7\xBFM\xB5\x14a\x02\xC9W\x80c\xC8\xAF:\xA6\x14a\x02\xDCW\x80c\xD1^\xC8Q\x14a\x02\xEFW\x80c\xEA\xD1\x84\0\x14a\x031W\x80c\xF2]T\xF5\x14a\x03SW_\x80\xFD[\x80c\x80&\xDE1\x14a\x02^W\x80c\x88\xDF\xDD\xC6\x14a\x02qW\x80c\xA3\x14\x15\x0F\x14a\x02\xABW\x80c\xA5\xD6f\xA9\x14a\x02\xB4W_\x80\xFD[\x80c\x1C\x13C\x15\x11a\0\xD9W\x80c\x1C\x13C\x15\x14a\x01\xCEW\x80c*\xE4&\x86\x14a\x01\xE1W\x80ci\x87\xB1\xFB\x14a\x02!W\x80cl\xC0\x14\xDE\x14a\x02BW_\x80\xFD[\x80c\x02\0\"\\\x14a\x01\nW\x80c\nM\x04\xF7\x14a\x01\x1FW\x80c\x0C\x16\x16\xC9\x14a\x01\x8EW\x80c\x14\x17\xA4\xF0\x14a\x01\xA1W[_\x80\xFD[a\x01\x1Da\x01\x186`\x04a\x085V[a\x03fV[\0[a\x01da\x01-6`\x04a\x08\xF4V[`\t` \x90\x81R_\x92\x83R`@\x80\x84 \x90\x91R\x90\x82R\x90 \x80T`\x01\x90\x91\x01T`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[`@\x80Q\x93\x84R`\x01`\x01`\x80\x1B\x03\x92\x83\x16` \x85\x01R\x91\x16\x90\x82\x01R``\x01[`@Q\x80\x91\x03\x90\xF3[a\x01\x1Da\x01\x9C6`\x04a\tEV[a\x03\xAAV[a\x01\x1Da\x01\xAF6`\x04a\n%V[`\x06\x92\x90\x92U`\x01`\x01`\x80\x1B\x03\x91\x82\x16`\x01`\x80\x1B\x02\x91\x16\x17`\x07UV[a\x01\x1Da\x01\xDC6`\x04a\n^V[a\x04\xEBV[a\x02\ta\x01\xEF6`\x04a\n\x88V[`\x04` R_\x90\x81R`@\x90 T`\x01`\x01`\xA0\x1B\x03\x16\x81V[`@Q`\x01`\x01`\xA0\x1B\x03\x90\x91\x16\x81R` \x01a\x01\x85V[a\x024a\x02/6`\x04a\n\x88V[a\x05\x18V[`@Q\x90\x81R` \x01a\x01\x85V[_Ta\x02N\x90`\xFF\x16\x81V[`@Q\x90\x15\x15\x81R` \x01a\x01\x85V[a\x01\x1Da\x02l6`\x04a\n\x9FV[a\x057V[a\x01da\x02\x7F6`\x04a\n\x88V[`\x08` R_\x90\x81R`@\x90 \x80T`\x01\x90\x91\x01T`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[a\x024`\x01T\x81V[a\x02\xBCa\x05\x89V[`@Qa\x01\x85\x91\x90a\n\xD7V[a\x01\x1Da\x02\xD76`\x04a\x0B#V[a\x06\x15V[`\x03Ta\x02\t\x90`\x01`\x01`\xA0\x1B\x03\x16\x81V[a\x01\x1Da\x02\xFD6`\x04a\n\x88V[`\x05\x80T`\x01\x81\x01\x82U_\x91\x90\x91R\x7F\x03kc\x84\xB5\xEC\xA7\x91\xC6'a\x15-\x0Cy\xBB\x06\x04\xC1\x04\xA5\xFBoN\xB0p?1T\xBB=\xB0\x01UV[`\x06T`\x07Ta\x01d\x91\x90`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[a\x01\x1Da\x03a6`\x04a\n\x88V[`\x01UV[_\x80T`\xFF\x19\x16\x85\x15\x15\x17\x90U`\x01\x83\x90U`\x02a\x03\x84\x83\x82a\x0C{V[P`\x03\x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x92\x90\x92\x16\x91\x90\x91\x17\x90UPPPV[_[\x81Q\x81\x10\x15a\x04\xE7W_\x82\x82\x81Q\x81\x10a\x03\xC8Wa\x03\xC8a\rOV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x03\xE5Wa\x03\xE5a\r;V[\x03a\x04,W`\x04_\x83\x83\x81Q\x81\x10a\x03\xFFWa\x03\xFFa\rOV[` \x90\x81\x02\x91\x90\x91\x01\x81\x01QQ\x82R\x81\x01\x91\x90\x91R`@\x01_ \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16\x90Ua\x04\xDFV[`\x02\x82\x82\x81Q\x81\x10a\x04@Wa\x04@a\rOV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x04]Wa\x04]a\r;V[\x14\x80a\x04\x97WP`\x01\x82\x82\x81Q\x81\x10a\x04xWa\x04xa\rOV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x04\x95Wa\x04\x95a\r;V[\x14[\x15a\x04\xDFWa\x04\xDF\x82\x82\x81Q\x81\x10a\x04\xB1Wa\x04\xB1a\rOV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x04\xCEWa\x04\xCEa\rOV[` \x02` \x01\x01Q` \x01Qa\x04\xEBV[`\x01\x01a\x03\xACV[PPV[_\x91\x82R`\x04` R`@\x90\x91 \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x90\x92\x16\x91\x90\x91\x17\x90UV[`\x05\x81\x81T\x81\x10a\x05'W_\x80\xFD[_\x91\x82R` \x90\x91 \x01T\x90P\x81V[`@\x80Q``\x81\x01\x82R\x93\x84R`\x01`\x01`\x80\x1B\x03\x92\x83\x16` \x80\x86\x01\x91\x82R\x92\x84\x16\x85\x83\x01\x90\x81R_\x96\x87R`\x08\x90\x93R\x94 \x92Q\x83U\x92Q\x92Q\x81\x16`\x01`\x80\x1B\x02\x92\x16\x91\x90\x91\x17`\x01\x90\x91\x01UV[`\x02\x80Ta\x05\x96\x90a\x0B\xF7V[\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80Ta\x05\xC2\x90a\x0B\xF7V[\x80\x15a\x06\rW\x80`\x1F\x10a\x05\xE4Wa\x01\0\x80\x83T\x04\x02\x83R\x91` \x01\x91a\x06\rV[\x82\x01\x91\x90_R` _ \x90[\x81T\x81R\x90`\x01\x01\x90` \x01\x80\x83\x11a\x05\xF0W\x82\x90\x03`\x1F\x16\x82\x01\x91[PPPPP\x81V[_[\x81Q\x81\x10\x15a\x04\xE7W_\x82\x82\x81Q\x81\x10a\x063Wa\x063a\rOV[` \x02` \x01\x01Q`\x80\x01Q`\x02\x81\x11\x15a\x06PWa\x06Pa\r;V[\x03a\x06\x92W`\x08_\x83\x83\x81Q\x81\x10a\x06jWa\x06ja\rOV[` \x90\x81\x02\x91\x90\x91\x01\x81\x01QQ\x82R\x81\x01\x91\x90\x91R`@\x01_\x90\x81 \x81\x81U`\x01\x01Ua\x07\x81V[`\x02\x82\x82\x81Q\x81\x10a\x06\xA6Wa\x06\xA6a\rOV[` \x02` \x01\x01Q`\x80\x01Q`\x02\x81\x11\x15a\x06\xC3Wa\x06\xC3a\r;V[\x14\x80a\x06\xFDWP`\x01\x82\x82\x81Q\x81\x10a\x06\xDEWa\x06\xDEa\rOV[` \x02` \x01\x01Q`\x80\x01Q`\x02\x81\x11\x15a\x06\xFBWa\x06\xFBa\r;V[\x14[\x15a\x07\x81Wa\x07\x81\x82\x82\x81Q\x81\x10a\x07\x17Wa\x07\x17a\rOV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x074Wa\x074a\rOV[` \x02` \x01\x01Q` \x01Q\x84\x84\x81Q\x81\x10a\x07RWa\x07Ra\rOV[` \x02` \x01\x01Q`@\x01Q\x85\x85\x81Q\x81\x10a\x07pWa\x07pa\rOV[` \x02` \x01\x01Q``\x01Qa\x057V[`\x01\x01a\x06\x17V[cNH{q`\xE0\x1B_R`A`\x04R`$_\xFD[`@Q``\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x07\xC0Wa\x07\xC0a\x07\x89V[`@R\x90V[`@Q`\xA0\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x07\xC0Wa\x07\xC0a\x07\x89V[`@Q`\x1F\x82\x01`\x1F\x19\x16\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x08\x12Wa\x08\x12a\x07\x89V[`@R\x91\x90PV[\x805`\x01`\x01`\xA0\x1B\x03\x81\x16\x81\x14a\x080W_\x80\xFD[\x91\x90PV[_\x80_\x80`\x80\x85\x87\x03\x12\x15a\x08HW_\x80\xFD[\x845\x80\x15\x15\x81\x14a\x08WW_\x80\xFD[\x93P` \x85\x81\x015\x93P`@\x86\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x82\x11\x15a\x08{W_\x80\xFD[\x81\x88\x01\x91P\x88`\x1F\x83\x01\x12a\x08\x8EW_\x80\xFD[\x815\x81\x81\x11\x15a\x08\xA0Wa\x08\xA0a\x07\x89V[a\x08\xB2`\x1F\x82\x01`\x1F\x19\x16\x85\x01a\x07\xE9V[\x91P\x80\x82R\x89\x84\x82\x85\x01\x01\x11\x15a\x08\xC7W_\x80\xFD[\x80\x84\x84\x01\x85\x84\x017_\x84\x82\x84\x01\x01RP\x80\x94PPPPa\x08\xE9``\x86\x01a\x08\x1AV[\x90P\x92\x95\x91\x94P\x92PV[_\x80`@\x83\x85\x03\x12\x15a\t\x05W_\x80\xFD[PP\x805\x92` \x90\x91\x015\x91PV[_g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x82\x11\x15a\t-Wa\t-a\x07\x89V[P`\x05\x1B` \x01\x90V[\x805`\x03\x81\x10a\x080W_\x80\xFD[_` \x80\x83\x85\x03\x12\x15a\tVW_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\tlW_\x80\xFD[\x83\x01`\x1F\x81\x01\x85\x13a\t|W_\x80\xFD[\x805a\t\x8Fa\t\x8A\x82a\t\x14V[a\x07\xE9V[\x81\x81R``\x91\x82\x02\x83\x01\x84\x01\x91\x84\x82\x01\x91\x90\x88\x84\x11\x15a\t\xADW_\x80\xFD[\x93\x85\x01\x93[\x83\x85\x10\x15a\n\x03W\x80\x85\x8A\x03\x12\x15a\t\xC8W_\x80\xFD[a\t\xD0a\x07\x9DV[\x855\x81Ra\t\xDF\x87\x87\x01a\x08\x1AV[\x87\x82\x01R`@a\t\xF0\x81\x88\x01a\t7V[\x90\x82\x01R\x83R\x93\x84\x01\x93\x91\x85\x01\x91a\t\xB2V[P\x97\x96PPPPPPPV[\x805`\x01`\x01`\x80\x1B\x03\x81\x16\x81\x14a\x080W_\x80\xFD[_\x80_``\x84\x86\x03\x12\x15a\n7W_\x80\xFD[\x835\x92Pa\nG` \x85\x01a\n\x0FV[\x91Pa\nU`@\x85\x01a\n\x0FV[\x90P\x92P\x92P\x92V[_\x80`@\x83\x85\x03\x12\x15a\noW_\x80\xFD[\x825\x91Pa\n\x7F` \x84\x01a\x08\x1AV[\x90P\x92P\x92\x90PV[_` \x82\x84\x03\x12\x15a\n\x98W_\x80\xFD[P5\x91\x90PV[_\x80_\x80`\x80\x85\x87\x03\x12\x15a\n\xB2W_\x80\xFD[\x845\x93P` \x85\x015\x92Pa\n\xC9`@\x86\x01a\n\x0FV[\x91Pa\x08\xE9``\x86\x01a\n\x0FV[_` \x80\x83R\x83Q\x80` \x85\x01R_[\x81\x81\x10\x15a\x0B\x03W\x85\x81\x01\x83\x01Q\x85\x82\x01`@\x01R\x82\x01a\n\xE7V[P_`@\x82\x86\x01\x01R`@`\x1F\x19`\x1F\x83\x01\x16\x85\x01\x01\x92PPP\x92\x91PPV[_` \x80\x83\x85\x03\x12\x15a\x0B4W_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0BJW_\x80\xFD[\x83\x01`\x1F\x81\x01\x85\x13a\x0BZW_\x80\xFD[\x805a\x0Bha\t\x8A\x82a\t\x14V[\x81\x81R`\xA0\x91\x82\x02\x83\x01\x84\x01\x91\x84\x82\x01\x91\x90\x88\x84\x11\x15a\x0B\x86W_\x80\xFD[\x93\x85\x01\x93[\x83\x85\x10\x15a\n\x03W\x80\x85\x8A\x03\x12\x15a\x0B\xA1W_\x80\xFD[a\x0B\xA9a\x07\xC6V[\x855\x81R\x86\x86\x015\x87\x82\x01R`@a\x0B\xC2\x81\x88\x01a\n\x0FV[\x90\x82\x01R``a\x0B\xD3\x87\x82\x01a\n\x0FV[\x90\x82\x01R`\x80a\x0B\xE4\x87\x82\x01a\t7V[\x90\x82\x01R\x83R\x93\x84\x01\x93\x91\x85\x01\x91a\x0B\x8BV[`\x01\x81\x81\x1C\x90\x82\x16\x80a\x0C\x0BW`\x7F\x82\x16\x91P[` \x82\x10\x81\x03a\x0C)WcNH{q`\xE0\x1B_R`\"`\x04R`$_\xFD[P\x91\x90PV[`\x1F\x82\x11\x15a\x0CvW\x80_R` _ `\x1F\x84\x01`\x05\x1C\x81\x01` \x85\x10\x15a\x0CTWP\x80[`\x1F\x84\x01`\x05\x1C\x82\x01\x91P[\x81\x81\x10\x15a\x0CsW_\x81U`\x01\x01a\x0C`V[PP[PPPV[\x81Qg\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0C\x95Wa\x0C\x95a\x07\x89V[a\x0C\xA9\x81a\x0C\xA3\x84Ta\x0B\xF7V[\x84a\x0C/V[` \x80`\x1F\x83\x11`\x01\x81\x14a\x0C\xDCW_\x84\x15a\x0C\xC5WP\x85\x83\x01Q[_\x19`\x03\x86\x90\x1B\x1C\x19\x16`\x01\x85\x90\x1B\x17\x85Ua\r3V[_\x85\x81R` \x81 `\x1F\x19\x86\x16\x91[\x82\x81\x10\x15a\r\nW\x88\x86\x01Q\x82U\x94\x84\x01\x94`\x01\x90\x91\x01\x90\x84\x01a\x0C\xEBV[P\x85\x82\x10\x15a\r'W\x87\x85\x01Q_\x19`\x03\x88\x90\x1B`\xF8\x16\x1C\x19\x16\x81U[PP`\x01\x84`\x01\x1B\x01\x85U[PPPPPPV[cNH{q`\xE0\x1B_R`!`\x04R`$_\xFD[cNH{q`\xE0\x1B_R`2`\x04R`$_\xFD\xFE\xA2dipfsX\"\x12 e\x90\xD86V\x1D\x83@\xD2\xD8=\\\xD0c\xD2\xC7Q\xFD\xEA3k\x0E3\x02\xA9\x90\xDD\x06\0\xD1q\xD4dsolcC\0\x08\x18\x003", ); /// The runtime bytecode of the contract, as deployed on the network. /// /// ```text - ///0x608060405234801561000f575f80fd5b50600436106100e5575f3560e01c806388dfddc611610088578063c8af3aa611610063578063c8af3aa614610268578063d15ec8511461027b578063ead18400146102bd578063f25d54f5146102df575f80fd5b806388dfddc614610210578063a314150f1461024a578063a5d666a914610253575f80fd5b80631c134315116100c35780631c134315146101805780632ae42686146101935780636987b1fb146101d35780636cc014de146101f4575f80fd5b80630200225c146100e95780630a4d04f7146100fe5780630c1616c91461016d575b5f80fd5b6100fc6100f73660046105d8565b6102f2565b005b61014361010c366004610697565b600960209081525f9283526040808420909152908252902080546001909101546001600160801b0380821691600160801b90041683565b604080519384526001600160801b0392831660208501529116908201526060015b60405180910390f35b6100fc61017b3660046106b7565b610336565b6100fc61018e366004610797565b610477565b6101bb6101a13660046107c1565b60046020525f90815260409020546001600160a01b031681565b6040516001600160a01b039091168152602001610164565b6101e66101e13660046107c1565b6104a4565b604051908152602001610164565b5f546102009060ff1681565b6040519015158152602001610164565b61014361021e3660046107c1565b60086020525f9081526040902080546001909101546001600160801b0380821691600160801b90041683565b6101e660015481565b61025b6104c3565b60405161016491906107d8565b6003546101bb906001600160a01b031681565b6100fc6102893660046107c1565b600580546001810182555f919091527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00155565b60065460075461014391906001600160801b0380821691600160801b90041683565b6100fc6102ed3660046107c1565b600155565b5f805460ff19168515151790556001839055600261031083826108a8565b50600380546001600160a01b0319166001600160a01b0392909216919091179055505050565b5f5b8151811015610473575f8282815181106103545761035461097c565b602002602001015160400151600281111561037157610371610968565b036103b85760045f83838151811061038b5761038b61097c565b6020908102919091018101515182528101919091526040015f2080546001600160a01b031916905561046b565b60028282815181106103cc576103cc61097c565b60200260200101516040015160028111156103e9576103e9610968565b1480610423575060018282815181106104045761040461097c565b602002602001015160400151600281111561042157610421610968565b145b1561046b5761046b82828151811061043d5761043d61097c565b60200260200101515f015183838151811061045a5761045a61097c565b602002602001015160200151610477565b600101610338565b5050565b5f9182526004602052604090912080546001600160a01b0319166001600160a01b03909216919091179055565b600581815481106104b3575f80fd5b5f91825260209091200154905081565b600280546104d090610824565b80601f01602080910402602001604051908101604052809291908181526020018280546104fc90610824565b80156105475780601f1061051e57610100808354040283529160200191610547565b820191905f5260205f20905b81548152906001019060200180831161052a57829003601f168201915b505050505081565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff811182821017156105865761058661054f565b60405290565b604051601f8201601f1916810167ffffffffffffffff811182821017156105b5576105b561054f565b604052919050565b80356001600160a01b03811681146105d3575f80fd5b919050565b5f805f80608085870312156105eb575f80fd5b843580151581146105fa575f80fd5b93506020858101359350604086013567ffffffffffffffff8082111561061e575f80fd5b818801915088601f830112610631575f80fd5b8135818111156106435761064361054f565b610655601f8201601f1916850161058c565b9150808252898482850101111561066a575f80fd5b80848401858401375f8482840101525080945050505061068c606086016105bd565b905092959194509250565b5f80604083850312156106a8575f80fd5b50508035926020909101359150565b5f60208083850312156106c8575f80fd5b823567ffffffffffffffff808211156106df575f80fd5b818501915085601f8301126106f2575f80fd5b8135818111156107045761070461054f565b610712848260051b0161058c565b81815284810192506060918202840185019188831115610730575f80fd5b938501935b8285101561078b5780858a03121561074b575f80fd5b610753610563565b853581526107628787016105bd565b8782015260408087013560038110610778575f80fd5b9082015284529384019392850192610735565b50979650505050505050565b5f80604083850312156107a8575f80fd5b823591506107b8602084016105bd565b90509250929050565b5f602082840312156107d1575f80fd5b5035919050565b5f602080835283518060208501525f5b81811015610804578581018301518582016040015282016107e8565b505f604082860101526040601f19601f8301168501019250505092915050565b600181811c9082168061083857607f821691505b60208210810361085657634e487b7160e01b5f52602260045260245ffd5b50919050565b601f8211156108a357805f5260205f20601f840160051c810160208510156108815750805b601f840160051c820191505b818110156108a0575f815560010161088d565b50505b505050565b815167ffffffffffffffff8111156108c2576108c261054f565b6108d6816108d08454610824565b8461085c565b602080601f831160018114610909575f84156108f25750858301515b5f19600386901b1c1916600185901b178555610960565b5f85815260208120601f198616915b8281101561093757888601518255948401946001909101908401610918565b508582101561095457878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52603260045260245ffdfea2646970667358221220ca66fade7918263b04a1bbf7b050789f811f5c17ad957facfc9651c3decb1cd564736f6c63430008180033 + ///0x608060405234801561000f575f80fd5b5060043610610106575f3560e01c80638026de311161009e578063c7bf4db51161006e578063c7bf4db5146102c9578063c8af3aa6146102dc578063d15ec851146102ef578063ead1840014610331578063f25d54f514610353575f80fd5b80638026de311461025e57806388dfddc614610271578063a314150f146102ab578063a5d666a9146102b4575f80fd5b80631c134315116100d95780631c134315146101ce5780632ae42686146101e15780636987b1fb146102215780636cc014de14610242575f80fd5b80630200225c1461010a5780630a4d04f71461011f5780630c1616c91461018e5780631417a4f0146101a1575b5f80fd5b61011d610118366004610835565b610366565b005b61016461012d3660046108f4565b600960209081525f9283526040808420909152908252902080546001909101546001600160801b0380821691600160801b90041683565b604080519384526001600160801b0392831660208501529116908201526060015b60405180910390f35b61011d61019c366004610945565b6103aa565b61011d6101af366004610a25565b6006929092556001600160801b03918216600160801b02911617600755565b61011d6101dc366004610a5e565b6104eb565b6102096101ef366004610a88565b60046020525f90815260409020546001600160a01b031681565b6040516001600160a01b039091168152602001610185565b61023461022f366004610a88565b610518565b604051908152602001610185565b5f5461024e9060ff1681565b6040519015158152602001610185565b61011d61026c366004610a9f565b610537565b61016461027f366004610a88565b60086020525f9081526040902080546001909101546001600160801b0380821691600160801b90041683565b61023460015481565b6102bc610589565b6040516101859190610ad7565b61011d6102d7366004610b23565b610615565b600354610209906001600160a01b031681565b61011d6102fd366004610a88565b600580546001810182555f919091527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00155565b60065460075461016491906001600160801b0380821691600160801b90041683565b61011d610361366004610a88565b600155565b5f805460ff1916851515179055600183905560026103848382610c7b565b50600380546001600160a01b0319166001600160a01b0392909216919091179055505050565b5f5b81518110156104e7575f8282815181106103c8576103c8610d4f565b60200260200101516040015160028111156103e5576103e5610d3b565b0361042c5760045f8383815181106103ff576103ff610d4f565b6020908102919091018101515182528101919091526040015f2080546001600160a01b03191690556104df565b600282828151811061044057610440610d4f565b602002602001015160400151600281111561045d5761045d610d3b565b14806104975750600182828151811061047857610478610d4f565b602002602001015160400151600281111561049557610495610d3b565b145b156104df576104df8282815181106104b1576104b1610d4f565b60200260200101515f01518383815181106104ce576104ce610d4f565b6020026020010151602001516104eb565b6001016103ac565b5050565b5f9182526004602052604090912080546001600160a01b0319166001600160a01b03909216919091179055565b60058181548110610527575f80fd5b5f91825260209091200154905081565b604080516060810182529384526001600160801b0392831660208086019182529284168583019081525f9687526008909352942092518355925192518116600160801b02921691909117600190910155565b6002805461059690610bf7565b80601f01602080910402602001604051908101604052809291908181526020018280546105c290610bf7565b801561060d5780601f106105e45761010080835404028352916020019161060d565b820191905f5260205f20905b8154815290600101906020018083116105f057829003601f168201915b505050505081565b5f5b81518110156104e7575f82828151811061063357610633610d4f565b602002602001015160800151600281111561065057610650610d3b565b036106925760085f83838151811061066a5761066a610d4f565b6020908102919091018101515182528101919091526040015f90812081815560010155610781565b60028282815181106106a6576106a6610d4f565b60200260200101516080015160028111156106c3576106c3610d3b565b14806106fd575060018282815181106106de576106de610d4f565b60200260200101516080015160028111156106fb576106fb610d3b565b145b156107815761078182828151811061071757610717610d4f565b60200260200101515f015183838151811061073457610734610d4f565b60200260200101516020015184848151811061075257610752610d4f565b60200260200101516040015185858151811061077057610770610d4f565b602002602001015160600151610537565b600101610617565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff811182821017156107c0576107c0610789565b60405290565b60405160a0810167ffffffffffffffff811182821017156107c0576107c0610789565b604051601f8201601f1916810167ffffffffffffffff8111828210171561081257610812610789565b604052919050565b80356001600160a01b0381168114610830575f80fd5b919050565b5f805f8060808587031215610848575f80fd5b84358015158114610857575f80fd5b93506020858101359350604086013567ffffffffffffffff8082111561087b575f80fd5b818801915088601f83011261088e575f80fd5b8135818111156108a0576108a0610789565b6108b2601f8201601f191685016107e9565b915080825289848285010111156108c7575f80fd5b80848401858401375f848284010152508094505050506108e96060860161081a565b905092959194509250565b5f8060408385031215610905575f80fd5b50508035926020909101359150565b5f67ffffffffffffffff82111561092d5761092d610789565b5060051b60200190565b803560038110610830575f80fd5b5f6020808385031215610956575f80fd5b823567ffffffffffffffff81111561096c575f80fd5b8301601f8101851361097c575f80fd5b803561098f61098a82610914565b6107e9565b818152606091820283018401918482019190888411156109ad575f80fd5b938501935b83851015610a035780858a0312156109c8575f80fd5b6109d061079d565b853581526109df87870161081a565b8782015260406109f0818801610937565b90820152835293840193918501916109b2565b50979650505050505050565b80356001600160801b0381168114610830575f80fd5b5f805f60608486031215610a37575f80fd5b83359250610a4760208501610a0f565b9150610a5560408501610a0f565b90509250925092565b5f8060408385031215610a6f575f80fd5b82359150610a7f6020840161081a565b90509250929050565b5f60208284031215610a98575f80fd5b5035919050565b5f805f8060808587031215610ab2575f80fd5b8435935060208501359250610ac960408601610a0f565b91506108e960608601610a0f565b5f602080835283518060208501525f5b81811015610b0357858101830151858201604001528201610ae7565b505f604082860101526040601f19601f8301168501019250505092915050565b5f6020808385031215610b34575f80fd5b823567ffffffffffffffff811115610b4a575f80fd5b8301601f81018513610b5a575f80fd5b8035610b6861098a82610914565b81815260a09182028301840191848201919088841115610b86575f80fd5b938501935b83851015610a035780858a031215610ba1575f80fd5b610ba96107c6565b8535815286860135878201526040610bc2818801610a0f565b908201526060610bd3878201610a0f565b908201526080610be4878201610937565b9082015283529384019391850191610b8b565b600181811c90821680610c0b57607f821691505b602082108103610c2957634e487b7160e01b5f52602260045260245ffd5b50919050565b601f821115610c7657805f5260205f20601f840160051c81016020851015610c545750805b601f840160051c820191505b81811015610c73575f8155600101610c60565b50505b505050565b815167ffffffffffffffff811115610c9557610c95610789565b610ca981610ca38454610bf7565b84610c2f565b602080601f831160018114610cdc575f8415610cc55750858301515b5f19600386901b1c1916600185901b178555610d33565b5f85815260208120601f198616915b82811015610d0a57888601518255948401946001909101908401610ceb565b5085821015610d2757878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52603260045260245ffdfea26469706673582212206590d836561d8340d2d83d5cd063d2c751fdea336b0e3302a990dd0600d171d464736f6c63430008180033 /// ``` #[rustfmt::skip] #[allow(clippy::all)] pub static DEPLOYED_BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( - b"`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[P`\x046\x10a\0\xE5W_5`\xE0\x1C\x80c\x88\xDF\xDD\xC6\x11a\0\x88W\x80c\xC8\xAF:\xA6\x11a\0cW\x80c\xC8\xAF:\xA6\x14a\x02hW\x80c\xD1^\xC8Q\x14a\x02{W\x80c\xEA\xD1\x84\0\x14a\x02\xBDW\x80c\xF2]T\xF5\x14a\x02\xDFW_\x80\xFD[\x80c\x88\xDF\xDD\xC6\x14a\x02\x10W\x80c\xA3\x14\x15\x0F\x14a\x02JW\x80c\xA5\xD6f\xA9\x14a\x02SW_\x80\xFD[\x80c\x1C\x13C\x15\x11a\0\xC3W\x80c\x1C\x13C\x15\x14a\x01\x80W\x80c*\xE4&\x86\x14a\x01\x93W\x80ci\x87\xB1\xFB\x14a\x01\xD3W\x80cl\xC0\x14\xDE\x14a\x01\xF4W_\x80\xFD[\x80c\x02\0\"\\\x14a\0\xE9W\x80c\nM\x04\xF7\x14a\0\xFEW\x80c\x0C\x16\x16\xC9\x14a\x01mW[_\x80\xFD[a\0\xFCa\0\xF76`\x04a\x05\xD8V[a\x02\xF2V[\0[a\x01Ca\x01\x0C6`\x04a\x06\x97V[`\t` \x90\x81R_\x92\x83R`@\x80\x84 \x90\x91R\x90\x82R\x90 \x80T`\x01\x90\x91\x01T`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[`@\x80Q\x93\x84R`\x01`\x01`\x80\x1B\x03\x92\x83\x16` \x85\x01R\x91\x16\x90\x82\x01R``\x01[`@Q\x80\x91\x03\x90\xF3[a\0\xFCa\x01{6`\x04a\x06\xB7V[a\x036V[a\0\xFCa\x01\x8E6`\x04a\x07\x97V[a\x04wV[a\x01\xBBa\x01\xA16`\x04a\x07\xC1V[`\x04` R_\x90\x81R`@\x90 T`\x01`\x01`\xA0\x1B\x03\x16\x81V[`@Q`\x01`\x01`\xA0\x1B\x03\x90\x91\x16\x81R` \x01a\x01dV[a\x01\xE6a\x01\xE16`\x04a\x07\xC1V[a\x04\xA4V[`@Q\x90\x81R` \x01a\x01dV[_Ta\x02\0\x90`\xFF\x16\x81V[`@Q\x90\x15\x15\x81R` \x01a\x01dV[a\x01Ca\x02\x1E6`\x04a\x07\xC1V[`\x08` R_\x90\x81R`@\x90 \x80T`\x01\x90\x91\x01T`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[a\x01\xE6`\x01T\x81V[a\x02[a\x04\xC3V[`@Qa\x01d\x91\x90a\x07\xD8V[`\x03Ta\x01\xBB\x90`\x01`\x01`\xA0\x1B\x03\x16\x81V[a\0\xFCa\x02\x896`\x04a\x07\xC1V[`\x05\x80T`\x01\x81\x01\x82U_\x91\x90\x91R\x7F\x03kc\x84\xB5\xEC\xA7\x91\xC6'a\x15-\x0Cy\xBB\x06\x04\xC1\x04\xA5\xFBoN\xB0p?1T\xBB=\xB0\x01UV[`\x06T`\x07Ta\x01C\x91\x90`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[a\0\xFCa\x02\xED6`\x04a\x07\xC1V[`\x01UV[_\x80T`\xFF\x19\x16\x85\x15\x15\x17\x90U`\x01\x83\x90U`\x02a\x03\x10\x83\x82a\x08\xA8V[P`\x03\x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x92\x90\x92\x16\x91\x90\x91\x17\x90UPPPV[_[\x81Q\x81\x10\x15a\x04sW_\x82\x82\x81Q\x81\x10a\x03TWa\x03Ta\t|V[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x03qWa\x03qa\thV[\x03a\x03\xB8W`\x04_\x83\x83\x81Q\x81\x10a\x03\x8BWa\x03\x8Ba\t|V[` \x90\x81\x02\x91\x90\x91\x01\x81\x01QQ\x82R\x81\x01\x91\x90\x91R`@\x01_ \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16\x90Ua\x04kV[`\x02\x82\x82\x81Q\x81\x10a\x03\xCCWa\x03\xCCa\t|V[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x03\xE9Wa\x03\xE9a\thV[\x14\x80a\x04#WP`\x01\x82\x82\x81Q\x81\x10a\x04\x04Wa\x04\x04a\t|V[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x04!Wa\x04!a\thV[\x14[\x15a\x04kWa\x04k\x82\x82\x81Q\x81\x10a\x04=Wa\x04=a\t|V[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x04ZWa\x04Za\t|V[` \x02` \x01\x01Q` \x01Qa\x04wV[`\x01\x01a\x038V[PPV[_\x91\x82R`\x04` R`@\x90\x91 \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x90\x92\x16\x91\x90\x91\x17\x90UV[`\x05\x81\x81T\x81\x10a\x04\xB3W_\x80\xFD[_\x91\x82R` \x90\x91 \x01T\x90P\x81V[`\x02\x80Ta\x04\xD0\x90a\x08$V[\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80Ta\x04\xFC\x90a\x08$V[\x80\x15a\x05GW\x80`\x1F\x10a\x05\x1EWa\x01\0\x80\x83T\x04\x02\x83R\x91` \x01\x91a\x05GV[\x82\x01\x91\x90_R` _ \x90[\x81T\x81R\x90`\x01\x01\x90` \x01\x80\x83\x11a\x05*W\x82\x90\x03`\x1F\x16\x82\x01\x91[PPPPP\x81V[cNH{q`\xE0\x1B_R`A`\x04R`$_\xFD[`@Q``\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x05\x86Wa\x05\x86a\x05OV[`@R\x90V[`@Q`\x1F\x82\x01`\x1F\x19\x16\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x05\xB5Wa\x05\xB5a\x05OV[`@R\x91\x90PV[\x805`\x01`\x01`\xA0\x1B\x03\x81\x16\x81\x14a\x05\xD3W_\x80\xFD[\x91\x90PV[_\x80_\x80`\x80\x85\x87\x03\x12\x15a\x05\xEBW_\x80\xFD[\x845\x80\x15\x15\x81\x14a\x05\xFAW_\x80\xFD[\x93P` \x85\x81\x015\x93P`@\x86\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x82\x11\x15a\x06\x1EW_\x80\xFD[\x81\x88\x01\x91P\x88`\x1F\x83\x01\x12a\x061W_\x80\xFD[\x815\x81\x81\x11\x15a\x06CWa\x06Ca\x05OV[a\x06U`\x1F\x82\x01`\x1F\x19\x16\x85\x01a\x05\x8CV[\x91P\x80\x82R\x89\x84\x82\x85\x01\x01\x11\x15a\x06jW_\x80\xFD[\x80\x84\x84\x01\x85\x84\x017_\x84\x82\x84\x01\x01RP\x80\x94PPPPa\x06\x8C``\x86\x01a\x05\xBDV[\x90P\x92\x95\x91\x94P\x92PV[_\x80`@\x83\x85\x03\x12\x15a\x06\xA8W_\x80\xFD[PP\x805\x92` \x90\x91\x015\x91PV[_` \x80\x83\x85\x03\x12\x15a\x06\xC8W_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x82\x11\x15a\x06\xDFW_\x80\xFD[\x81\x85\x01\x91P\x85`\x1F\x83\x01\x12a\x06\xF2W_\x80\xFD[\x815\x81\x81\x11\x15a\x07\x04Wa\x07\x04a\x05OV[a\x07\x12\x84\x82`\x05\x1B\x01a\x05\x8CV[\x81\x81R\x84\x81\x01\x92P``\x91\x82\x02\x84\x01\x85\x01\x91\x88\x83\x11\x15a\x070W_\x80\xFD[\x93\x85\x01\x93[\x82\x85\x10\x15a\x07\x8BW\x80\x85\x8A\x03\x12\x15a\x07KW_\x80\xFD[a\x07Sa\x05cV[\x855\x81Ra\x07b\x87\x87\x01a\x05\xBDV[\x87\x82\x01R`@\x80\x87\x015`\x03\x81\x10a\x07xW_\x80\xFD[\x90\x82\x01R\x84R\x93\x84\x01\x93\x92\x85\x01\x92a\x075V[P\x97\x96PPPPPPPV[_\x80`@\x83\x85\x03\x12\x15a\x07\xA8W_\x80\xFD[\x825\x91Pa\x07\xB8` \x84\x01a\x05\xBDV[\x90P\x92P\x92\x90PV[_` \x82\x84\x03\x12\x15a\x07\xD1W_\x80\xFD[P5\x91\x90PV[_` \x80\x83R\x83Q\x80` \x85\x01R_[\x81\x81\x10\x15a\x08\x04W\x85\x81\x01\x83\x01Q\x85\x82\x01`@\x01R\x82\x01a\x07\xE8V[P_`@\x82\x86\x01\x01R`@`\x1F\x19`\x1F\x83\x01\x16\x85\x01\x01\x92PPP\x92\x91PPV[`\x01\x81\x81\x1C\x90\x82\x16\x80a\x088W`\x7F\x82\x16\x91P[` \x82\x10\x81\x03a\x08VWcNH{q`\xE0\x1B_R`\"`\x04R`$_\xFD[P\x91\x90PV[`\x1F\x82\x11\x15a\x08\xA3W\x80_R` _ `\x1F\x84\x01`\x05\x1C\x81\x01` \x85\x10\x15a\x08\x81WP\x80[`\x1F\x84\x01`\x05\x1C\x82\x01\x91P[\x81\x81\x10\x15a\x08\xA0W_\x81U`\x01\x01a\x08\x8DV[PP[PPPV[\x81Qg\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x08\xC2Wa\x08\xC2a\x05OV[a\x08\xD6\x81a\x08\xD0\x84Ta\x08$V[\x84a\x08\\V[` \x80`\x1F\x83\x11`\x01\x81\x14a\t\tW_\x84\x15a\x08\xF2WP\x85\x83\x01Q[_\x19`\x03\x86\x90\x1B\x1C\x19\x16`\x01\x85\x90\x1B\x17\x85Ua\t`V[_\x85\x81R` \x81 `\x1F\x19\x86\x16\x91[\x82\x81\x10\x15a\t7W\x88\x86\x01Q\x82U\x94\x84\x01\x94`\x01\x90\x91\x01\x90\x84\x01a\t\x18V[P\x85\x82\x10\x15a\tTW\x87\x85\x01Q_\x19`\x03\x88\x90\x1B`\xF8\x16\x1C\x19\x16\x81U[PP`\x01\x84`\x01\x1B\x01\x85U[PPPPPPV[cNH{q`\xE0\x1B_R`!`\x04R`$_\xFD[cNH{q`\xE0\x1B_R`2`\x04R`$_\xFD\xFE\xA2dipfsX\"\x12 \xCAf\xFA\xDEy\x18&;\x04\xA1\xBB\xF7\xB0Px\x9F\x81\x1F\\\x17\xAD\x95\x7F\xAC\xFC\x96Q\xC3\xDE\xCB\x1C\xD5dsolcC\0\x08\x18\x003", + b"`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[P`\x046\x10a\x01\x06W_5`\xE0\x1C\x80c\x80&\xDE1\x11a\0\x9EW\x80c\xC7\xBFM\xB5\x11a\0nW\x80c\xC7\xBFM\xB5\x14a\x02\xC9W\x80c\xC8\xAF:\xA6\x14a\x02\xDCW\x80c\xD1^\xC8Q\x14a\x02\xEFW\x80c\xEA\xD1\x84\0\x14a\x031W\x80c\xF2]T\xF5\x14a\x03SW_\x80\xFD[\x80c\x80&\xDE1\x14a\x02^W\x80c\x88\xDF\xDD\xC6\x14a\x02qW\x80c\xA3\x14\x15\x0F\x14a\x02\xABW\x80c\xA5\xD6f\xA9\x14a\x02\xB4W_\x80\xFD[\x80c\x1C\x13C\x15\x11a\0\xD9W\x80c\x1C\x13C\x15\x14a\x01\xCEW\x80c*\xE4&\x86\x14a\x01\xE1W\x80ci\x87\xB1\xFB\x14a\x02!W\x80cl\xC0\x14\xDE\x14a\x02BW_\x80\xFD[\x80c\x02\0\"\\\x14a\x01\nW\x80c\nM\x04\xF7\x14a\x01\x1FW\x80c\x0C\x16\x16\xC9\x14a\x01\x8EW\x80c\x14\x17\xA4\xF0\x14a\x01\xA1W[_\x80\xFD[a\x01\x1Da\x01\x186`\x04a\x085V[a\x03fV[\0[a\x01da\x01-6`\x04a\x08\xF4V[`\t` \x90\x81R_\x92\x83R`@\x80\x84 \x90\x91R\x90\x82R\x90 \x80T`\x01\x90\x91\x01T`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[`@\x80Q\x93\x84R`\x01`\x01`\x80\x1B\x03\x92\x83\x16` \x85\x01R\x91\x16\x90\x82\x01R``\x01[`@Q\x80\x91\x03\x90\xF3[a\x01\x1Da\x01\x9C6`\x04a\tEV[a\x03\xAAV[a\x01\x1Da\x01\xAF6`\x04a\n%V[`\x06\x92\x90\x92U`\x01`\x01`\x80\x1B\x03\x91\x82\x16`\x01`\x80\x1B\x02\x91\x16\x17`\x07UV[a\x01\x1Da\x01\xDC6`\x04a\n^V[a\x04\xEBV[a\x02\ta\x01\xEF6`\x04a\n\x88V[`\x04` R_\x90\x81R`@\x90 T`\x01`\x01`\xA0\x1B\x03\x16\x81V[`@Q`\x01`\x01`\xA0\x1B\x03\x90\x91\x16\x81R` \x01a\x01\x85V[a\x024a\x02/6`\x04a\n\x88V[a\x05\x18V[`@Q\x90\x81R` \x01a\x01\x85V[_Ta\x02N\x90`\xFF\x16\x81V[`@Q\x90\x15\x15\x81R` \x01a\x01\x85V[a\x01\x1Da\x02l6`\x04a\n\x9FV[a\x057V[a\x01da\x02\x7F6`\x04a\n\x88V[`\x08` R_\x90\x81R`@\x90 \x80T`\x01\x90\x91\x01T`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[a\x024`\x01T\x81V[a\x02\xBCa\x05\x89V[`@Qa\x01\x85\x91\x90a\n\xD7V[a\x01\x1Da\x02\xD76`\x04a\x0B#V[a\x06\x15V[`\x03Ta\x02\t\x90`\x01`\x01`\xA0\x1B\x03\x16\x81V[a\x01\x1Da\x02\xFD6`\x04a\n\x88V[`\x05\x80T`\x01\x81\x01\x82U_\x91\x90\x91R\x7F\x03kc\x84\xB5\xEC\xA7\x91\xC6'a\x15-\x0Cy\xBB\x06\x04\xC1\x04\xA5\xFBoN\xB0p?1T\xBB=\xB0\x01UV[`\x06T`\x07Ta\x01d\x91\x90`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[a\x01\x1Da\x03a6`\x04a\n\x88V[`\x01UV[_\x80T`\xFF\x19\x16\x85\x15\x15\x17\x90U`\x01\x83\x90U`\x02a\x03\x84\x83\x82a\x0C{V[P`\x03\x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x92\x90\x92\x16\x91\x90\x91\x17\x90UPPPV[_[\x81Q\x81\x10\x15a\x04\xE7W_\x82\x82\x81Q\x81\x10a\x03\xC8Wa\x03\xC8a\rOV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x03\xE5Wa\x03\xE5a\r;V[\x03a\x04,W`\x04_\x83\x83\x81Q\x81\x10a\x03\xFFWa\x03\xFFa\rOV[` \x90\x81\x02\x91\x90\x91\x01\x81\x01QQ\x82R\x81\x01\x91\x90\x91R`@\x01_ \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16\x90Ua\x04\xDFV[`\x02\x82\x82\x81Q\x81\x10a\x04@Wa\x04@a\rOV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x04]Wa\x04]a\r;V[\x14\x80a\x04\x97WP`\x01\x82\x82\x81Q\x81\x10a\x04xWa\x04xa\rOV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x04\x95Wa\x04\x95a\r;V[\x14[\x15a\x04\xDFWa\x04\xDF\x82\x82\x81Q\x81\x10a\x04\xB1Wa\x04\xB1a\rOV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x04\xCEWa\x04\xCEa\rOV[` \x02` \x01\x01Q` \x01Qa\x04\xEBV[`\x01\x01a\x03\xACV[PPV[_\x91\x82R`\x04` R`@\x90\x91 \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x90\x92\x16\x91\x90\x91\x17\x90UV[`\x05\x81\x81T\x81\x10a\x05'W_\x80\xFD[_\x91\x82R` \x90\x91 \x01T\x90P\x81V[`@\x80Q``\x81\x01\x82R\x93\x84R`\x01`\x01`\x80\x1B\x03\x92\x83\x16` \x80\x86\x01\x91\x82R\x92\x84\x16\x85\x83\x01\x90\x81R_\x96\x87R`\x08\x90\x93R\x94 \x92Q\x83U\x92Q\x92Q\x81\x16`\x01`\x80\x1B\x02\x92\x16\x91\x90\x91\x17`\x01\x90\x91\x01UV[`\x02\x80Ta\x05\x96\x90a\x0B\xF7V[\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80Ta\x05\xC2\x90a\x0B\xF7V[\x80\x15a\x06\rW\x80`\x1F\x10a\x05\xE4Wa\x01\0\x80\x83T\x04\x02\x83R\x91` \x01\x91a\x06\rV[\x82\x01\x91\x90_R` _ \x90[\x81T\x81R\x90`\x01\x01\x90` \x01\x80\x83\x11a\x05\xF0W\x82\x90\x03`\x1F\x16\x82\x01\x91[PPPPP\x81V[_[\x81Q\x81\x10\x15a\x04\xE7W_\x82\x82\x81Q\x81\x10a\x063Wa\x063a\rOV[` \x02` \x01\x01Q`\x80\x01Q`\x02\x81\x11\x15a\x06PWa\x06Pa\r;V[\x03a\x06\x92W`\x08_\x83\x83\x81Q\x81\x10a\x06jWa\x06ja\rOV[` \x90\x81\x02\x91\x90\x91\x01\x81\x01QQ\x82R\x81\x01\x91\x90\x91R`@\x01_\x90\x81 \x81\x81U`\x01\x01Ua\x07\x81V[`\x02\x82\x82\x81Q\x81\x10a\x06\xA6Wa\x06\xA6a\rOV[` \x02` \x01\x01Q`\x80\x01Q`\x02\x81\x11\x15a\x06\xC3Wa\x06\xC3a\r;V[\x14\x80a\x06\xFDWP`\x01\x82\x82\x81Q\x81\x10a\x06\xDEWa\x06\xDEa\rOV[` \x02` \x01\x01Q`\x80\x01Q`\x02\x81\x11\x15a\x06\xFBWa\x06\xFBa\r;V[\x14[\x15a\x07\x81Wa\x07\x81\x82\x82\x81Q\x81\x10a\x07\x17Wa\x07\x17a\rOV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x074Wa\x074a\rOV[` \x02` \x01\x01Q` \x01Q\x84\x84\x81Q\x81\x10a\x07RWa\x07Ra\rOV[` \x02` \x01\x01Q`@\x01Q\x85\x85\x81Q\x81\x10a\x07pWa\x07pa\rOV[` \x02` \x01\x01Q``\x01Qa\x057V[`\x01\x01a\x06\x17V[cNH{q`\xE0\x1B_R`A`\x04R`$_\xFD[`@Q``\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x07\xC0Wa\x07\xC0a\x07\x89V[`@R\x90V[`@Q`\xA0\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x07\xC0Wa\x07\xC0a\x07\x89V[`@Q`\x1F\x82\x01`\x1F\x19\x16\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x08\x12Wa\x08\x12a\x07\x89V[`@R\x91\x90PV[\x805`\x01`\x01`\xA0\x1B\x03\x81\x16\x81\x14a\x080W_\x80\xFD[\x91\x90PV[_\x80_\x80`\x80\x85\x87\x03\x12\x15a\x08HW_\x80\xFD[\x845\x80\x15\x15\x81\x14a\x08WW_\x80\xFD[\x93P` \x85\x81\x015\x93P`@\x86\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x82\x11\x15a\x08{W_\x80\xFD[\x81\x88\x01\x91P\x88`\x1F\x83\x01\x12a\x08\x8EW_\x80\xFD[\x815\x81\x81\x11\x15a\x08\xA0Wa\x08\xA0a\x07\x89V[a\x08\xB2`\x1F\x82\x01`\x1F\x19\x16\x85\x01a\x07\xE9V[\x91P\x80\x82R\x89\x84\x82\x85\x01\x01\x11\x15a\x08\xC7W_\x80\xFD[\x80\x84\x84\x01\x85\x84\x017_\x84\x82\x84\x01\x01RP\x80\x94PPPPa\x08\xE9``\x86\x01a\x08\x1AV[\x90P\x92\x95\x91\x94P\x92PV[_\x80`@\x83\x85\x03\x12\x15a\t\x05W_\x80\xFD[PP\x805\x92` \x90\x91\x015\x91PV[_g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x82\x11\x15a\t-Wa\t-a\x07\x89V[P`\x05\x1B` \x01\x90V[\x805`\x03\x81\x10a\x080W_\x80\xFD[_` \x80\x83\x85\x03\x12\x15a\tVW_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\tlW_\x80\xFD[\x83\x01`\x1F\x81\x01\x85\x13a\t|W_\x80\xFD[\x805a\t\x8Fa\t\x8A\x82a\t\x14V[a\x07\xE9V[\x81\x81R``\x91\x82\x02\x83\x01\x84\x01\x91\x84\x82\x01\x91\x90\x88\x84\x11\x15a\t\xADW_\x80\xFD[\x93\x85\x01\x93[\x83\x85\x10\x15a\n\x03W\x80\x85\x8A\x03\x12\x15a\t\xC8W_\x80\xFD[a\t\xD0a\x07\x9DV[\x855\x81Ra\t\xDF\x87\x87\x01a\x08\x1AV[\x87\x82\x01R`@a\t\xF0\x81\x88\x01a\t7V[\x90\x82\x01R\x83R\x93\x84\x01\x93\x91\x85\x01\x91a\t\xB2V[P\x97\x96PPPPPPPV[\x805`\x01`\x01`\x80\x1B\x03\x81\x16\x81\x14a\x080W_\x80\xFD[_\x80_``\x84\x86\x03\x12\x15a\n7W_\x80\xFD[\x835\x92Pa\nG` \x85\x01a\n\x0FV[\x91Pa\nU`@\x85\x01a\n\x0FV[\x90P\x92P\x92P\x92V[_\x80`@\x83\x85\x03\x12\x15a\noW_\x80\xFD[\x825\x91Pa\n\x7F` \x84\x01a\x08\x1AV[\x90P\x92P\x92\x90PV[_` \x82\x84\x03\x12\x15a\n\x98W_\x80\xFD[P5\x91\x90PV[_\x80_\x80`\x80\x85\x87\x03\x12\x15a\n\xB2W_\x80\xFD[\x845\x93P` \x85\x015\x92Pa\n\xC9`@\x86\x01a\n\x0FV[\x91Pa\x08\xE9``\x86\x01a\n\x0FV[_` \x80\x83R\x83Q\x80` \x85\x01R_[\x81\x81\x10\x15a\x0B\x03W\x85\x81\x01\x83\x01Q\x85\x82\x01`@\x01R\x82\x01a\n\xE7V[P_`@\x82\x86\x01\x01R`@`\x1F\x19`\x1F\x83\x01\x16\x85\x01\x01\x92PPP\x92\x91PPV[_` \x80\x83\x85\x03\x12\x15a\x0B4W_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0BJW_\x80\xFD[\x83\x01`\x1F\x81\x01\x85\x13a\x0BZW_\x80\xFD[\x805a\x0Bha\t\x8A\x82a\t\x14V[\x81\x81R`\xA0\x91\x82\x02\x83\x01\x84\x01\x91\x84\x82\x01\x91\x90\x88\x84\x11\x15a\x0B\x86W_\x80\xFD[\x93\x85\x01\x93[\x83\x85\x10\x15a\n\x03W\x80\x85\x8A\x03\x12\x15a\x0B\xA1W_\x80\xFD[a\x0B\xA9a\x07\xC6V[\x855\x81R\x86\x86\x015\x87\x82\x01R`@a\x0B\xC2\x81\x88\x01a\n\x0FV[\x90\x82\x01R``a\x0B\xD3\x87\x82\x01a\n\x0FV[\x90\x82\x01R`\x80a\x0B\xE4\x87\x82\x01a\t7V[\x90\x82\x01R\x83R\x93\x84\x01\x93\x91\x85\x01\x91a\x0B\x8BV[`\x01\x81\x81\x1C\x90\x82\x16\x80a\x0C\x0BW`\x7F\x82\x16\x91P[` \x82\x10\x81\x03a\x0C)WcNH{q`\xE0\x1B_R`\"`\x04R`$_\xFD[P\x91\x90PV[`\x1F\x82\x11\x15a\x0CvW\x80_R` _ `\x1F\x84\x01`\x05\x1C\x81\x01` \x85\x10\x15a\x0CTWP\x80[`\x1F\x84\x01`\x05\x1C\x82\x01\x91P[\x81\x81\x10\x15a\x0CsW_\x81U`\x01\x01a\x0C`V[PP[PPPV[\x81Qg\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0C\x95Wa\x0C\x95a\x07\x89V[a\x0C\xA9\x81a\x0C\xA3\x84Ta\x0B\xF7V[\x84a\x0C/V[` \x80`\x1F\x83\x11`\x01\x81\x14a\x0C\xDCW_\x84\x15a\x0C\xC5WP\x85\x83\x01Q[_\x19`\x03\x86\x90\x1B\x1C\x19\x16`\x01\x85\x90\x1B\x17\x85Ua\r3V[_\x85\x81R` \x81 `\x1F\x19\x86\x16\x91[\x82\x81\x10\x15a\r\nW\x88\x86\x01Q\x82U\x94\x84\x01\x94`\x01\x90\x91\x01\x90\x84\x01a\x0C\xEBV[P\x85\x82\x10\x15a\r'W\x87\x85\x01Q_\x19`\x03\x88\x90\x1B`\xF8\x16\x1C\x19\x16\x81U[PP`\x01\x84`\x01\x1B\x01\x85U[PPPPPPV[cNH{q`\xE0\x1B_R`!`\x04R`$_\xFD[cNH{q`\xE0\x1B_R`2`\x04R`$_\xFD\xFE\xA2dipfsX\"\x12 e\x90\xD86V\x1D\x83@\xD2\xD8=\\\xD0c\xD2\xC7Q\xFD\xEA3k\x0E3\x02\xA9\x90\xDD\x06\0\xD1q\xD4dsolcC\0\x08\x18\x003", ); #[allow(non_camel_case_types, non_snake_case)] #[derive(Clone)] @@ -644,6 +745,261 @@ pub mod Simple { } } }; + /**```solidity + struct MappingStructChange { uint256 key; uint256 field1; uint128 field2; uint128 field3; MappingOperation operation; } + ```*/ + #[allow(non_camel_case_types, non_snake_case)] + #[derive(Clone)] + pub struct MappingStructChange { + pub key: alloy::sol_types::private::U256, + pub field1: alloy::sol_types::private::U256, + pub field2: u128, + pub field3: u128, + pub operation: ::RustType, + } + #[allow(non_camel_case_types, non_snake_case, clippy::style)] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[doc(hidden)] + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + MappingOperation, + ); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::U256, + alloy::sol_types::private::U256, + u128, + u128, + ::RustType, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: MappingStructChange) -> Self { + ( + value.key, + value.field1, + value.field2, + value.field3, + value.operation, + ) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for MappingStructChange { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { + key: tuple.0, + field1: tuple.1, + field2: tuple.2, + field3: tuple.3, + operation: tuple.4, + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolValue for MappingStructChange { + type SolType = Self; + } + #[automatically_derived] + impl alloy_sol_types::private::SolTypeValue for MappingStructChange { + #[inline] + fn stv_to_tokens(&self) -> ::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.key, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field1, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field2, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field3, + ), + ::tokenize(&self.operation), + ) + } + #[inline] + fn stv_abi_encoded_size(&self) -> usize { + if let Some(size) = ::ENCODED_SIZE { + return size; + } + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_encoded_size(&tuple) + } + #[inline] + fn stv_eip712_data_word(&self) -> alloy_sol_types::Word { + ::eip712_hash_struct(self) + } + #[inline] + fn stv_abi_encode_packed_to(&self, out: &mut alloy_sol_types::private::Vec) { + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_encode_packed_to( + &tuple, out, + ) + } + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + if let Some(size) = ::PACKED_ENCODED_SIZE { + return size; + } + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_packed_encoded_size( + &tuple, + ) + } + } + #[automatically_derived] + impl alloy_sol_types::SolType for MappingStructChange { + type RustType = Self; + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SOL_NAME: &'static str = ::NAME; + const ENCODED_SIZE: Option = + as alloy_sol_types::SolType>::ENCODED_SIZE; + const PACKED_ENCODED_SIZE: Option = + as alloy_sol_types::SolType>::PACKED_ENCODED_SIZE; + #[inline] + fn valid_token(token: &Self::Token<'_>) -> bool { + as alloy_sol_types::SolType>::valid_token(token) + } + #[inline] + fn detokenize(token: Self::Token<'_>) -> Self::RustType { + let tuple = as alloy_sol_types::SolType>::detokenize(token); + >>::from(tuple) + } + } + #[automatically_derived] + impl alloy_sol_types::SolStruct for MappingStructChange { + const NAME: &'static str = "MappingStructChange"; + #[inline] + fn eip712_root_type() -> alloy_sol_types::private::Cow<'static, str> { + alloy_sol_types::private::Cow::Borrowed( + "MappingStructChange(uint256 key,uint256 field1,uint128 field2,uint128 field3,uint8 operation)", + ) + } + #[inline] + fn eip712_components( + ) -> alloy_sol_types::private::Vec> + { + alloy_sol_types::private::Vec::new() + } + #[inline] + fn eip712_encode_type() -> alloy_sol_types::private::Cow<'static, str> { + ::eip712_root_type() + } + #[inline] + fn eip712_encode_data(&self) -> alloy_sol_types::private::Vec { + [ + as alloy_sol_types::SolType>::eip712_data_word(&self.key) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.field1) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.field2) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.field3) + .0, + ::eip712_data_word( + &self.operation, + ) + .0, + ] + .concat() + } + } + #[automatically_derived] + impl alloy_sol_types::EventTopic for MappingStructChange { + #[inline] + fn topic_preimage_length(rust: &Self::RustType) -> usize { + 0usize + + as alloy_sol_types::EventTopic>::topic_preimage_length(&rust.key) + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.field1, + ) + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.field2, + ) + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.field3, + ) + + ::topic_preimage_length( + &rust.operation, + ) + } + #[inline] + fn encode_topic_preimage( + rust: &Self::RustType, + out: &mut alloy_sol_types::private::Vec, + ) { + out.reserve(::topic_preimage_length(rust)); + as alloy_sol_types::EventTopic>::encode_topic_preimage(&rust.key, out); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.field1, + out, + ); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.field2, + out, + ); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.field3, + out, + ); + ::encode_topic_preimage( + &rust.operation, + out, + ); + } + #[inline] + fn encode_topic(rust: &Self::RustType) -> alloy_sol_types::abi::token::WordToken { + let mut out = alloy_sol_types::private::Vec::new(); + ::encode_topic_preimage(rust, &mut out); + alloy_sol_types::abi::token::WordToken(alloy_sol_types::private::keccak256(out)) + } + } + }; /**Function with signature `addToArray(uint256)` and selector `0xd15ec851`. ```solidity function addToArray(uint256 value) external; @@ -962,7 +1318,123 @@ pub mod Simple { fn tokenize(&self) -> Self::Token<'_> { ( as alloy_sol_types::SolType>::tokenize(&self.changes), + ) + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `changeMappingStruct((uint256,uint256,uint128,uint128,uint8)[])` and selector `0xc7bf4db5`. + ```solidity + function changeMappingStruct(MappingStructChange[] memory changes) external; + ```*/ + #[allow(non_camel_case_types, non_snake_case)] + #[derive(Clone)] + pub struct changeMappingStructCall { + pub changes: alloy::sol_types::private::Vec< + ::RustType, + >, + } + ///Container type for the return parameters of the [`changeMappingStruct((uint256,uint256,uint128,uint128,uint8)[])`](changeMappingStructCall) function. + #[allow(non_camel_case_types, non_snake_case)] + #[derive(Clone)] + pub struct changeMappingStructReturn {} + #[allow(non_camel_case_types, non_snake_case, clippy::style)] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Array,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::Vec< + ::RustType, + >, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: changeMappingStructCall) -> Self { + (value.changes,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for changeMappingStructCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { changes: tuple.0 } + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: changeMappingStructReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for changeMappingStructReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for changeMappingStructCall { + type Parameters<'a> = (alloy::sol_types::sol_data::Array,); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = changeMappingStructReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = + "changeMappingStruct((uint256,uint256,uint128,uint128,uint8)[])"; + const SELECTOR: [u8; 4] = [199u8, 191u8, 77u8, 181u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize(&self.changes), ) } @@ -1775,6 +2247,147 @@ pub mod Simple { } } }; + /**Function with signature `setMappingStruct(uint256,uint256,uint128,uint128)` and selector `0x8026de31`. + ```solidity + function setMappingStruct(uint256 _key, uint256 _field1, uint128 _field2, uint128 _field3) external; + ```*/ + #[allow(non_camel_case_types, non_snake_case)] + #[derive(Clone)] + pub struct setMappingStructCall { + pub _key: alloy::sol_types::private::U256, + pub _field1: alloy::sol_types::private::U256, + pub _field2: u128, + pub _field3: u128, + } + ///Container type for the return parameters of the [`setMappingStruct(uint256,uint256,uint128,uint128)`](setMappingStructCall) function. + #[allow(non_camel_case_types, non_snake_case)] + #[derive(Clone)] + pub struct setMappingStructReturn {} + #[allow(non_camel_case_types, non_snake_case, clippy::style)] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::U256, + alloy::sol_types::private::U256, + u128, + u128, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setMappingStructCall) -> Self { + (value._key, value._field1, value._field2, value._field3) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for setMappingStructCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { + _key: tuple.0, + _field1: tuple.1, + _field2: tuple.2, + _field3: tuple.3, + } + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setMappingStructReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for setMappingStructReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for setMappingStructCall { + type Parameters<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = setMappingStructReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "setMappingStruct(uint256,uint256,uint128,uint128)"; + const SELECTOR: [u8; 4] = [128u8, 38u8, 222u8, 49u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self._key, + ), + as alloy_sol_types::SolType>::tokenize( + &self._field1, + ), + as alloy_sol_types::SolType>::tokenize( + &self._field2, + ), + as alloy_sol_types::SolType>::tokenize( + &self._field3, + ), + ) + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; /**Function with signature `setS2(uint256)` and selector `0xf25d54f5`. ```solidity function setS2(uint256 newS2) external; @@ -1884,6 +2497,135 @@ pub mod Simple { } } }; + /**Function with signature `setSimpleStruct(uint256,uint128,uint128)` and selector `0x1417a4f0`. + ```solidity + function setSimpleStruct(uint256 _field1, uint128 _field2, uint128 _field3) external; + ```*/ + #[allow(non_camel_case_types, non_snake_case)] + #[derive(Clone)] + pub struct setSimpleStructCall { + pub _field1: alloy::sol_types::private::U256, + pub _field2: u128, + pub _field3: u128, + } + ///Container type for the return parameters of the [`setSimpleStruct(uint256,uint128,uint128)`](setSimpleStructCall) function. + #[allow(non_camel_case_types, non_snake_case)] + #[derive(Clone)] + pub struct setSimpleStructReturn {} + #[allow(non_camel_case_types, non_snake_case, clippy::style)] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::U256, u128, u128); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setSimpleStructCall) -> Self { + (value._field1, value._field2, value._field3) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for setSimpleStructCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { + _field1: tuple.0, + _field2: tuple.1, + _field3: tuple.2, + } + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setSimpleStructReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for setSimpleStructReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for setSimpleStructCall { + type Parameters<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = setSimpleStructReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "setSimpleStruct(uint256,uint128,uint128)"; + const SELECTOR: [u8; 4] = [20u8, 23u8, 164u8, 240u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self._field1, + ), + as alloy_sol_types::SolType>::tokenize( + &self._field2, + ), + as alloy_sol_types::SolType>::tokenize( + &self._field3, + ), + ) + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; /**Function with signature `setSimples(bool,uint256,string,address)` and selector `0x0200225c`. ```solidity function setSimples(bool newS1, uint256 newS2, string memory newS3, address newS4) external; @@ -2274,6 +3016,7 @@ pub mod Simple { addToArray(addToArrayCall), arr1(arr1Call), changeMapping(changeMappingCall), + changeMappingStruct(changeMappingStructCall), m1(m1Call), mappingOfMappings(mappingOfMappingsCall), s1(s1Call), @@ -2281,7 +3024,9 @@ pub mod Simple { s3(s3Call), s4(s4Call), setMapping(setMappingCall), + setMappingStruct(setMappingStructCall), setS2(setS2Call), + setSimpleStruct(setSimpleStructCall), setSimples(setSimplesCall), simpleStruct(simpleStructCall), structMapping(structMappingCall), @@ -2298,13 +3043,16 @@ pub mod Simple { [2u8, 0u8, 34u8, 92u8], [10u8, 77u8, 4u8, 247u8], [12u8, 22u8, 22u8, 201u8], + [20u8, 23u8, 164u8, 240u8], [28u8, 19u8, 67u8, 21u8], [42u8, 228u8, 38u8, 134u8], [105u8, 135u8, 177u8, 251u8], [108u8, 192u8, 20u8, 222u8], + [128u8, 38u8, 222u8, 49u8], [136u8, 223u8, 221u8, 198u8], [163u8, 20u8, 21u8, 15u8], [165u8, 214u8, 102u8, 169u8], + [199u8, 191u8, 77u8, 181u8], [200u8, 175u8, 58u8, 166u8], [209u8, 94u8, 200u8, 81u8], [234u8, 209u8, 132u8, 0u8], @@ -2315,13 +3063,16 @@ pub mod Simple { impl alloy_sol_types::SolInterface for SimpleCalls { const NAME: &'static str = "SimpleCalls"; const MIN_DATA_LENGTH: usize = 0usize; - const COUNT: usize = 14usize; + const COUNT: usize = 17usize; #[inline] fn selector(&self) -> [u8; 4] { match self { Self::addToArray(_) => ::SELECTOR, Self::arr1(_) => ::SELECTOR, Self::changeMapping(_) => ::SELECTOR, + Self::changeMappingStruct(_) => { + ::SELECTOR + } Self::m1(_) => ::SELECTOR, Self::mappingOfMappings(_) => { ::SELECTOR @@ -2331,7 +3082,13 @@ pub mod Simple { Self::s3(_) => ::SELECTOR, Self::s4(_) => ::SELECTOR, Self::setMapping(_) => ::SELECTOR, + Self::setMappingStruct(_) => { + ::SELECTOR + } Self::setS2(_) => ::SELECTOR, + Self::setSimpleStruct(_) => { + ::SELECTOR + } Self::setSimples(_) => ::SELECTOR, Self::simpleStruct(_) => ::SELECTOR, Self::structMapping(_) => ::SELECTOR, @@ -2387,6 +3144,18 @@ pub mod Simple { } changeMapping }, + { + fn setSimpleStruct( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(SimpleCalls::setSimpleStruct) + } + setSimpleStruct + }, { fn setMapping( data: &[u8], @@ -2418,6 +3187,18 @@ pub mod Simple { } s1 }, + { + fn setMappingStruct( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(SimpleCalls::setMappingStruct) + } + setMappingStruct + }, { fn structMapping( data: &[u8], @@ -2444,6 +3225,18 @@ pub mod Simple { } s3 }, + { + fn changeMappingStruct( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(SimpleCalls::changeMappingStruct) + } + changeMappingStruct + }, { fn s4(data: &[u8], validate: bool) -> alloy_sol_types::Result { ::abi_decode_raw(data, validate) @@ -2501,6 +3294,9 @@ pub mod Simple { Self::changeMapping(inner) => { ::abi_encoded_size(inner) } + Self::changeMappingStruct(inner) => { + ::abi_encoded_size(inner) + } Self::m1(inner) => ::abi_encoded_size(inner), Self::mappingOfMappings(inner) => { ::abi_encoded_size(inner) @@ -2512,9 +3308,15 @@ pub mod Simple { Self::setMapping(inner) => { ::abi_encoded_size(inner) } + Self::setMappingStruct(inner) => { + ::abi_encoded_size(inner) + } Self::setS2(inner) => { ::abi_encoded_size(inner) } + Self::setSimpleStruct(inner) => { + ::abi_encoded_size(inner) + } Self::setSimples(inner) => { ::abi_encoded_size(inner) } @@ -2538,6 +3340,11 @@ pub mod Simple { Self::changeMapping(inner) => { ::abi_encode_raw(inner, out) } + Self::changeMappingStruct(inner) => { + ::abi_encode_raw( + inner, out, + ) + } Self::m1(inner) => ::abi_encode_raw(inner, out), Self::mappingOfMappings(inner) => { ::abi_encode_raw(inner, out) @@ -2549,9 +3356,15 @@ pub mod Simple { Self::setMapping(inner) => { ::abi_encode_raw(inner, out) } + Self::setMappingStruct(inner) => { + ::abi_encode_raw(inner, out) + } Self::setS2(inner) => { ::abi_encode_raw(inner, out) } + Self::setSimpleStruct(inner) => { + ::abi_encode_raw(inner, out) + } Self::setSimples(inner) => { ::abi_encode_raw(inner, out) } @@ -2750,6 +3563,15 @@ pub mod Simple { ) -> alloy_contract::SolCallBuilder { self.call_builder(&changeMappingCall { changes }) } + ///Creates a new call builder for the [`changeMappingStruct`] function. + pub fn changeMappingStruct( + &self, + changes: alloy::sol_types::private::Vec< + ::RustType, + >, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&changeMappingStructCall { changes }) + } ///Creates a new call builder for the [`m1`] function. pub fn m1( &self, @@ -2789,6 +3611,21 @@ pub mod Simple { ) -> alloy_contract::SolCallBuilder { self.call_builder(&setMappingCall { key, value }) } + ///Creates a new call builder for the [`setMappingStruct`] function. + pub fn setMappingStruct( + &self, + _key: alloy::sol_types::private::U256, + _field1: alloy::sol_types::private::U256, + _field2: u128, + _field3: u128, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&setMappingStructCall { + _key, + _field1, + _field2, + _field3, + }) + } ///Creates a new call builder for the [`setS2`] function. pub fn setS2( &self, @@ -2796,6 +3633,19 @@ pub mod Simple { ) -> alloy_contract::SolCallBuilder { self.call_builder(&setS2Call { newS2 }) } + ///Creates a new call builder for the [`setSimpleStruct`] function. + pub fn setSimpleStruct( + &self, + _field1: alloy::sol_types::private::U256, + _field2: u128, + _field3: u128, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&setSimpleStructCall { + _field1, + _field2, + _field3, + }) + } ///Creates a new call builder for the [`setSimples`] function. pub fn setSimples( &self, diff --git a/mp2-v1/tests/common/cases/contract.rs b/mp2-v1/tests/common/cases/contract.rs index c25b3aa44..c321e91b1 100644 --- a/mp2-v1/tests/common/cases/contract.rs +++ b/mp2-v1/tests/common/cases/contract.rs @@ -1,10 +1,22 @@ -use alloy::{primitives::Address, providers::ProviderBuilder}; -use anyhow::Result; -use log::info; - -use crate::common::{bindings::simple::Simple, TestContext}; - -use super::indexing::{SimpleSingleValue, UpdateSimpleStorage}; +use super::{ + indexing::MappingUpdate, + storage_slot_value::{LargeStruct, StorageSlotValue}, + table_source::DEFAULT_ADDRESS, +}; +use crate::common::{ + bindings::simple::{ + Simple, + Simple::{MappingChange, MappingOperation, MappingStructChange}, + }, + TestContext, +}; +use alloy::{ + contract::private::Provider, + primitives::{Address, U256}, + providers::ProviderBuilder, +}; +use itertools::Itertools; +use log::{debug, info}; pub struct Contract { pub address: Address, @@ -12,35 +24,211 @@ pub struct Contract { } impl Contract { - pub async fn current_single_values(&self, ctx: &TestContext) -> Result { + /// Deploy the simple contract. + pub(crate) async fn deploy_simple_contract(ctx: &TestContext) -> Self { + // Create a provider with the wallet for contract deployment and interaction. let provider = ProviderBuilder::new() .with_recommended_fillers() .wallet(ctx.wallet()) .on_http(ctx.rpc_url.parse().unwrap()); - let contract = Simple::new(self.address, &provider); + let contract = Simple::deploy(&provider).await.unwrap(); + let address = *contract.address(); + info!("Deployed Simple contract at address: {address}"); + let chain_id = ctx.rpc.get_chain_id().await.unwrap(); + Self { address, chain_id } + } +} + +/// Common functions for a specific type to interact with the test contract +pub trait ContractController { + /// Get the current values from the contract. + async fn current_values(ctx: &TestContext, contract: &Contract) -> Self; + + /// Update the values to the contract. + async fn update_contract(&self, ctx: &TestContext, contract: &Contract); +} + +/// Single values collection +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SimpleSingleValues { + pub(crate) s1: bool, + pub(crate) s2: U256, + pub(crate) s3: String, + pub(crate) s4: Address, +} + +impl ContractController for SimpleSingleValues { + async fn current_values(ctx: &TestContext, contract: &Contract) -> Self { + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + let contract = Simple::new(contract.address, &provider); - Ok(SimpleSingleValue { + SimpleSingleValues { s1: contract.s1().call().await.unwrap()._0, s2: contract.s2().call().await.unwrap()._0, s3: contract.s3().call().await.unwrap()._0, s4: contract.s4().call().await.unwrap()._0, - }) - } - // Returns the table updated - pub async fn apply_update( - &self, - ctx: &TestContext, - update: &UpdateSimpleStorage, - ) -> Result<()> { + } + } + + async fn update_contract(&self, ctx: &TestContext, contract: &Contract) { let provider = ProviderBuilder::new() .with_recommended_fillers() .wallet(ctx.wallet()) .on_http(ctx.rpc_url.parse().unwrap()); + let simple_contract = Simple::new(contract.address, &provider); + + let call = simple_contract.setSimples(self.s1, self.s2, self.s3.clone(), self.s4); + call.send().await.unwrap().watch().await.unwrap(); + log::info!("Updated simple contract single values"); + // Sanity check + { + let updated = Self::current_values(ctx, contract).await; + assert_eq!(self, &updated); + } + } +} + +impl ContractController for LargeStruct { + async fn current_values(ctx: &TestContext, contract: &Contract) -> Self { + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + let contract = Simple::new(contract.address, &provider); + + contract.simpleStruct().call().await.unwrap().into() + } + + async fn update_contract(&self, ctx: &TestContext, contract: &Contract) { + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + let simple_contract = Simple::new(contract.address, &provider); + + let call = simple_contract.setSimpleStruct(self.field1, self.field2, self.field3); + call.send().await.unwrap().watch().await.unwrap(); + // Sanity check + { + let updated = Self::current_values(ctx, contract).await; + assert_eq!(self, &updated); + } + log::info!("Updated simple contract for LargeStruct"); + } +} + +impl ContractController for Vec> { + async fn current_values(_ctx: &TestContext, _contract: &Contract) -> Self { + unimplemented!("Unimplemented for fetching the all mapping values") + } + + async fn update_contract(&self, ctx: &TestContext, contract: &Contract) { + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + let contract = Simple::new(contract.address, &provider); + + let changes = self + .iter() + .map(|tuple| { + let operation: MappingOperation = tuple.into(); + let operation = operation.into(); + let (key, value) = match tuple { + MappingUpdate::Deletion(k, _) => (*k, *DEFAULT_ADDRESS), + MappingUpdate::Update(k, _, v) | MappingUpdate::Insertion(k, v) => (*k, *v), + }; + MappingChange { + operation, + key, + value, + } + }) + .collect_vec(); + + let call = contract.changeMapping(changes); + call.send().await.unwrap().watch().await.unwrap(); + // Sanity check + for update in self.iter() { + match update { + MappingUpdate::Deletion(k, _) => { + let res = contract.m1(*k).call().await.unwrap(); + let v: U256 = res._0.into_word().into(); + assert_eq!(v, U256::ZERO, "Key deletion is wrong on contract"); + } + MappingUpdate::Insertion(k, v) => { + let res = contract.m1(*k).call().await.unwrap(); + let new_value: U256 = res._0.into_word().into(); + let new_value = Address::from_u256_slice(&[new_value]); + assert_eq!(&new_value, v, "Key insertion is wrong on contract"); + } + MappingUpdate::Update(k, _, v) => { + let res = contract.m1(*k).call().await.unwrap(); + let new_value: U256 = res._0.into_word().into(); + let new_value = Address::from_u256_slice(&[new_value]); + assert_eq!(&new_value, v, "Key update is wrong on contract"); + } + } + } + log::info!("Updated simple contract single values"); + } +} + +impl ContractController for Vec> { + async fn current_values(_ctx: &TestContext, _contract: &Contract) -> Self { + unimplemented!("Unimplemented for fetching the all mapping values") + } + + async fn update_contract(&self, ctx: &TestContext, contract: &Contract) { + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + let contract = Simple::new(contract.address, &provider); + + let changes = self + .iter() + .map(|tuple| { + let operation: MappingOperation = tuple.into(); + let operation = operation.into(); + let (key, field1, field2, field3) = match tuple { + MappingUpdate::Insertion(k, v) + | MappingUpdate::Deletion(k, v) + | MappingUpdate::Update(k, _, v) => (*k, v.field1, v.field2, v.field3), + }; + MappingStructChange { + operation, + key, + field1, + field2, + field3, + } + }) + .collect_vec(); - let contract = Simple::new(self.address, &provider); - update.apply_to(&contract).await; - info!("Updated contract with new values {:?}", update); - Ok(()) + let call = contract.changeMappingStruct(changes); + call.send().await.unwrap().watch().await.unwrap(); + // Sanity check + for update in self.iter() { + match update { + MappingUpdate::Deletion(k, _) => { + let res = contract.structMapping(*k).call().await.unwrap(); + assert_eq!( + LargeStruct::from(res), + LargeStruct::new(U256::from(0), 0, 0) + ); + } + MappingUpdate::Insertion(k, v) | MappingUpdate::Update(k, _, v) => { + let res = contract.structMapping(*k).call().await.unwrap(); + debug!("Set mapping struct: key = {k}, value = {v:?}"); + assert_eq!(&LargeStruct::from(res), v); + } + } + } + log::info!("Updated simple contract for mapping values of LargeStruct"); } } diff --git a/mp2-v1/tests/common/cases/indexing.rs b/mp2-v1/tests/common/cases/indexing.rs index a51d4046b..790e85345 100644 --- a/mp2-v1/tests/common/cases/indexing.rs +++ b/mp2-v1/tests/common/cases/indexing.rs @@ -1,6 +1,8 @@ //! Test case for local Simple contract //! Reference `test-contracts/src/Simple.sol` for the details of Simple contract. +use anyhow::Result; +use itertools::Itertools; use log::{debug, info}; use mp2_v1::{ api::SlotInput, @@ -11,44 +13,39 @@ use mp2_v1::{ row::{CellCollection, CellInfo, Row, RowTreeKey}, ColumnID, }, - values_extraction::{identifier_block_column, identifier_for_value_column}, + values_extraction::{ + gadgets::column_info::ColumnInfo, identifier_block_column, identifier_for_value_column, + }, }; +use rand::{thread_rng, Rng}; use ryhope::storage::RoEpochKvStorage; use crate::common::{ - bindings::simple::Simple::{self, MappingChange, MappingOperation}, + bindings::simple::Simple::MappingOperation, cases::{ contract::Contract, identifier_for_mapping_key_column, + storage_slot_value::LargeStruct, table_source::{ - single_var_slot_info, LengthExtractionArgs, MappingIndex, MappingValuesExtractionArgs, - MergeSource, SingleValuesExtractionArgs, DEFAULT_ADDRESS, + LengthExtractionArgs, MappingExtractionArgs, MappingIndex, MergeSource, + SingleExtractionArgs, }, }, proof_storage::{ProofKey, ProofStorage}, rowtree::SecondaryIndexCell, table::{ - CellsUpdate, IndexType, IndexUpdate, Table, TableColumn, TableColumns, TreeRowUpdate, - TreeUpdateType, + CellsUpdate, IndexType, IndexUpdate, Table, TableColumn, TableColumns, TableRowUniqueID, + TreeRowUpdate, TreeUpdateType, }, TableInfo, TestContext, }; -use super::{ - super::bindings::simple::Simple::SimpleInstance, ContractExtractionArgs, TableIndexing, - TableSource, -}; -use alloy::{ - contract::private::{Network, Provider, Transport}, - primitives::{Address, U256}, - providers::ProviderBuilder, -}; +use super::{ContractExtractionArgs, TableIndexing, TableSource}; +use alloy::primitives::U256; use mp2_common::{eth::StorageSlot, proof::ProofWithVK, types::HashOutput}; /// Test slots for single values extraction -const SINGLE_SLOTS: [u8; 4] = [0, 1, 2, 3]; -/// Define which slots is the secondary index. In this case, it's the U256 -const INDEX_SLOT: u8 = 1; +pub(crate) const SINGLE_SLOTS: [u8; 4] = [0, 1, 2, 3]; /// Test slot for mapping values extraction const MAPPING_SLOT: u8 = 4; @@ -62,150 +59,187 @@ const LENGTH_VALUE: u8 = 2; /// Test slot for contract extraction const CONTRACT_SLOT: usize = 1; -/// Test slot for single Struct extractin -#[allow(dead_code)] -const SINGLE_STRUCT_SLOT: usize = 6; +/// Test slot for single Struct extraction +pub(crate) const SINGLE_STRUCT_SLOT: usize = 6; /// Test slot for mapping Struct extraction -#[allow(dead_code)] -const MAPPING_STRUCT_SLOT: usize = 7; +const MAPPING_STRUCT_SLOT: usize = 8; /// Test slot for mapping of mappings extraction #[allow(dead_code)] -const MAPPING_OF_MAPPINGS_SLOT: usize = 8; +const MAPPING_OF_MAPPINGS_SLOT: usize = 9; /// human friendly name about the column containing the block number pub(crate) const BLOCK_COLUMN_NAME: &str = "block_number"; -pub(crate) const MAPPING_VALUE_COLUMN: &str = "map_value"; -pub(crate) const MAPPING_KEY_COLUMN: &str = "map_key"; +pub(crate) const SINGLE_SECONDARY_COLUMN: &str = "single_secondary_column"; +pub(crate) const MAPPING_KEY_COLUMN: &str = "mapping_key_column"; +pub(crate) const MAPPING_VALUE_COLUMN: &str = "mapping_value_column"; + +/// Construct the all slot inputs for single value testing. +fn single_value_slot_inputs() -> Vec { + let mut slot_inputs = SINGLE_SLOTS + .map(|slot| SlotInput::new(slot, 0, 256, 0)) + .to_vec(); + + // Add the Struct single slots. + let struct_slots = LargeStruct::slot_inputs(SINGLE_STRUCT_SLOT as u8); + slot_inputs.extend(struct_slots); + + slot_inputs +} impl TableIndexing { pub(crate) async fn merge_table_test_case( ctx: &mut TestContext, - ) -> anyhow::Result<(Self, Vec>)> { - // Create a provider with the wallet for contract deployment and interaction. - let provider = ProviderBuilder::new() - .with_recommended_fillers() - .wallet(ctx.wallet()) - .on_http(ctx.rpc_url.parse().unwrap()); - - let contract = Simple::deploy(&provider).await.unwrap(); - info!( - "Deployed Simple contract at address: {}", - contract.address() - ); - let contract_address = contract.address(); - let chain_id = ctx.rpc.get_chain_id().await.unwrap(); - let contract = Contract { - address: *contract_address, - chain_id, - }; - let single_source = SingleValuesExtractionArgs { - // this test puts the mapping value as secondary index so there is no index for the - // single variable slots. - index_slot: None, - slots: single_var_slot_info(contract_address, chain_id), - }; - // to toggle off and on - let value_as_index = true; - let slot_input = SlotInput::new( - MAPPING_SLOT, - // byte_offset - 0, - // bit_offset - 0, - // length - 0, - // evm_word - 0, - ); - let value_id = identifier_for_value_column(&slot_input, contract_address, chain_id, vec![]); - let key_id = - identifier_for_mapping_key_column(MAPPING_SLOT, contract_address, chain_id, vec![]); - let (mapping_index_id, mapping_index, mapping_cell_id) = match value_as_index { - true => (value_id, MappingIndex::Value(value_id), key_id), - false => (key_id, MappingIndex::Key(key_id), value_id), - }; + ) -> Result<(Self, Vec>)> { + // Deploy the simple contract. + let contract = Contract::deploy_simple_contract(ctx).await; + let contract_address = contract.address; + let chain_id = contract.chain_id; - let mapping_source = MappingValuesExtractionArgs { - slot: MAPPING_SLOT, - index: mapping_index, - // at the beginning there is no mapping key inserted - // NOTE: This array is a convenience to handle smart contract updates - // manually, but does not need to be stored explicitely by dist system. - mapping_keys: vec![], + // This test puts the mapping value as secondary index so there is no index for the + // single variable slots. + let single_source = { + let slot_inputs = single_value_slot_inputs(); + SingleExtractionArgs::new(None, slot_inputs) }; - let mut source = TableSource::Merge(MergeSource::new(single_source, mapping_source)); - let genesis_change = source.init_contract_data(ctx, &contract).await; - let single_columns = SINGLE_SLOTS + let single_columns = single_source + .slot_inputs .iter() .enumerate() - .map(|(i, slot)| { - let slot_input = SlotInput::new( - *slot, // byte_offset - 0, // bit_offset - 0, // length - 0, // evm_word - 0, - ); + .map(|(i, slot_input)| { let identifier = - identifier_for_value_column(&slot_input, contract_address, chain_id, vec![]); + identifier_for_value_column(slot_input, &contract_address, chain_id, vec![]); + let info = ColumnInfo::new_from_slot_input(identifier, slot_input); TableColumn { - name: format!("column_{}", i), - identifier, + name: format!("single_column_{i}"), index: IndexType::None, // ALL single columns are "multiplier" since we do tableA * D(tableB), i.e. all // entries of table A are repeated for each entry of table B. multiplier: true, + info, } }) - .collect::>(); - let mapping_column = vec![TableColumn { - name: if value_as_index { - MAPPING_KEY_COLUMN - } else { - MAPPING_VALUE_COLUMN - } - .to_string(), - identifier: mapping_cell_id, - index: IndexType::None, - // here is it important to specify false to mean that the entries of table B are - // not repeated. - multiplier: false, - }]; - let value_column = mapping_column[0].name.clone(); - let all_columns = [single_columns.as_slice(), mapping_column.as_slice()].concat(); + .collect_vec(); + let (mapping_secondary_column, mapping_rest_columns, row_unique_id, mapping_source) = { + let mut slot_inputs = LargeStruct::slot_inputs(MAPPING_STRUCT_SLOT as u8); + let key_id = identifier_for_mapping_key_column( + MAPPING_STRUCT_SLOT as u8, + &contract_address, + chain_id, + vec![], + ); + let mut value_ids = slot_inputs + .iter() + .map(|slot_input| { + identifier_for_value_column(slot_input, &contract_address, chain_id, vec![]) + }) + .collect_vec(); + // Switch the test index. + // let mapping_index = MappingIndex::Value(value_ids(1)); + let mapping_index = MappingIndex::OuterKey(key_id); + let source = MappingExtractionArgs::new( + MAPPING_STRUCT_SLOT as u8, + mapping_index.clone(), + slot_inputs.clone(), + ); + // Construct the table columns. + let (secondary_column, rest_columns) = match mapping_index { + MappingIndex::OuterKey(_) => { + let secondary_column = TableColumn { + name: MAPPING_KEY_COLUMN.to_string(), + index: IndexType::Secondary, + multiplier: false, + info: ColumnInfo::new_from_slot_input( + key_id, + // The slot input is useless for the key column. + &slot_inputs[0], + ), + }; + let rest_columns = value_ids + .into_iter() + .zip(slot_inputs.iter()) + .enumerate() + .map(|(i, (id, slot_input))| TableColumn { + name: format!("{MAPPING_VALUE_COLUMN}_{i}"), + index: IndexType::None, + multiplier: false, + info: ColumnInfo::new_from_slot_input(id, slot_input), + }) + .collect_vec(); + + (secondary_column, rest_columns) + } + MappingIndex::Value(secondary_value_id) => { + let pos = value_ids + .iter() + .position(|id| id == &secondary_value_id) + .unwrap(); + let secondary_id = value_ids.remove(pos); + let secondary_slot_input = slot_inputs.remove(pos); + let secondary_column = TableColumn { + name: MAPPING_VALUE_COLUMN.to_string(), + index: IndexType::Secondary, + multiplier: false, + info: ColumnInfo::new_from_slot_input(secondary_id, &secondary_slot_input), + }; + let mut rest_columns = value_ids + .into_iter() + .zip(slot_inputs.iter()) + .enumerate() + .map(|(i, (id, slot_input))| TableColumn { + name: format!("{MAPPING_VALUE_COLUMN}_{i}"), + index: IndexType::None, + multiplier: false, + info: ColumnInfo::new_from_slot_input(id, slot_input), + }) + .collect_vec(); + rest_columns.push(TableColumn { + name: MAPPING_KEY_COLUMN.to_string(), + index: IndexType::None, + multiplier: false, + // The slot input is useless for the key column. + info: ColumnInfo::new_from_slot_input(key_id, &slot_inputs[0]), + }); + + (secondary_column, rest_columns) + } + _ => unreachable!(), + }; + let row_unique_id = TableRowUniqueID::Mapping(key_id); + + (secondary_column, rest_columns, row_unique_id, source) + }; + let mut source = TableSource::Merge(MergeSource::new(single_source, mapping_source)); + let genesis_change = source.init_contract_data(ctx, &contract).await; + let value_column = mapping_rest_columns[0].name.clone(); + let all_columns = [single_columns.as_slice(), &mapping_rest_columns].concat(); let columns = TableColumns { primary: TableColumn { name: BLOCK_COLUMN_NAME.to_string(), - identifier: identifier_block_column(), index: IndexType::Primary, // it doesn't matter for this one since block is "outside" of the table definition // really, it is a special column we add multiplier: true, + // Only valid for the identifier of block column, others are dummy. + info: ColumnInfo::new(0, identifier_block_column(), 0, 0, 0, 0), }, - secondary: TableColumn { - name: if value_as_index { - MAPPING_VALUE_COLUMN - } else { - MAPPING_KEY_COLUMN - } - .to_string(), - identifier: mapping_index_id, - index: IndexType::Secondary, - // here is it important to specify false to mean that the entries of table B are - // not repeated. - multiplier: false, - }, + secondary: mapping_secondary_column, rest: all_columns, }; - println!( + info!( "Table information:\n{}\n", serde_json::to_string_pretty(&columns)? ); let indexing_genesis_block = ctx.block_number().await; - let table = Table::new(indexing_genesis_block, "merged_table".to_string(), columns).await; + let table = Table::new( + indexing_genesis_block, + "merged_table".to_string(), + columns, + row_unique_id, + ) + .await; Ok(( Self { value_column, @@ -220,97 +254,86 @@ impl TableIndexing { )) } + /// The single value test case includes the all single value slots and one single Struct slot. pub(crate) async fn single_value_test_case( ctx: &mut TestContext, - ) -> anyhow::Result<(Self, Vec>)> { - // Create a provider with the wallet for contract deployment and interaction. - let provider = ProviderBuilder::new() - .with_recommended_fillers() - .wallet(ctx.wallet()) - .on_http(ctx.rpc_url.parse().unwrap()); - - let contract = Simple::deploy(&provider).await.unwrap(); - info!( - "Deployed Simple contract at address: {}", - contract.address() - ); - let contract_address = contract.address(); - let chain_id = ctx.rpc.get_chain_id().await.unwrap(); - let contract = Contract { - address: *contract_address, - chain_id, - }; + ) -> Result<(Self, Vec>)> { + let rng = &mut thread_rng(); - let mut source = TableSource::SingleValues(SingleValuesExtractionArgs { - index_slot: Some(INDEX_SLOT), - slots: single_var_slot_info(contract_address, chain_id), - }); - let genesis_updates = source.init_contract_data(ctx, &contract).await; + // Deploy the simple contract. + let contract = Contract::deploy_simple_contract(ctx).await; + let contract_address = contract.address; + let chain_id = contract.chain_id; + let mut source = { + let slot_inputs = single_value_slot_inputs(); + let secondary_index = rng.gen_range(0..slot_inputs.len()); + SingleExtractionArgs::new(Some(secondary_index), slot_inputs) + }; + let genesis_updates = source.init_contract_data(ctx, &contract).await; let indexing_genesis_block = ctx.block_number().await; + let secondary_index_slot_input = source.secondary_index_slot_input().unwrap(); + let rest_column_slot_inputs = source.rest_column_slot_inputs(); + let source = TableSource::Single(source); + // Defining the columns structure of the table from the source slots - // This is depending on what is our data source, mappings and CSV both have their o + // This is depending on what is our data source, mappings and CSV both have their // own way of defining their table. let columns = TableColumns { primary: TableColumn { name: BLOCK_COLUMN_NAME.to_string(), - identifier: identifier_block_column(), index: IndexType::Primary, multiplier: false, + // Only valid for the identifier of block column, others are dummy. + info: ColumnInfo::new(0, identifier_block_column(), 0, 0, 0, 0), }, secondary: TableColumn { - name: "column_value".to_string(), - identifier: identifier_for_value_column( - &SlotInput::new( - INDEX_SLOT, // byte_offset - 0, // bit_offset - 0, // length - 0, // evm_word - 0, - ), - contract_address, - chain_id, - vec![], - ), + name: SINGLE_SECONDARY_COLUMN.to_string(), index: IndexType::Secondary, // here we put false always since these are not coming from a "merged" table multiplier: false, + info: ColumnInfo::new_from_slot_input( + identifier_for_value_column( + &secondary_index_slot_input, + &contract_address, + chain_id, + vec![], + ), + &secondary_index_slot_input, + ), }, - rest: SINGLE_SLOTS + rest: rest_column_slot_inputs .iter() .enumerate() - .filter_map(|(i, slot)| match i { - _ if *slot == INDEX_SLOT => None, - _ => { - let slot_input = SlotInput::new( - *slot, // byte_offset - 0, // bit_offset - 0, // length - 0, // evm_word - 0, - ); - let identifier = identifier_for_value_column( - &slot_input, - contract_address, - chain_id, - vec![], - ); - Some(TableColumn { - name: format!("column_{}", i), - identifier, - index: IndexType::None, - // here we put false always since these are not coming from a "merged" table - multiplier: false, - }) + .map(|(i, slot_input)| { + let identifier = identifier_for_value_column( + slot_input, + &contract_address, + chain_id, + vec![], + ); + let info = ColumnInfo::new_from_slot_input(identifier, slot_input); + TableColumn { + name: format!("rest_column_{i}"), + index: IndexType::None, + multiplier: false, + info, } }) - .collect::>(), + .collect_vec(), }; - let table = Table::new(indexing_genesis_block, "single_table".to_string(), columns).await; + let row_unique_id = TableRowUniqueID::Single; + let table = Table::new( + indexing_genesis_block, + "single_table".to_string(), + columns, + row_unique_id, + ) + .await; Ok(( Self { value_column: "".to_string(), - source: source.clone(), + source, table, contract, contract_extraction: ContractExtractionArgs { @@ -321,105 +344,96 @@ impl TableIndexing { )) } - pub(crate) async fn mapping_test_case( + /// The test case for mapping of single values + pub(crate) async fn mapping_value_test_case( ctx: &mut TestContext, - ) -> anyhow::Result<(Self, Vec>)> { - // Create a provider with the wallet for contract deployment and interaction. - let provider = ProviderBuilder::new() - .with_recommended_fillers() - .wallet(ctx.wallet()) - .on_http(ctx.rpc_url.parse().unwrap()); - - let contract = Simple::deploy(&provider).await.unwrap(); - info!( - "Deployed MAPPING Simple contract at address: {}", - contract.address() - ); - let contract_address = contract.address(); - let chain_id = ctx.rpc.get_chain_id().await.unwrap(); - // to toggle off and on - let value_as_index = true; - let slot_input = SlotInput::new( + ) -> Result<(Self, Vec>)> { + // Deploy the simple contract. + let contract = Contract::deploy_simple_contract(ctx).await; + let contract_address = contract.address; + let chain_id = contract.chain_id; + + let slot_input = SlotInput::new(MAPPING_SLOT, 0, 256, 0); + let key_id = + identifier_for_mapping_key_column(MAPPING_SLOT, &contract_address, chain_id, vec![]); + let value_id = + identifier_for_value_column(&slot_input, &contract_address, chain_id, vec![]); + // Switch the test index. + // let mapping_index = MappingIndex::Value(value_id); + let mapping_index = MappingIndex::OuterKey(key_id); + let args = MappingExtractionArgs::new( MAPPING_SLOT, - // byte_offset - 0, - // bit_offset - 0, - // length - 0, - // evm_word - 0, + mapping_index.clone(), + vec![slot_input.clone()], ); - let value_id = identifier_for_value_column(&slot_input, contract_address, chain_id, vec![]); - let key_id = - identifier_for_mapping_key_column(MAPPING_SLOT, contract_address, chain_id, vec![]); - let (index_identifier, mapping_index, cell_identifier) = match value_as_index { - true => (value_id, MappingIndex::Value(value_id), key_id), - false => (key_id, MappingIndex::Key(key_id), value_id), - }; - - // mapping(uint256 => address) public m1 - let mapping_args = MappingValuesExtractionArgs { - slot: MAPPING_SLOT, - index: mapping_index, - // at the beginning there is no mapping key inserted - // NOTE: This array is a convenience to handle smart contract updates - // manually, but does not need to be stored explicitely by dist system. - mapping_keys: vec![], - }; - - let mut source = TableSource::Mapping(( - mapping_args, + let mut source = TableSource::MappingValues( + args, Some(LengthExtractionArgs { slot: LENGTH_SLOT, value: LENGTH_VALUE, }), - )); - let contract = Contract { - address: *contract_address, - chain_id, - }; - + ); let table_row_updates = source.init_contract_data(ctx, &contract).await; - // Defining the columns structure of the table from the source slots - // This is depending on what is our data source, mappings and CSV both have their o - // own way of defining their table. - let columns = TableColumns { - primary: TableColumn { - name: BLOCK_COLUMN_NAME.to_string(), - identifier: identifier_block_column(), - index: IndexType::Primary, - multiplier: false, - }, - secondary: TableColumn { - name: if value_as_index { - MAPPING_VALUE_COLUMN - } else { - MAPPING_KEY_COLUMN - } - .to_string(), - identifier: index_identifier, - index: IndexType::Secondary, - // here important to put false since these are not coming from any "merged" table - multiplier: false, + + let table = build_mapping_table( + ctx, + &mapping_index, + key_id, + vec![value_id], + vec![slot_input], + ) + .await; + let value_column = table.columns.rest[0].name.clone(); + + Ok(( + Self { + value_column, + contract_extraction: ContractExtractionArgs { + slot: StorageSlot::Simple(CONTRACT_SLOT), + }, + contract, + source, + table, }, - rest: vec![TableColumn { - name: if value_as_index { - MAPPING_KEY_COLUMN - } else { - MAPPING_VALUE_COLUMN - } - .to_string(), - identifier: cell_identifier, - index: IndexType::None, - // here important to put false since these are not coming from any "merged" table - multiplier: false, - }], - }; - let value_column = columns.rest[0].name.clone(); - debug!("MAPPING ZK COLUMNS -> {:?}", columns); - let index_genesis_block = ctx.block_number().await; - let table = Table::new(index_genesis_block, "mapping_table".to_string(), columns).await; + table_row_updates, + )) + } + + /// The test case for mapping of Struct values + pub(crate) async fn mapping_struct_test_case( + ctx: &mut TestContext, + ) -> Result<(Self, Vec>)> { + // Deploy the simple contract. + let contract = Contract::deploy_simple_contract(ctx).await; + let contract_address = contract.address; + let chain_id = contract.chain_id; + + let slot_inputs = LargeStruct::slot_inputs(MAPPING_STRUCT_SLOT as u8); + let key_id = identifier_for_mapping_key_column( + MAPPING_STRUCT_SLOT as u8, + &contract_address, + chain_id, + vec![], + ); + let value_ids = slot_inputs + .iter() + .map(|slot_input| { + identifier_for_value_column(slot_input, &contract_address, chain_id, vec![]) + }) + .collect_vec(); + // Switch the test index. + // let mapping_index = MappingIndex::OuterKey(key_id); + let mapping_index = MappingIndex::Value(value_ids[1]); + let args = MappingExtractionArgs::new( + MAPPING_STRUCT_SLOT as u8, + mapping_index.clone(), + slot_inputs.clone(), + ); + let mut source = TableSource::MappingStruct(args, None); + let table_row_updates = source.init_contract_data(ctx, &contract).await; + + let table = build_mapping_table(ctx, &mapping_index, key_id, value_ids, slot_inputs).await; + let value_column = table.columns.rest[0].name.clone(); Ok(( Self { @@ -458,6 +472,9 @@ impl TableIndexing { .source .random_contract_update(ctx, &self.contract, ut) .await; + if table_row_updates.is_empty() { + continue; + } let bn = ctx.block_number().await as BlockPrimaryIndex; log::info!("Applying follow up updates to contract done - now at block {bn}",); // we first run the initial preprocessing and db creation. @@ -512,7 +529,7 @@ impl TableIndexing { false => Row::default(), }; let new_cell_collection = row_update.updated_cells_collection( - self.table.columns.secondary_column().identifier, + self.table.columns.secondary_column().identifier(), bn, &previous_row.payload.cells, ); @@ -545,7 +562,7 @@ impl TableIndexing { .await? .expect("unable to find previous row"); let new_cell_collection = row_update.updated_cells_collection( - self.table.columns.secondary_column().identifier, + self.table.columns.secondary_column().identifier(), bn, &old_row.cells, ); @@ -698,6 +715,7 @@ impl TableIndexing { .source .generate_extraction_proof_inputs(ctx, &self.contract, value_key) .await?; + // no need to generate it if it's already present if ctx.storage.get_proof_exact(&final_key).is_err() { let proof = ctx @@ -714,27 +732,116 @@ impl TableIndexing { } } -#[derive(Clone, Debug)] -pub enum UpdateSimpleStorage { - Single(SimpleSingleValue), - Mapping(Vec), +/// Build the mapping table. +async fn build_mapping_table( + ctx: &TestContext, + mapping_index: &MappingIndex, + key_id: u64, + mut value_ids: Vec, + mut slot_inputs: Vec, +) -> Table { + // Construct the table columns. + let (secondary_column, rest_columns) = match mapping_index { + MappingIndex::OuterKey(_) => { + let secondary_column = TableColumn { + name: MAPPING_KEY_COLUMN.to_string(), + index: IndexType::Secondary, + multiplier: false, + info: ColumnInfo::new_from_slot_input( + key_id, + // The slot input is useless for the key column. + &slot_inputs[0], + ), + }; + let rest_columns = value_ids + .into_iter() + .zip(slot_inputs.iter()) + .enumerate() + .map(|(i, (id, slot_input))| TableColumn { + name: format!("{MAPPING_VALUE_COLUMN}_{i}"), + index: IndexType::None, + multiplier: false, + info: ColumnInfo::new_from_slot_input(id, slot_input), + }) + .collect_vec(); + + (secondary_column, rest_columns) + } + MappingIndex::Value(secondary_value_id) => { + let pos = value_ids + .iter() + .position(|id| id == secondary_value_id) + .unwrap(); + let secondary_id = value_ids.remove(pos); + let secondary_slot_input = slot_inputs.remove(pos); + let secondary_column = TableColumn { + name: MAPPING_VALUE_COLUMN.to_string(), + index: IndexType::Secondary, + multiplier: false, + info: ColumnInfo::new_from_slot_input(secondary_id, &secondary_slot_input), + }; + let mut rest_columns = value_ids + .into_iter() + .zip(slot_inputs.iter()) + .enumerate() + .map(|(i, (id, slot_input))| TableColumn { + name: format!("{MAPPING_VALUE_COLUMN}_{i}"), + index: IndexType::None, + multiplier: false, + info: ColumnInfo::new_from_slot_input(id, slot_input), + }) + .collect_vec(); + rest_columns.push(TableColumn { + name: MAPPING_KEY_COLUMN.to_string(), + index: IndexType::None, + multiplier: false, + // The slot input is useless for the key column. + info: ColumnInfo::new_from_slot_input(key_id, &Default::default()), + }); + + (secondary_column, rest_columns) + } + _ => unreachable!(), + }; + // Defining the columns structure of the table from the source slots + // This is depending on what is our data source, mappings and CSV both have their o + // own way of defining their table. + let columns = TableColumns { + primary: TableColumn { + name: BLOCK_COLUMN_NAME.to_string(), + index: IndexType::Primary, + multiplier: false, + // Only valid for the identifier of block column, others are dummy. + info: ColumnInfo::new(0, identifier_block_column(), 0, 0, 0, 0), + }, + secondary: secondary_column, + rest: rest_columns, + }; + debug!("MAPPING ZK COLUMNS -> {:?}", columns); + let index_genesis_block = ctx.block_number().await; + let row_unique_id = TableRowUniqueID::Mapping(key_id); + Table::new( + index_genesis_block, + "mapping_table".to_string(), + columns, + row_unique_id, + ) + .await } -/// Represents the update that can come from the chain #[derive(Clone, Debug)] -pub enum MappingUpdate { - // key, value - Deletion(U256, U256), - // key, previous_value, new_value - Update(U256, U256, U256), - // key, value - Insertion(U256, U256), +pub enum MappingUpdate { + // key and value + Insertion(U256, V), + // key and value + Deletion(U256, V), + // key, previous value and new value + Update(U256, V, V), } -/// passing form the rust type to the solidity type -impl From<&MappingUpdate> for MappingOperation { - fn from(value: &MappingUpdate) -> Self { - Self::from(match value { +impl From<&MappingUpdate> for MappingOperation { + fn from(update: &MappingUpdate) -> Self { + Self::from(match update { MappingUpdate::Deletion(_, _) => 0, MappingUpdate::Update(_, _, _) => 1, MappingUpdate::Insertion(_, _) => 2, @@ -742,93 +849,6 @@ impl From<&MappingUpdate> for MappingOperation { } } -#[derive(Clone, Debug)] -pub struct SimpleSingleValue { - pub(crate) s1: bool, - pub(crate) s2: U256, - pub(crate) s3: String, - pub(crate) s4: Address, -} - -impl UpdateSimpleStorage { - // This function applies the update in _one_ transaction so that Anvil only moves by one block - // so we can test the "subsequent block" - pub async fn apply_to, N: Network>( - &self, - contract: &SimpleInstance, - ) { - match self { - UpdateSimpleStorage::Single(ref single) => { - Self::update_single_values(contract, single).await - } - UpdateSimpleStorage::Mapping(ref updates) => { - Self::update_mapping_values(contract, updates).await - } - } - } - - async fn update_single_values, N: Network>( - contract: &SimpleInstance, - values: &SimpleSingleValue, - ) { - let b = contract.setSimples(values.s1, values.s2, values.s3.clone(), values.s4); - b.send().await.unwrap().watch().await.unwrap(); - log::info!("Updated simple contract single values"); - } - - async fn update_mapping_values, N: Network>( - contract: &SimpleInstance, - values: &[MappingUpdate], - ) { - let contract_changes = values - .iter() - .map(|tuple| { - let op: MappingOperation = tuple.into(); - let (k, v) = match tuple { - MappingUpdate::Deletion(k, _) => (*k, *DEFAULT_ADDRESS), - MappingUpdate::Update(k, _, v) | MappingUpdate::Insertion(k, v) => { - (*k, Address::from_slice(&v.to_be_bytes_trimmed_vec())) - } - }; - MappingChange { - key: k, - value: v, - operation: op.into(), - } - }) - .collect::>(); - - let b = contract.changeMapping(contract_changes); - b.send().await.unwrap().watch().await.unwrap(); - { - // sanity check - for op in values { - match op { - MappingUpdate::Deletion(k, _) => { - let res = contract.m1(*k).call().await.unwrap(); - let vu: U256 = res._0.into_word().into(); - let is_correct = vu == U256::from(0); - assert!(is_correct, "key deletion not correct on contract"); - } - MappingUpdate::Insertion(k, v) => { - let res = contract.m1(*k).call().await.unwrap(); - let newv: U256 = res._0.into_word().into(); - let is_correct = newv == *v; - assert!(is_correct, "key insertion not correct on contract"); - } - MappingUpdate::Update(k, _, v) => { - let res = contract.m1(*k).call().await.unwrap(); - let newv: U256 = res._0.into_word().into(); - let is_correct = newv == *v; - assert!(is_correct, "KEY Updated, new value valid ? {is_correct}"); - } - } - } - } - log::info!("Updated simple contract single values"); - } -} - #[derive(Clone, Debug)] pub enum ChangeType { Deletion, @@ -1006,6 +1026,7 @@ impl TableIndexing { columns: self.table.columns.clone(), contract_address: self.contract.address, source: self.source.clone(), + row_unique_id: self.table.row_unique_id.clone(), } } } diff --git a/mp2-v1/tests/common/cases/mod.rs b/mp2-v1/tests/common/cases/mod.rs index b25f8cd3d..fae2642df 100644 --- a/mp2-v1/tests/common/cases/mod.rs +++ b/mp2-v1/tests/common/cases/mod.rs @@ -9,6 +9,7 @@ use super::table::Table; pub mod contract; pub mod indexing; pub mod query; +pub mod storage_slot_value; pub mod table_source; /// Test case definition diff --git a/mp2-v1/tests/common/cases/query/mod.rs b/mp2-v1/tests/common/cases/query/mod.rs index 7887e635d..0f982f7a0 100644 --- a/mp2-v1/tests/common/cases/query/mod.rs +++ b/mp2-v1/tests/common/cases/query/mod.rs @@ -105,7 +105,9 @@ pub(crate) struct QueryPlanner<'a> { pub async fn test_query(ctx: &mut TestContext, table: Table, t: TableInfo) -> Result<()> { match &t.source { - TableSource::Mapping(_) | TableSource::Merge(_) => query_mapping(ctx, &table, &t).await?, + TableSource::MappingValues(_, _) + | TableSource::Merge(_) + | TableSource::MappingStruct(_, _) => query_mapping(ctx, &table, &t).await?, _ => unimplemented!("yet"), } Ok(()) diff --git a/mp2-v1/tests/common/cases/query/simple_select_queries.rs b/mp2-v1/tests/common/cases/query/simple_select_queries.rs index 6531a2117..18a4d9804 100644 --- a/mp2-v1/tests/common/cases/query/simple_select_queries.rs +++ b/mp2-v1/tests/common/cases/query/simple_select_queries.rs @@ -262,7 +262,7 @@ pub(crate) async fn prove_single_row Self; + + /// Update the slot input specified field to a random value. + fn random_update(&mut self, slot_input_to_update: &SlotInput); + + /// Convert from an Uint256 vector. + fn from_u256_slice(u: &[U256]) -> Self; + + /// Convert into an Uint256 vector. + fn to_u256_vec(&self) -> Vec; + + /// Construct a storage slot for a mapping entry. + fn mapping_storage_slot(slot: u8, evm_word: u32, mapping_key: Vec) -> StorageSlot; +} + +impl StorageSlotValue for Address { + fn sample() -> Self { + Address::random() + } + fn random_update(&mut self, _: &SlotInput) { + loop { + let new_addr = Self::sample(); + if &new_addr != self { + *self = new_addr; + break; + } + warn!("Generated the same address"); + } + } + fn from_u256_slice(u: &[U256]) -> Self { + assert_eq!(u.len(), 1, "Must convert from one U256"); + + Address::from_slice(&u[0].to_be_bytes_trimmed_vec()) + } + fn to_u256_vec(&self) -> Vec { + vec![U256::from_be_slice(self.as_ref())] + } + fn mapping_storage_slot(slot: u8, evm_word: u32, mapping_key: Vec) -> StorageSlot { + // It should be a mapping single value slot if the value is an Uint256. + assert_eq!(evm_word, 0); + + StorageSlot::Mapping(mapping_key, slot as usize) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)] +pub struct LargeStruct { + pub(crate) field1: U256, + pub(crate) field2: u128, + pub(crate) field3: u128, +} + +impl StorageSlotValue for LargeStruct { + fn sample() -> Self { + let rng = &mut thread_rng(); + let field1 = U256::from(rng.gen::()); // sample a u128 to avoid overflows in queries + let [field2, field3] = array::from_fn(|_| rng.gen()); + + Self { + field1, + field2, + field3, + } + } + fn random_update(&mut self, slot_input_to_update: &SlotInput) { + let field_index = LargeStruct::slot_inputs(slot_input_to_update.slot()) + .iter() + .position(|slot_input| slot_input == slot_input_to_update) + .unwrap(); + let rng = &mut thread_rng(); + let diff = rng.gen_range(1..100); + if field_index == 0 { + self.field1 += U256::from(diff); + } else if field_index == 1 { + self.field2 += diff; + } else if field_index == 2 { + self.field3 += diff; + } else { + panic!("Wrong Struct field index"); + } + } + fn from_u256_slice(u: &[U256]) -> Self { + assert_eq!(u.len(), 3, "Must convert from three U256 for LargeStruct"); + + let field1 = u[0]; + let field2 = u[1].to(); + let field3 = u[2].to(); + + Self { + field1, + field2, + field3, + } + } + fn to_u256_vec(&self) -> Vec { + let [field2, field3] = [self.field2, self.field3].map(U256::from); + vec![self.field1, field2, field3] + } + fn mapping_storage_slot(slot: u8, evm_word: u32, mapping_key: Vec) -> StorageSlot { + // Check if the EVM word must be included. + assert!(Self::slot_inputs(slot) + .iter() + .any(|slot_input| slot_input.evm_word() == evm_word)); + + let parent_slot = StorageSlot::Mapping(mapping_key, slot as usize); + StorageSlot::Node(StorageSlotNode::new_struct(parent_slot, evm_word)) + } +} + +impl LargeStruct { + pub const FIELD_NUM: usize = 3; + + pub fn new(field1: U256, field2: u128, field3: u128) -> Self { + Self { + field1, + field2, + field3, + } + } + + pub fn slot_inputs(slot: u8) -> Vec { + vec![ + SlotInput::new(slot, 0, 256, 0), + // Big-endian layout + SlotInput::new(slot, 16, 128, 1), + SlotInput::new(slot, 0, 128, 1), + ] + } +} + +impl From for LargeStruct { + fn from(res: simpleStructReturn) -> Self { + Self { + field1: res.field1, + field2: res.field2, + field3: res.field3, + } + } +} + +impl From for LargeStruct { + fn from(res: structMappingReturn) -> Self { + Self { + field1: res.field1, + field2: res.field2, + field3: res.field3, + } + } +} + +impl From<&[[u8; MAPPING_LEAF_VALUE_LEN]]> for LargeStruct { + fn from(fields: &[[u8; MAPPING_LEAF_VALUE_LEN]]) -> Self { + assert_eq!(fields.len(), Self::FIELD_NUM); + + let fields = fields + .iter() + .cloned() + .map(U256::from_be_bytes) + .collect_vec(); + + let field1 = fields[0]; + let field2 = fields[1].to(); + let field3 = fields[2].to(); + Self { + field1, + field2, + field3, + } + } +} diff --git a/mp2-v1/tests/common/cases/table_source.rs b/mp2-v1/tests/common/cases/table_source.rs index 27db3443e..569f3a4ae 100644 --- a/mp2-v1/tests/common/cases/table_source.rs +++ b/mp2-v1/tests/common/cases/table_source.rs @@ -1,6 +1,8 @@ use std::{ + array, assert_matches::assert_matches, - slice, + collections::{BTreeSet, HashMap}, + marker::PhantomData, str::FromStr, sync::atomic::{AtomicU64, AtomicUsize}, }; @@ -8,62 +10,79 @@ use std::{ use alloy::{ eips::BlockNumberOrTag, primitives::{Address, U256}, - providers::Provider, }; use anyhow::{bail, Result}; use futures::{future::BoxFuture, FutureExt}; use itertools::Itertools; use log::{debug, info}; use mp2_common::{ - digest::TableDimension, - eth::{ProofQuery, StorageSlot}, + eth::{ProofQuery, StorageSlot, StorageSlotNode}, proof::ProofWithVK, types::HashOutput, }; use mp2_v1::{ - api::{merge_metadata_hash, metadata_hash, SlotInput, SlotInputs}, + api::{compute_table_info, merge_metadata_hash, metadata_hash, SlotInput, SlotInputs}, indexing::{ block::BlockPrimaryIndex, cell::Cell, row::{RowTreeKey, ToNonce}, }, values_extraction::{ - gadgets::{column_info::ColumnInfo, metadata_gadget::MetadataGadget}, - identifier_for_mapping_key_column, identifier_for_value_column, + gadgets::{column_gadget::extract_value, column_info::ColumnInfo}, + identifier_for_mapping_key_column, identifier_for_value_column, StorageSlotInfo, }, }; use plonky2::field::types::PrimeField64; -use rand::{Rng, SeedableRng}; +use rand::{ + distributions::{Alphanumeric, DistString}, + rngs::StdRng, + Rng, SeedableRng, +}; use serde::{Deserialize, Serialize}; use crate::common::{ - cases::indexing::{MappingUpdate, SimpleSingleValue, TableRowValues}, final_extraction::{ExtractionProofInput, ExtractionTableProof, MergeExtractionProof}, proof_storage::{ProofKey, ProofStorage}, rowtree::SecondaryIndexCell, table::CellsUpdate, - StorageSlotInfo, TestContext, TEST_MAX_COLUMNS, TEST_MAX_FIELD_PER_EVM, + TestContext, TEST_MAX_COLUMNS, TEST_MAX_FIELD_PER_EVM, }; use super::{ - contract::Contract, - indexing::{ChangeType, TableRowUpdate, UpdateSimpleStorage, UpdateType}, + contract::{Contract, ContractController, SimpleSingleValues}, + indexing::{ + ChangeType, MappingUpdate, TableRowUpdate, TableRowValues, UpdateType, SINGLE_SLOTS, + SINGLE_STRUCT_SLOT, + }, + storage_slot_value::{LargeStruct, StorageSlotValue}, }; -/// The key,value such that the combination is unique. This can be turned into a RowTreeKey. -/// to store in the row tree. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct UniqueMappingEntry { - key: U256, - value: U256, -} +/// Save the columns information of same slot and EVM word. +#[derive(Debug)] +struct SlotEvmWordColumns(Vec); -impl From<(U256, U256)> for UniqueMappingEntry { - fn from(pair: (U256, U256)) -> Self { - Self { - key: pair.0, - value: pair.1, - } +impl SlotEvmWordColumns { + fn new(column_info: Vec) -> Self { + // Ensure the column information should have the same slot and EVM word. + let slot = column_info[0].slot(); + let evm_word = column_info[0].evm_word(); + column_info[1..].iter().for_each(|col| { + assert_eq!(col.slot(), slot); + assert_eq!(col.evm_word(), evm_word); + }); + + Self(column_info) + } + fn slot(&self) -> u8 { + // The columns should have the same slot. + u8::try_from(self.0[0].slot().to_canonical_u64()).unwrap() + } + fn evm_word(&self) -> u32 { + // The columns should have the same EVM word. + u32::try_from(self.0[0].evm_word().to_canonical_u64()).unwrap() + } + fn column_info(&self) -> &[ColumnInfo] { + &self.0 } } @@ -71,31 +90,47 @@ impl From<(U256, U256)> for UniqueMappingEntry { /// Each entry contains the identifier of the column expected to store in our tree #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] pub enum MappingIndex { - Key(u64), + OuterKey(u64), + InnerKey(u64), Value(u64), // This can happen if it is being part of a merge table and the secondary index is from the // other table None, } -impl UniqueMappingEntry { - pub fn new(k: &U256, v: &U256) -> Self { - Self { key: *k, value: *v } +/// The key,value such that the combination is unique. This can be turned into a RowTreeKey. +/// to store in the row tree. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct UniqueMappingEntry { + key: U256, + value: V, +} + +impl From<(U256, V)> for UniqueMappingEntry { + fn from(pair: (U256, V)) -> Self { + Self { + key: pair.0, + value: pair.1, + } + } +} + +impl UniqueMappingEntry { + pub fn new(key: U256, value: V) -> Self { + Self { key, value } } pub fn to_update( &self, block_number: BlockPrimaryIndex, + contract: &Contract, mapping_index: &MappingIndex, - slot: u8, - contract: &Address, - chain_id: u64, + slot_inputs: &[SlotInput], previous_row_key: Option, ) -> (CellsUpdate, SecondaryIndexCell) { - let row_value = - self.to_table_row_value(block_number, mapping_index, slot, contract, chain_id); + let row_value = self.to_table_row_value(block_number, contract, mapping_index, slot_inputs); let cells_update = CellsUpdate { previous_row_key: previous_row_key.unwrap_or_default(), - new_row_key: self.to_row_key(mapping_index), + new_row_key: self.to_row_key(contract, mapping_index, slot_inputs), updated_cells: row_value.current_cells, primary: block_number, }; @@ -106,132 +141,145 @@ impl UniqueMappingEntry { /// Return a row given this mapping entry, depending on the chosen index pub fn to_table_row_value( &self, - block_number: BlockPrimaryIndex, + primary: BlockPrimaryIndex, + contract: &Contract, index: &MappingIndex, - slot: u8, - contract: &Address, - chain_id: u64, + slot_inputs: &[SlotInput], ) -> TableRowValues { - // we construct the two associated cells in the table. One of them will become - // a SecondaryIndexCell depending on the secondary index type we have chosen - // for this mapping. - let extract_key = MappingIndex::Key(identifier_for_mapping_key_column( - slot, - contract, - chain_id, - vec![], - )); - let key_cell = self.to_cell(extract_key); - let extract_key = MappingIndex::Value(identifier_for_value_column( - &SlotInput::new( - slot, // byte_offset - 0, // bit_offset - 0, // length - 0, // evm_word - 0, - ), - contract, - chain_id, - vec![], - )); - let value_cell = self.to_cell(extract_key); - // then we look at which one is must be the secondary cell, if any - let (secondary, rest_cells) = match index { - MappingIndex::Key(_) => ( - // by definition, mapping key is unique, so there is no need for a specific - // nonce for the tree in that case - SecondaryIndexCell::new_from(key_cell, U256::from(0)), - vec![value_cell], - ), - MappingIndex::Value(_) => { - // Here we take the tuple (value,key) as uniquely identifying a row in the - // table - ( - SecondaryIndexCell::new_from(value_cell, self.key), - vec![key_cell], - ) + let slot = slot_inputs[0].slot(); + // Ensure it's the same mapping slot. + slot_inputs[1..] + .iter() + .for_each(|slot_input| assert_eq!(slot_input.slot(), slot)); + let key_cell = { + let key_id = identifier_for_mapping_key_column( + slot, + &contract.address, + contract.chain_id, + vec![], + ); + + Cell::new(key_id, self.key) + }; + let mut current_cells = slot_inputs + .iter() + .zip_eq(self.value.to_u256_vec()) + .map(|(slot_input, field)| { + let values_id = identifier_for_value_column( + slot_input, + &contract.address, + contract.chain_id, + vec![], + ); + + Cell::new(values_id, field) + }) + .collect_vec(); + + let secondary_cell = match index { + MappingIndex::OuterKey(_) | MappingIndex::InnerKey(_) => key_cell, + MappingIndex::Value(secondary_value_id) => { + let pos = current_cells + .iter() + .position(|c| &c.identifier() == secondary_value_id) + .unwrap(); + let secondary_cell = current_cells.remove(pos); + + current_cells.push(key_cell); + + secondary_cell } - MappingIndex::None => (Default::default(), vec![value_cell, key_cell]), + MappingIndex::None => unreachable!(), }; debug!( - " --- MAPPING: to row: secondary index {:?} -- cell {:?}", - secondary, rest_cells + " --- MAPPING to row: secondary index {:?} -- cells {:?}", + secondary_cell, current_cells, ); + let current_secondary = Some(SecondaryIndexCell::new_from(secondary_cell, U256::from(0))); TableRowValues { - current_cells: rest_cells, - current_secondary: Some(secondary), - primary: block_number, + current_cells, + current_secondary, + primary, } } - // using MappingIndex is a misleading name but it allows us to choose which part of the mapping - // we want to extract - pub fn to_cell(&self, index: MappingIndex) -> Cell { - match index { - MappingIndex::Key(id) => Cell::new(id, self.key), - MappingIndex::Value(id) => Cell::new(id, self.value), - MappingIndex::None => panic!("this should never happen"), - } - } + pub fn to_row_key( + &self, + contract: &Contract, + index: &MappingIndex, + slot_inputs: &[SlotInput], + ) -> RowTreeKey { + let (row_key, rest) = match index { + MappingIndex::OuterKey(_) | MappingIndex::InnerKey(_) => { + // The mapping key is unique for rows. + (self.key, vec![]) + } + MappingIndex::Value(secondary_value_id) => { + let pos = slot_inputs + .iter() + .position(|slot_input| { + &identifier_for_value_column( + slot_input, + &contract.address, + contract.chain_id, + vec![], + ) == secondary_value_id + }) + .unwrap(); + let secondary_value = self.value.to_u256_vec().remove(pos); + + // The mapping key is unique for rows. + (secondary_value, self.key.to_be_bytes_vec()) + } + MappingIndex::None => unreachable!(), + }; - pub fn to_row_key(&self, index: &MappingIndex) -> RowTreeKey { - match index { - MappingIndex::Key(_) => RowTreeKey { - // tree key indexed by mapping key - value: self.key, - rest: self.value.to_nonce(), - }, - MappingIndex::Value(_) => RowTreeKey { - // tree key indexed by mapping value - value: self.value, - rest: self.key.to_nonce(), - }, - MappingIndex::None => RowTreeKey::default(), + RowTreeKey { + value: row_key, + rest: rest.to_nonce(), } } } #[derive(Serialize, Deserialize, Debug, Hash, Clone, PartialEq, Eq)] pub(crate) enum TableSource { - /// Test arguments for single values extraction (C.1) - SingleValues(SingleValuesExtractionArgs), - /// Test arguments for mapping values extraction (C.1) - /// We can test with and without the length - Mapping((MappingValuesExtractionArgs, Option)), + /// Test arguments for simple slots which stores both single values and Struct values + Single(SingleExtractionArgs), + /// Test arguments for mapping slots which stores single values + MappingValues(MappingExtractionArgs
, Option), + /// Test arguments for mapping slots which stores the Struct values + MappingStruct( + MappingExtractionArgs, + Option, + ), + /// Test arguments for the merge source of both simple and mapping values Merge(MergeSource), } impl TableSource { - pub fn slot_input(&self) -> SlotInputs { + pub async fn generate_extraction_proof_inputs( + &self, + ctx: &mut TestContext, + contract: &Contract, + value_key: ProofKey, + ) -> Result<(ExtractionProofInput, HashOutput)> { match self { - TableSource::SingleValues(single) => { - let inputs = single - .slots - .iter() - .flat_map(|slot_info| { - slot_info - .metadata() - .extracted_table_info() - .iter() - .map(Into::into) - .collect_vec() - }) - .collect(); - SlotInputs::Simple(inputs) + TableSource::Single(ref args) => { + args.generate_extraction_proof_inputs(ctx, contract, value_key) + .await } - TableSource::Mapping((m, _)) => { - // TODO: Support for mapping to Struct. - let slot_input = SlotInput::new( - m.slot, // byte_offset - 0, // bit_offset - 0, // length - 0, // evm_word - 0, - ); - SlotInputs::Mapping(vec![slot_input]) + TableSource::MappingValues(ref args, _) => { + args.generate_extraction_proof_inputs(ctx, contract, value_key) + .await + } + TableSource::MappingStruct(ref args, _) => { + args.generate_extraction_proof_inputs(ctx, contract, value_key) + .await + } + TableSource::Merge(ref args) => { + args.generate_extraction_proof_inputs(ctx, contract, value_key) + .await } - // TODO: Support for mapping of mappings. - TableSource::Merge(_) => panic!("can't call slot inputs on merge table"), } } @@ -243,56 +291,43 @@ impl TableSource { ) -> BoxFuture>> { async move { match self { - TableSource::SingleValues(ref mut s) => s.init_contract_data(ctx, contract).await, - TableSource::Mapping((ref mut m, _)) => m.init_contract_data(ctx, contract).await, - TableSource::Merge(ref mut merge) => merge.init_contract_data(ctx, contract).await, + TableSource::Single(ref mut args) => args.init_contract_data(ctx, contract).await, + TableSource::MappingValues(ref mut args, _) => { + args.init_contract_data(ctx, contract).await + } + TableSource::MappingStruct(ref mut args, _) => { + args.init_contract_data(ctx, contract).await + } + TableSource::Merge(ref mut args) => args.init_contract_data(ctx, contract).await, } } .boxed() } - pub async fn generate_extraction_proof_inputs( - &self, - ctx: &mut TestContext, - contract: &Contract, - value_key: ProofKey, - ) -> Result<(ExtractionProofInput, HashOutput)> { - match self { - // first lets do without length - TableSource::Mapping((ref mapping, _)) => { - mapping - .generate_extraction_proof_inputs(ctx, contract, value_key) - .await - } - TableSource::SingleValues(ref args) => { - args.generate_extraction_proof_inputs(ctx, contract, value_key) - .await - } - TableSource::Merge(ref merge) => { - merge - .generate_extraction_proof_inputs(ctx, contract, value_key) - .await - } - } - } - #[allow(elided_named_lifetimes)] pub fn random_contract_update<'a>( &'a mut self, ctx: &'a mut TestContext, contract: &'a Contract, - c: ChangeType, + change_type: ChangeType, ) -> BoxFuture>> { async move { match self { - TableSource::Mapping((ref mut mapping, _)) => { - mapping.random_contract_update(ctx, contract, c).await + TableSource::Single(ref args) => { + args.random_contract_update(ctx, contract, change_type) + .await + } + TableSource::MappingValues(ref mut args, _) => { + args.random_contract_update(ctx, contract, change_type) + .await } - TableSource::SingleValues(ref v) => { - v.random_contract_update(ctx, contract, c).await + TableSource::MappingStruct(ref mut args, _) => { + args.random_contract_update(ctx, contract, change_type) + .await } - TableSource::Merge(ref mut merge) => { - merge.random_contract_update(ctx, contract, c).await + TableSource::Merge(ref mut args) => { + args.random_contract_update(ctx, contract, change_type) + .await } } } @@ -300,162 +335,321 @@ impl TableSource { } } -/// Single values extraction arguments (C.1) -#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] -pub(crate) struct SingleValuesExtractionArgs { - /// Simple slots - pub(crate) slots: Vec, - // in case of merge table, there might not be any index slot for single table - pub(crate) index_slot: Option, +#[derive(Serialize, Deserialize, Debug, Hash, Clone, PartialEq, Eq)] +pub struct MergeSource { + // NOTE: this is a hardcore assumption currently that table_a is single and table_b is mapping for now + // Extending to full merge between any table is not far - it requires some quick changes in + // circuit but quite a lot of changes in integrated test. + pub(crate) single: SingleExtractionArgs, + pub(crate) mapping: MappingExtractionArgs, } -impl SingleValuesExtractionArgs { - async fn init_contract_data( +impl MergeSource { + pub fn new(single: SingleExtractionArgs, mapping: MappingExtractionArgs) -> Self { + Self { single, mapping } + } + + #[allow(elided_named_lifetimes)] + pub fn generate_extraction_proof_inputs<'a>( + &'a self, + ctx: &'a mut TestContext, + contract: &'a Contract, + proof_key: ProofKey, + ) -> BoxFuture> { + async move { + let ProofKey::ValueExtraction((id, bn)) = proof_key else { + bail!("key wrong"); + }; + let id_a = id.clone() + "_a"; + let id_b = id + "_b"; + // generate the value extraction proof for the both table individually + let (extract_single, _) = self + .single + .generate_extraction_proof_inputs( + ctx, + contract, + ProofKey::ValueExtraction((id_a, bn)), + ) + .await?; + let ExtractionProofInput::Single(extract_a) = extract_single else { + bail!("can't merge non single tables") + }; + let (extract_mappping, _) = self + .mapping + .generate_extraction_proof_inputs( + ctx, + contract, + ProofKey::ValueExtraction((id_b, bn)), + ) + .await?; + let ExtractionProofInput::Single(extract_b) = extract_mappping else { + bail!("can't merge non single tables") + }; + + // add the metadata hashes together - this is mostly for debugging + let md = merge_metadata_hash::( + contract.address, + contract.chain_id, + vec![], + SlotInputs::Simple(self.single.slot_inputs.clone()), + SlotInputs::Mapping(self.mapping.slot_inputs.clone()), + ); + assert!(extract_a != extract_b); + Ok(( + ExtractionProofInput::Merge(MergeExtractionProof { + single: extract_a, + mapping: extract_b, + }), + md, + )) + } + .boxed() + } + + pub async fn init_contract_data( &mut self, ctx: &mut TestContext, contract: &Contract, ) -> Vec> { - let contract_update = SimpleSingleValue { - s1: true, - s2: U256::from(123), - s3: "test".to_string(), - s4: next_address(), - }; - // since the table is not created yet, we are giving an empty table row. When making the - // diff with the new updated contract storage, the logic will detect it's an initialization - // phase - let old_table_values = TableRowValues::default(); - contract - .apply_update(ctx, &UpdateSimpleStorage::Single(contract_update)) - .await - .unwrap(); - let new_table_values = self.current_table_row_values(ctx, contract).await; - assert!( - new_table_values.len() == 1, - "single variable case should only have one row" - ); - let update = old_table_values.compute_update(&new_table_values[0]); - assert!(update.len() == 1, "one row at a time"); - assert_matches!( - update[0], - TableRowUpdate::Insertion(_, _), - "initialization of the contract's table should be init" - ); - update + // OK to call both sequentially since we only look a the block number after setting the + // initial data + let update_single = self.single.init_contract_data(ctx, contract).await; + let update_mapping = self.mapping.init_contract_data(ctx, contract).await; + // now we merge all the cells change from the single contract to the mapping contract + update_mapping + .into_iter() + .flat_map(|um| { + let refm = &um; + // for each update from mapping, we "merge" all the updates from single, i.e. since + // single is the multiplier table + // NOTE: It assumes there is no secondary index on the single table right now. + // NOTE: there should be only one update per block for single table. Here we just try + // to make it a bit more general by saying each update of table a must be present for + // all updates of table b + update_single.iter().map(|us| match (refm, us) { + // We start by a few impossible methods + (_, TableRowUpdate::Deletion(_)) => panic!("no deletion on single table"), + (TableRowUpdate::Update(_), TableRowUpdate::Insertion(_, _)) => { + panic!("insertion on single only happens at genesis") + } + // WARNING: when a mapping row is deleted, it deletes the whole row even for single + // values + (TableRowUpdate::Deletion(ref d), _) => TableRowUpdate::Deletion(d.clone()), + // Regular update on both + (TableRowUpdate::Update(ref update_a), TableRowUpdate::Update(update_b)) => { + let mut update_a = update_a.clone(); + update_a.updated_cells.extend(update_b.updated_cells.iter().cloned()); + TableRowUpdate::Update(update_a) + } + // a new mapping entry and and update in the single variable + (TableRowUpdate::Insertion(ref cells, sec), TableRowUpdate::Update(cellsb)) => { + let mut cells = cells.clone(); + cells.updated_cells.extend(cellsb.updated_cells.iter().cloned()); + TableRowUpdate::Insertion(cells, sec.clone()) + } + // new case for both - likely genesis state + ( + TableRowUpdate::Insertion(ref cella, seca), + TableRowUpdate::Insertion(cellb, secb), + ) => { + assert_eq!(*secb, SecondaryIndexCell::default(), "no secondary index on single supported at the moment in integrated test"); + let mut cella = cella.clone(); + cella.updated_cells.extend(cellb.updated_cells.iter().cloned()); + TableRowUpdate::Insertion(cella,seca.clone()) + } + }).collect_vec() + }) + .collect() } pub async fn random_contract_update( - &self, + &mut self, ctx: &mut TestContext, contract: &Contract, c: ChangeType, ) -> Vec> { - let old_table_values = self.current_table_row_values(ctx, contract).await; - // we can take the first one since we're asking for single value and there is only - // one row - let old_table_values = &old_table_values[0]; - let mut current_values = contract - .current_single_values(ctx) - .await - .expect("can't get current values"); - match c { - ChangeType::Silent => {} - ChangeType::Deletion => { - panic!("can't remove a single row from blockchain data over single values") + // alternate between table a update or table b + // TODO: implement mixed update + match rotate() { + // SINGLE UPDATE only part. Can only do it if change type is not insertion or deletion since we + // can't insert a new row for single variables, there's only one... and we can't delete + // it either then. + // for single updates, we need to apply this update to all the mapping entries, that's + // the "multiplier" part. + 0 if !matches!(c, ChangeType::Insertion) && !matches!(c, ChangeType::Deletion) => { + let single_updates = self.single.random_contract_update(ctx, contract, c).await; + let rsu = &single_updates; + let bn = ctx.block_number().await; + // we fetch the value of all mapping entries, and + let mut all_updates = Vec::new(); + for mk in &self.mapping.mapping_keys { + let current_value = self.mapping.query_value(ctx, contract, mk.clone()).await; + let current_key = U256::from_be_slice(mk); + let entry = UniqueMappingEntry::new(current_key, current_value); + // create one update for each update of the first table (note again there + // should be only one update since it's single var) + all_updates.extend(rsu.iter().map(|s| { + let TableRowUpdate::Update(su) = s else { + panic!("can't have anything else than update for single table"); + }; + TableRowUpdate::Update(CellsUpdate { + // the row key doesn't change since the mapping value doesn't change + previous_row_key: entry.to_row_key( + contract, + &self.mapping.index, + &self.mapping.slot_inputs, + ), + new_row_key: entry.to_row_key( + contract, + &self.mapping.index, + &self.mapping.slot_inputs, + ), + // only insert the new cells from the single update + updated_cells: su.updated_cells.clone(), + primary: bn as BlockPrimaryIndex, + }) + })); + } + all_updates } - ChangeType::Insertion => { - panic!("can't add a new row for blockchain data over single values") + // For mappings, it is the same, we need to append all the single cells to the mapping + // cells for each new update + _ => { + let mapping_updates = self.mapping.random_contract_update(ctx, contract, c).await; + // get the current single cells by emulating as if it's the first time we see them + let single_values = self.single.current_table_row_values(ctx, contract).await; + // since we know there is only a single row for the single case... + let vec_update = TableRowValues::default().compute_update(&single_values[0]); + let TableRowUpdate::Insertion(single_cells, _) = vec_update[0].clone() else { + panic!("can't re-create cells of single variable"); + }; + mapping_updates + .into_iter() + .map(|row_update| { + match row_update { + // nothing else to do for deletion + TableRowUpdate::Deletion(k) => TableRowUpdate::Deletion(k), + // NOTE: nothing else to do for update as well since we know the + // update comes from the mapping, so single didn't change, so no need + // to add anything. + TableRowUpdate::Update(c) => TableRowUpdate::Update(c), + // add the single cells to the new row + TableRowUpdate::Insertion(mut cells, sec) => { + cells + .updated_cells + .extend(single_cells.updated_cells.clone()); + TableRowUpdate::Insertion(cells, sec) + } + } + }) + .collect_vec() } - ChangeType::Update(u) => match u { - UpdateType::Rest => current_values.s4 = next_address(), - UpdateType::SecondaryIndex => { - current_values.s2 = next_value(); - } - }, - }; + } + } +} - let contract_update = UpdateSimpleStorage::Single(current_values); - contract.apply_update(ctx, &contract_update).await.unwrap(); - let new_table_values = self.current_table_row_values(ctx, contract).await; - assert!( - new_table_values.len() == 1, - "there should be only a single row for single case" - ); - old_table_values.compute_update(&new_table_values[0]) +/// Length extraction arguments (C.2) +#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] +pub(crate) struct LengthExtractionArgs { + /// Length slot + pub(crate) slot: u8, + /// Length value + pub(crate) value: u8, +} + +/// Contract extraction arguments (C.3) +#[derive(Debug)] +pub(crate) struct ContractExtractionArgs { + /// Storage slot + pub(crate) slot: StorageSlot, +} + +static SHIFT: AtomicU64 = AtomicU64::new(0); +static ROTATOR: AtomicUsize = AtomicUsize::new(0); + +use lazy_static::lazy_static; +lazy_static! { + pub(crate) static ref BASE_VALUE: U256 = U256::from(10); + pub static ref DEFAULT_ADDRESS: Address = + Address::from_str("0xBA401cdAc1A3B6AEede21c9C4A483bE6c29F88C4").unwrap(); +} + +// can only be either 0 or 1 +pub fn rotate() -> usize { + ROTATOR.fetch_add(1, std::sync::atomic::Ordering::Relaxed) % 2 +} +pub fn next_mapping_key() -> U256 { + next_value() +} +pub fn next_address() -> Address { + let shift = SHIFT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(shift); + let slice = rng.gen::<[u8; 20]>(); + Address::from_slice(&slice) +} +pub fn next_value() -> U256 { + let shift = SHIFT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let bv: U256 = *BASE_VALUE; + bv + U256::from(shift) +} + +/// Extraction arguments for simple slots which stores both single values (Address or U256) and +/// Struct values (LargeStruct for testing) +#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] +pub(crate) struct SingleExtractionArgs { + /// The index of below slot input vector to identify which is the secondardy index column + pub(crate) secondary_index: Option, + /// Slot inputs for this table + pub(crate) slot_inputs: Vec, +} + +// This implementation includes the common function for extraction arguments of simple slots. +impl SingleExtractionArgs { + pub(crate) fn new(secondary_index: Option, slot_inputs: Vec) -> Self { + Self { + secondary_index, + slot_inputs, + } } - // construct a row of the table from the actual value in the contract by fetching from MPT - async fn current_table_row_values( - &self, - ctx: &mut TestContext, - contract: &Contract, - ) -> Vec> { - let mut secondary_cell = None; - let mut rest_cells = Vec::new(); - for slot_info in self.slots.iter() { - let slot = slot_info.slot().slot(); - let query = ProofQuery::new_simple_slot(contract.address, slot as usize); - // TODO: Support for the Struct. - let slot_input = (&slot_info.metadata().extracted_table_info()[0]).into(); - let id = identifier_for_value_column( - &slot_input, - &contract.address, - ctx.rpc.get_chain_id().await.unwrap(), - vec![], - ); - // Instead of manually setting the value to U256, we really extract from the - // MPT proof to mimick the way to "see" update. Also, it ensures we are getting - // the formatting and endianness right. - let value = ctx - .query_mpt_proof(&query, BlockNumberOrTag::Number(ctx.block_number().await)) - .await - .storage_proof[0] - .value; - let cell = Cell::new(id, value); - // make sure we separate the secondary cells and rest of the cells separately. - if let Some(index) = self.index_slot - && index == slot - { - // we put 0 since we know there are no other rows with that secondary value since we are dealing - // we single values, so only 1 row. - secondary_cell = Some(SecondaryIndexCell::new_from(cell, 0)); - } else { - // This is triggered for every cells that are not secondary index. If there is no - // secondary index, then all the values will end up there. - rest_cells.push(cell); - } + + pub(crate) fn secondary_index_slot_input(&self) -> Option { + self.secondary_index + .map(|idx| self.slot_inputs[idx].clone()) + } + + pub(crate) fn rest_column_slot_inputs(&self) -> Vec { + let mut slot_inputs = self.slot_inputs.clone(); + if let Some(idx) = self.secondary_index { + slot_inputs.remove(idx); } - vec![TableRowValues { - current_cells: rest_cells, - current_secondary: secondary_cell, - primary: ctx.block_number().await as BlockPrimaryIndex, - }] + + slot_inputs } - pub async fn generate_extraction_proof_inputs( + async fn generate_extraction_proof_inputs( &self, ctx: &mut TestContext, contract: &Contract, proof_key: ProofKey, ) -> Result<(ExtractionProofInput, HashOutput)> { - let chain_id = ctx.rpc.get_chain_id().await?; - let ProofKey::ValueExtraction((_id, bn)) = proof_key.clone() else { - bail!("invalid proof key"); + let ProofKey::ValueExtraction((_, bn)) = proof_key.clone() else { + bail!("Invalid proof key"); }; - let single_value_proof = match ctx.storage.get_proof_exact(&proof_key) { + let value_proof = match ctx.storage.get_proof_exact(&proof_key) { Ok(p) => p, Err(_) => { - let single_values_proof = ctx - .prove_single_values_extraction( + let storage_slot_info = self.storage_slot_info(contract); + let root_proof = ctx + .prove_values_extraction( &contract.address, BlockNumberOrTag::Number(bn as u64), - &self.slots, + &storage_slot_info, ) .await; - ctx.storage - .store_proof(proof_key, single_values_proof.clone())?; - info!("Generated Values Extraction (C.1) proof for single variables"); + ctx.storage.store_proof(proof_key, root_proof.clone())?; + info!("Generated extraction proof for simple slots"); { - let pproof = ProofWithVK::deserialize(&single_values_proof).unwrap(); + let pproof = ProofWithVK::deserialize(&root_proof).unwrap(); let pi = mp2_v1::values_extraction::PublicInputs::new(&pproof.proof().public_inputs); debug!( @@ -468,88 +662,293 @@ impl SingleValuesExtractionArgs { pi.root_hash() .into_iter() .flat_map(|u| u.to_be_bytes()) - .collect::>() + .collect_vec(), ) ); } - single_values_proof + + root_proof } }; - let inputs = self - .slots - .iter() - .flat_map(|slot_info| { - slot_info - .metadata() - .extracted_table_info() - .iter() - .map(Into::into) - .collect_vec() - }) - .collect(); - let slot_input = SlotInputs::Simple(inputs); + let slot_inputs = SlotInputs::Simple(self.slot_inputs.clone()); let metadata_hash = metadata_hash::( - slot_input, + slot_inputs, &contract.address, - chain_id, + contract.chain_id, vec![], ); - // we're just proving a single set of a value let input = ExtractionProofInput::Single(ExtractionTableProof { - dimension: TableDimension::Single, - value_proof: single_value_proof, + value_proof, length_proof: None, }); Ok((input, metadata_hash)) } + + async fn current_table_row_values( + &self, + ctx: &mut TestContext, + contract: &Contract, + ) -> Vec> { + let mut secondary_cell = None; + let mut rest_cells = Vec::new(); + let secondary_id = self.secondary_index_identifier(contract); + let evm_word_cols = self.evm_word_column_info(contract); + let storage_slots = self.storage_slots(&evm_word_cols); + for (evm_word_col, storage_slot) in evm_word_cols.into_iter().zip(storage_slots) { + let query = ProofQuery::new(contract.address, storage_slot); + let value = ctx + .query_mpt_proof(&query, BlockNumberOrTag::Number(ctx.block_number().await)) + .await + .storage_proof[0] + .value; + let value_bytes = value.to_be_bytes(); + evm_word_col.column_info().iter().for_each(|col_info| { + let extracted_value = extract_value(&value_bytes, col_info); + let extracted_value = U256::from_be_bytes(extracted_value); + let id = col_info.identifier().to_canonical_u64(); + let cell = Cell::new(col_info.identifier().to_canonical_u64(), extracted_value); + if Some(id) == secondary_id { + assert!(secondary_cell.is_none()); + secondary_cell = Some(SecondaryIndexCell::new_from(cell, 0)); + } else { + rest_cells.push(cell); + } + }); + } + vec![TableRowValues { + current_cells: rest_cells, + current_secondary: secondary_cell, + primary: ctx.block_number().await as BlockPrimaryIndex, + }] + } + + fn secondary_index_identifier(&self, contract: &Contract) -> Option { + self.secondary_index_slot_input().map(|slot_input| { + identifier_for_value_column(&slot_input, &contract.address, contract.chain_id, vec![]) + }) + } + + fn table_info(&self, contract: &Contract) -> Vec { + table_info(contract, self.slot_inputs.clone()) + } + + fn evm_word_column_info(&self, contract: &Contract) -> Vec { + let table_info = table_info(contract, self.slot_inputs.clone()); + evm_word_column_info(&table_info) + } + + fn storage_slots(&self, evm_word_cols: &[SlotEvmWordColumns]) -> Vec { + evm_word_cols + .iter() + .map(|evm_word_col| { + // The slot number and EVM word of extracted columns are same in the metadata. + let slot = evm_word_col.slot(); + let evm_word = evm_word_col.evm_word(); + // We could assume it's a single value slot if the EVM word is 0, even if it's the + // first field of a Struct. Since the computed slot location is same either it's + // considered as a single value slot or the first field of a Struct slot. + let storage_slot = StorageSlot::Simple(slot as usize); + if evm_word == 0 { + storage_slot + } else { + StorageSlot::Node(StorageSlotNode::new_struct(storage_slot, evm_word)) + } + }) + .collect() + } + + fn storage_slot_info(&self, contract: &Contract) -> Vec { + let table_info = self.table_info(contract); + self.storage_slots(&self.evm_word_column_info(contract)) + .into_iter() + .map(|storage_slot| StorageSlotInfo::new(storage_slot, table_info.clone())) + .collect() + } +} + +// This implementation includes the functions only used for testing. Since we need to +// generate random data and interact with a specific contract. +impl SingleExtractionArgs { + pub async fn init_contract_data( + &mut self, + ctx: &mut TestContext, + contract: &Contract, + ) -> Vec> { + // Generate a Rng with Send. + let rng = &mut StdRng::from_entropy(); + let single_values = SimpleSingleValues { + s1: rng.gen(), + s2: U256::from_limbs(rng.gen()), + s3: Alphanumeric.sample_string(rng, 10), + s4: next_address(), + }; + single_values.update_contract(ctx, contract).await; + let single_struct = LargeStruct { + field1: U256::from_limbs(rng.gen()), + field2: rng.gen(), + field3: rng.gen(), + }; + single_struct.update_contract(ctx, contract).await; + + // Since the table is not created yet, we are giving an empty table row. When making the + // diff with the new updated contract storage, the logic will detect it's an initialization + // phase. + let old_table_values = TableRowValues::default(); + let new_table_values = self.current_table_row_values(ctx, contract).await; + assert_eq!( + new_table_values.len(), + 1, + "Single variable case should only have one row", + ); + let updates = old_table_values.compute_update(&new_table_values[0]); + assert_eq!(updates.len(), 1); + assert_matches!( + updates[0], + TableRowUpdate::Insertion(_, _), + "Initialization of the contract's table should be init" + ); + + updates + } + + pub async fn random_contract_update( + &self, + ctx: &mut TestContext, + contract: &Contract, + change_type: ChangeType, + ) -> Vec> { + let old_table_values = self.current_table_row_values(ctx, contract).await; + // We can take the first one since we're asking for single value and there is only one row. + let old_table_values = &old_table_values[0]; + match change_type { + ChangeType::Silent => {} + ChangeType::Insertion => { + panic!("Can't add a new row for blockchain data over single values") + } + ChangeType::Deletion => { + panic!("Can't remove a single row from blockchain data over single values") + } + ChangeType::Update(update) => { + let index_slot_input = self.secondary_index_slot_input(); + match update { + UpdateType::Rest => { + let index_slot = index_slot_input.map(|slot_input| slot_input.slot()); + if index_slot == Some(SINGLE_STRUCT_SLOT as u8) { + // Update the single value slots as `Rest` if single Struct slot is the index. + let mut current_values = + SimpleSingleValues::current_values(ctx, contract).await; + current_values.s4 = next_address(); + current_values.update_contract(ctx, contract).await; + } else { + // Update the single Struct slot as `Rest` if one of single value slots is the index. + let mut current_struct = + LargeStruct::current_values(ctx, contract).await; + current_struct.field2 += 1; + current_struct.update_contract(ctx, contract).await; + } + } + UpdateType::SecondaryIndex => { + if let Some(index_slot_input) = index_slot_input { + let slot = index_slot_input.slot(); + let rng = &mut StdRng::from_entropy(); + if slot == SINGLE_STRUCT_SLOT as u8 { + let mut current_struct = + LargeStruct::current_values(ctx, contract).await; + // We only update the secondary index value here. + current_struct.random_update(&index_slot_input); + current_struct.update_contract(ctx, contract).await; + } else { + let mut current_values = + SimpleSingleValues::current_values(ctx, contract).await; + if slot == SINGLE_SLOTS[0] { + current_values.s1 = !current_values.s1; + } else if slot == SINGLE_SLOTS[1] { + current_values.s2 += U256::from(1); + } else if slot == SINGLE_SLOTS[2] { + current_values.s3 = Alphanumeric.sample_string(rng, 10); + } else if slot == SINGLE_SLOTS[3] { + current_values.s4 = next_address(); + } else { + panic!("Wrong slot number"); + } + current_values.update_contract(ctx, contract).await; + } + } + } + } + } + }; + + let new_table_values = self.current_table_row_values(ctx, contract).await; + assert_eq!( + new_table_values.len(), + 1, + "Single variable case should only have one row", + ); + old_table_values.compute_update(&new_table_values[0]) + } } -/// Mapping values extraction arguments (C.1) +/// Mapping extraction arguments #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] -pub(crate) struct MappingValuesExtractionArgs { +pub(crate) struct MappingExtractionArgs { /// Mapping slot number - pub(crate) slot: u8, - pub(crate) index: MappingIndex, + slot: u8, + /// Mapping index type + index: MappingIndex, + /// Slot input information + slot_inputs: Vec, /// Mapping keys: they are useful for two things: /// * doing some controlled changes on the smart contract, since if we want to do an update we /// need to know an existing key /// * doing the MPT proofs over, since this test doesn't implement the copy on write for MPT /// (yet), we're just recomputing all the proofs at every block and we need the keys for that. - pub(crate) mapping_keys: Vec>, + mapping_keys: BTreeSet>, + /// Phantom + _phantom: PhantomData, } -impl MappingValuesExtractionArgs { +impl MappingExtractionArgs +where + V: StorageSlotValue, + Vec>: ContractController, +{ + pub fn new(slot: u8, index: MappingIndex, slot_inputs: Vec) -> Self { + Self { + slot, + index, + slot_inputs, + mapping_keys: BTreeSet::new(), + _phantom: Default::default(), + } + } + + pub fn slot_inputs(&self) -> &[SlotInput] { + &self.slot_inputs + } + pub async fn init_contract_data( &mut self, ctx: &mut TestContext, contract: &Contract, ) -> Vec> { - let index = self.index.clone(); - let slot = self.slot; - let init_pair = (next_value(), next_address()); - // NOTE: here is the same address but for different mapping key (10,11) - let pair2 = (next_value(), init_pair.1); - let init_state = [init_pair, pair2, (next_value(), next_address())]; - // NOTE: uncomment this for simpler testing - //let init_state = [init_pair]; - // saving the keys we are tracking in the mapping + let init_key_and_value: [_; 3] = array::from_fn(|_| (next_mapping_key(), V::sample())); + // Save the mapping keys. self.mapping_keys.extend( - init_state + init_key_and_value .iter() .map(|u| u.0.to_be_bytes_trimmed_vec()) - .collect::>(), + .collect_vec(), ); - let mapping_updates = init_state - .iter() - .map(|u| MappingUpdate::Insertion(u.0, u.1.into_word().into())) - .collect::>(); + let updates = init_key_and_value + .into_iter() + .map(|(key, value)| MappingUpdate::Insertion(key, value)) + .collect_vec(); + + updates.update_contract(ctx, contract).await; - contract - .apply_update(ctx, &UpdateSimpleStorage::Mapping(mapping_updates.clone())) - .await - .unwrap(); let new_block_number = ctx.block_number().await as BlockPrimaryIndex; - self.mapping_to_table_update(new_block_number, mapping_updates, index, slot, contract) + self.mapping_to_table_update(new_block_number, contract, &updates) } async fn random_contract_update( @@ -559,13 +958,13 @@ impl MappingValuesExtractionArgs { c: ChangeType, ) -> Vec> { // NOTE 1: The first part is just trying to construct the right input to simulate any - // changes on a mapping. This is mostly irrelevant for dist system but needs to - // manually construct our test cases here. The second part is more interesting as it looks at "what to do - // when receiving an update from scrapper". The core of the function is in - // `from_mapping_to_table_update` + // changes on a mapping. This is mostly irrelevant for dist system but needs to manually + // construct our test cases here. The second part is more interesting as it looks at + // "what to do when receiving an update from scrapper". The core of the function is in + // `mapping_to_table_update` // - // NOTE 2: Thhis implementation tries to emulate as much as possible what happens in dist - // system. TO compute the set of updates, it first simulate an update on the contract + // NOTE 2: This implementation tries to emulate as much as possible what happens in dist + // system. To compute the set of updates, it first simulate an update on the contract // and creates the signal "MappingUpdate" corresponding to the update. From that point // onwards, the table row updates are manually created. // Note this can actually lead to more work than necessary in some cases. @@ -580,47 +979,32 @@ impl MappingValuesExtractionArgs { // In the backend, we translate that in the "table world" to a deletion and an insertion. // Having such optimization could be done later on, need to properly evaluate the cost // of it. - let idx = 0; - let mkey = &self.mapping_keys[idx].clone(); - let slot = self.slot as usize; - let index_type = self.index.clone(); - let address = &contract.address.clone(); - let query = ProofQuery::new_mapping_slot(*address, slot, mkey.to_owned()); - let response = ctx - .query_mpt_proof(&query, BlockNumberOrTag::Number(ctx.block_number().await)) - .await; - let current_value = response.storage_proof[0].value; - let current_key = U256::from_be_slice(mkey); + let current_key = self.mapping_keys.first().unwrap().clone(); + let current_value = self.query_value(ctx, contract, current_key.clone()).await; + let current_key = U256::from_be_slice(¤t_key); let new_key = next_mapping_key(); - let new_value: U256 = next_address().into_word().into(); - let mapping_updates = match c { + let updates = match c { ChangeType::Silent => vec![], ChangeType::Insertion => { - vec![MappingUpdate::Insertion(new_key, new_value)] + vec![MappingUpdate::Insertion(new_key, V::sample())] } ChangeType::Deletion => { - // NOTE: We care about the value here since that allows _us_ to pinpoint the - // correct row in the table and delete it since for a mpping, we uniquely - // identify row per (mapping_key,mapping_value) (in the order dictated by - // the secondary index) vec![MappingUpdate::Deletion(current_key, current_value)] } ChangeType::Update(u) => { match u { - // update the non-indexed column UpdateType::Rest => { - // check which one it is and change accordingly - match index_type { - MappingIndex::Key(_) => { + let new_value = V::sample(); + match self.index { + MappingIndex::OuterKey(_) | MappingIndex::InnerKey(_) => { // we simply change the mapping value since the key is the secondary index vec![MappingUpdate::Update(current_key, current_value, new_value)] } MappingIndex::Value(_) => { // TRICKY: in this case, the mapping key must change. But from the - // onchain perspective, it means a transfer - // mapping(old_key -> new_key,value) + // onchain perspective, it means a transfer mapping(old_key -> new_key,value) vec![ - MappingUpdate::Deletion(current_key, current_value), + MappingUpdate::Deletion(current_key, current_value.clone()), MappingUpdate::Insertion(new_key, current_value), ] } @@ -634,17 +1018,32 @@ impl MappingValuesExtractionArgs { } } UpdateType::SecondaryIndex => { - match index_type { - MappingIndex::Key(_) => { + match self.index { + MappingIndex::OuterKey(_) | MappingIndex::InnerKey(_) => { // TRICKY: if the mapping key changes, it's a deletion then // insertion from onchain perspective vec![ - MappingUpdate::Deletion(current_key, current_value), + MappingUpdate::Deletion(current_key, current_value.clone()), // we insert the same value but with a new mapping key MappingUpdate::Insertion(new_key, current_value), ] } - MappingIndex::Value(_) => { + MappingIndex::Value(secondary_value_id) => { + // We only update the second index value here. + let slot_input_to_update = self + .slot_inputs + .iter() + .find(|slot_input| { + identifier_for_value_column( + slot_input, + &contract.address, + contract.chain_id, + vec![], + ) == secondary_value_id + }) + .unwrap(); + let mut new_value = current_value.clone(); + new_value.random_update(slot_input_to_update); // if the value changes, it's a simple update in mapping vec![MappingUpdate::Update(current_key, current_value, new_value)] } @@ -659,37 +1058,28 @@ impl MappingValuesExtractionArgs { } }; // small iteration to always have a good updated list of mapping keys - for update in mapping_updates.iter() { + for update in &updates { match update { - MappingUpdate::Deletion(mkey, _) => { - info!("Removing key {} from mappping keys tracking", mkey); - let key_stored = mkey.to_be_bytes_trimmed_vec(); + MappingUpdate::Deletion(key, _) => { + info!("Removing key {} from mappping keys tracking", key); + let key_stored = key.to_be_bytes_trimmed_vec(); self.mapping_keys.retain(|u| u != &key_stored); } - MappingUpdate::Insertion(mkey, _) => { - info!("Inserting key {} to mappping keys tracking", mkey); - self.mapping_keys.push(mkey.to_be_bytes_trimmed_vec()); + MappingUpdate::Insertion(key, _) => { + info!("Inserting key {} to mappping keys tracking", key); + self.mapping_keys.insert(key.to_be_bytes_trimmed_vec()); } // the mapping key doesn't change here so no need to update the list MappingUpdate::Update(_, _, _) => {} } } + updates.update_contract(ctx, contract).await; - contract - .apply_update(ctx, &UpdateSimpleStorage::Mapping(mapping_updates.clone())) - .await - .unwrap(); let new_block_number = ctx.block_number().await as BlockPrimaryIndex; // NOTE HERE is the interesting bit for dist system as this is the logic to execute // on receiving updates from scapper. This only needs to have the relevant // information from update and it will translate that to changes in the tree. - self.mapping_to_table_update( - new_block_number, - mapping_updates, - index_type, - slot as u8, - contract, - ) + self.mapping_to_table_update(new_block_number, contract, &updates) } pub async fn generate_extraction_proof_inputs( @@ -698,29 +1088,23 @@ impl MappingValuesExtractionArgs { contract: &Contract, proof_key: ProofKey, ) -> Result<(ExtractionProofInput, HashOutput)> { - let chain_id = ctx.rpc.get_chain_id().await?; - let slot_input = SlotInput::new( - self.slot, // byte_offset - 0, // bit_offset - 0, // length - 0, // evm_word - 0, - ); + let ProofKey::ValueExtraction((_, bn)) = proof_key.clone() else { + bail!("invalid proof key"); + }; let mapping_root_proof = match ctx.storage.get_proof_exact(&proof_key) { Ok(p) => p, Err(_) => { + let storage_slot_info = self.all_storage_slot_info(contract); let mapping_values_proof = ctx - .prove_mapping_values_extraction( + .prove_values_extraction( &contract.address, - chain_id, - &slot_input, - self.mapping_keys.clone(), + BlockNumberOrTag::Number(bn as u64), + &storage_slot_info, ) .await; - ctx.storage .store_proof(proof_key, mapping_values_proof.clone())?; - info!("Generated Values Extraction (C.1) proof for mapping slots"); + info!("Generated Values Extraction proof for mapping slot"); { let pproof = ProofWithVK::deserialize(&mapping_values_proof).unwrap(); let pi = @@ -735,7 +1119,7 @@ impl MappingValuesExtractionArgs { pi.root_hash() .into_iter() .flat_map(|u| u.to_be_bytes()) - .collect::>() + .collect_vec() ) ); } @@ -743,71 +1127,77 @@ impl MappingValuesExtractionArgs { } }; let metadata_hash = metadata_hash::( - SlotInputs::Mapping(vec![slot_input]), + SlotInputs::Mapping(self.slot_inputs.clone()), &contract.address, - chain_id, + contract.chain_id, vec![], ); // it's a compoound value type of proof since we're not using the length let input = ExtractionProofInput::Single(ExtractionTableProof { - dimension: TableDimension::Compound, value_proof: mapping_root_proof, length_proof: None, }); Ok((input, metadata_hash)) } + /// The generic parameter `V` could be set to an Uint256 as single value or a Struct. pub fn mapping_to_table_update( &self, block_number: BlockPrimaryIndex, - updates: Vec, - index: MappingIndex, - slot: u8, contract: &Contract, + updates: &[MappingUpdate], ) -> Vec> { updates .iter() - .flat_map(|mapping_change| { - match mapping_change { - MappingUpdate::Deletion(mkey, mvalue) => { - // find the associated row key tree to that value - // HERE: there are multiple possibilities: - // * search for the entry at the previous block instead - // * passing inside the deletion the value deleted as well, so we can - // reconstruct the row key - // * or have this extra list of mapping keys - let entry = UniqueMappingEntry::new(mkey, mvalue); - vec![TableRowUpdate::Deletion(entry.to_row_key(&index))] - } - MappingUpdate::Insertion(mkey, mvalue) => { + .flat_map(|update| { + match update { + MappingUpdate::Insertion(key, value) => { // we transform the mapping entry into the "table notion" of row - let entry = UniqueMappingEntry::new(mkey, mvalue); + let entry = UniqueMappingEntry::new(*key, value.clone()); let (cells, index) = entry.to_update( block_number, - &index, - slot, - &contract.address, - contract.chain_id, + contract, + &self.index, + &self.slot_inputs, None, ); + debug!( + "Insert mapping cells: secondary_index = {:?}, update_cell_len = {}", + index, + cells.updated_cells.len() + ); vec![TableRowUpdate::Insertion(cells, index)] } - MappingUpdate::Update(mkey, old_value, mvalue) => { + MappingUpdate::Deletion(key, value) => { + // find the associated row key tree to that value + // HERE: there are multiple possibilities: + // * search for the entry at the previous block instead + // * passing inside the deletion the value deleted as well, so we can + // reconstruct the row key + // * or have this extra list of mapping keys + let entry = UniqueMappingEntry::new(*key, value.clone()); + vec![TableRowUpdate::Deletion(entry.to_row_key( + contract, + &self.index, + &self.slot_inputs, + ))] + } + MappingUpdate::Update(key, old_value, new_value) => { // NOTE: we need here to (a) delete current row and (b) insert new row // Regardless of the change if it's on the mapping key or value, since a // row is uniquely identified by its pair (key,value) then if one of those // change, that means the row tree key needs to change as well, i.e. it's a // deletion and addition. - let previous_entry = UniqueMappingEntry::new(mkey, old_value); - let previous_row_key = previous_entry.to_row_key(&index); - let new_entry = UniqueMappingEntry::new(mkey, mvalue); + let previous_entry = UniqueMappingEntry::new(*key, old_value.clone()); + let previous_row_key = + previous_entry.to_row_key(contract, &self.index, &self.slot_inputs); + let new_entry = UniqueMappingEntry::new(*key, new_value.clone()); let (mut cells, mut secondary_index) = new_entry.to_update( block_number, - &index, - slot, - &contract.address, - contract.chain_id, + contract, + &self.index, + &self.slot_inputs, // NOTE: here we provide the previous key such that we can // reconstruct the cells tree as it was before and then apply // the update and put it in a new row. Otherwise we don't know @@ -818,8 +1208,8 @@ impl MappingValuesExtractionArgs { // In the case it's the value, then we'll have to reprove the cell. Some(previous_row_key.clone()), ); - match index { - MappingIndex::Key(_) => { + match self.index { + MappingIndex::OuterKey(_) | MappingIndex::InnerKey(_) => { // in this case, the mapping value changed, so the cells changed so // we need to start from scratch. Telling there was no previous row // key means it's treated as a full new cells tree. @@ -849,310 +1239,105 @@ impl MappingValuesExtractionArgs { } } }) - .collect::>() + .collect_vec() } -} -#[derive(Serialize, Deserialize, Debug, Hash, Clone, PartialEq, Eq)] -pub struct MergeSource { - // NOTE: this is a hardcore assumption currently that table_a is single and table_b is mapping for now - // Extending to full merge between any table is not far - it requires some quick changes in - // circuit but quite a lot of changes in integrated test. - pub(crate) single: SingleValuesExtractionArgs, - pub(crate) mapping: MappingValuesExtractionArgs, -} + /// Construct a storage slot info by metadata and a mapping key. + fn storage_slot_info( + &self, + evm_word: u32, + table_info: Vec, + mapping_key: Vec, + ) -> StorageSlotInfo { + let storage_slot = V::mapping_storage_slot(self.slot, evm_word, mapping_key); -impl MergeSource { - pub fn new(single: SingleValuesExtractionArgs, mapping: MappingValuesExtractionArgs) -> Self { - Self { single, mapping } + StorageSlotInfo::new(storage_slot, table_info) } - pub async fn init_contract_data( - &mut self, - ctx: &mut TestContext, - contract: &Contract, - ) -> Vec> { - // OK to call both sequentially since we only look a the block number after setting the - // initial data - let update_single = self.single.init_contract_data(ctx, contract).await; - let update_mapping = self.mapping.init_contract_data(ctx, contract).await; - // now we merge all the cells change from the single contract to the mapping contract - update_mapping - .into_iter() - .flat_map(|um| { - let refm = &um; - // for each update from mapping, we "merge" all the updates from single, i.e. since - // single is the multiplier table - // NOTE: It assumes there is no secondary index on the single table right now. - // NOTE: there should be only one update per block for single table. Here we just try - // to make it a bit more general by saying each update of table a must be present for - // all updates of table b - update_single.iter().map(|us| match (refm, us) { - // We start by a few impossible methods - (_, TableRowUpdate::Deletion(_)) => panic!("no deletion on single table"), - (TableRowUpdate::Update(_), TableRowUpdate::Insertion(_, _)) => { - panic!("insertion on single only happens at genesis") - } - // WARNING: when a mapping row is deleted, it deletes the whole row even for single - // values - (TableRowUpdate::Deletion(ref d), _) => TableRowUpdate::Deletion(d.clone()), - // Regular update on both - (TableRowUpdate::Update(ref update_a), TableRowUpdate::Update(update_b)) => { - let mut update_a = update_a.clone(); - update_a.updated_cells.extend(update_b.updated_cells.iter().cloned()); - TableRowUpdate::Update(update_a) - } - // a new mapping entry and and update in the single variable - (TableRowUpdate::Insertion(ref cells, sec), TableRowUpdate::Update(cellsb)) => { - let mut cells = cells.clone(); - cells.updated_cells.extend(cellsb.updated_cells.iter().cloned()); - TableRowUpdate::Insertion(cells, sec.clone()) - } - // new case for both - likely genesis state - ( - TableRowUpdate::Insertion(ref cella, seca), - TableRowUpdate::Insertion(cellb, secb), - ) => { - assert_eq!(*secb,SecondaryIndexCell::default(),"no secondary index on single supported at the moment in integrated test"); - let mut cella = cella.clone(); - cella.updated_cells.extend(cellb.updated_cells.iter().cloned()); - TableRowUpdate::Insertion(cella,seca.clone()) - } - }).collect::>() - }).collect() + /// Construct the storage slot info by the all mapping keys. + fn all_storage_slot_info(&self, contract: &Contract) -> Vec { + let table_info = self.table_info(contract); + let evm_word_cols = self.evm_word_column_info(contract); + evm_word_cols + .iter() + .cartesian_product(self.mapping_keys.iter()) + .map(|(evm_word_col, mapping_key)| { + self.storage_slot_info( + evm_word_col.evm_word(), + table_info.clone(), + mapping_key.clone(), + ) + }) + .collect() } - pub async fn random_contract_update( - &mut self, + /// Query a storage slot value by a mapping key. + async fn query_value( + &self, ctx: &mut TestContext, contract: &Contract, - c: ChangeType, - ) -> Vec> { - // alternate between table a update or table b - // TODO: implement mixed update - match rotate() { - // SINGLE UPDATE only part. Can only do it if change type is not insertion or deletion since we - // can't insert a new row for single variables, there's only one... and we can't delete - // it either then. - // for single updates, we need to apply this update to all the mapping entries, that's - // the "multiplier" part. - 0 if !matches!(c, ChangeType::Insertion) && !matches!(c, ChangeType::Deletion) => { - let single_updates = self.single.random_contract_update(ctx, contract, c).await; - let rsu = &single_updates; - let bn = ctx.block_number().await; - let mslot = self.mapping.slot as usize; - let address = &contract.address.clone(); - // we fetch the value of all mapping entries, and - let mut all_updates = Vec::new(); - for mk in &self.mapping.mapping_keys { - let query = ProofQuery::new_mapping_slot(*address, mslot, mk.to_owned()); - let response = ctx - .query_mpt_proof(&query, BlockNumberOrTag::Number(ctx.block_number().await)) - .await; - let current_value = response.storage_proof[0].value; - let current_key = U256::from_be_slice(mk); - let entry = UniqueMappingEntry::new(¤t_key, ¤t_value); - // create one update for each update of the first table (note again there - // should be only one update since it's single var) - all_updates.extend(rsu.iter().map(|s| { - let TableRowUpdate::Update(su) = s else { - panic!("can't have anything else than update for single table"); - }; - TableRowUpdate::Update(CellsUpdate { - // the row key doesn't change since the mapping value doesn't change - previous_row_key: entry.to_row_key(&self.mapping.index), - new_row_key: entry.to_row_key(&self.mapping.index), - // only insert the new cells from the single update - updated_cells: su.updated_cells.clone(), - primary: bn as BlockPrimaryIndex, - }) - })); - } - all_updates - } - // For mappings, it is the same, we need to append all the single cells to the mapping - // cells for each new update - _ => { - let mapping_updates = self.mapping.random_contract_update(ctx, contract, c).await; - // get the current single cells by emulating as if it's the first time we see them - let single_values = self.single.current_table_row_values(ctx, contract).await; - // since we know there is only a single row for the single case... - let vec_update = TableRowValues::default().compute_update(&single_values[0]); - let TableRowUpdate::Insertion(single_cells, _) = vec_update[0].clone() else { - panic!("can't re-create cells of single variable"); - }; - mapping_updates - .into_iter() - .map(|row_update| { - match row_update { - // nothing else to do for deletion - TableRowUpdate::Deletion(k) => TableRowUpdate::Deletion(k), - // NOTE: nothing else to do for update as well since we know the - // update comes from the mapping, so single didn't change, so no need - // to add anything. - TableRowUpdate::Update(c) => TableRowUpdate::Update(c), - // add the single cells to the new row - TableRowUpdate::Insertion(mut cells, sec) => { - cells - .updated_cells - .extend(single_cells.updated_cells.clone()); - TableRowUpdate::Insertion(cells, sec) - } - } - }) - .collect::>() - } - } - } + mapping_key: Vec, + ) -> V { + let mut extracted_values = vec![]; + let evm_word_cols = self.evm_word_column_info(contract); + for evm_word_col in evm_word_cols { + let storage_slot = + V::mapping_storage_slot(self.slot, evm_word_col.evm_word(), mapping_key.clone()); + let query = ProofQuery::new(contract.address, storage_slot); + let value = ctx + .query_mpt_proof(&query, BlockNumberOrTag::Number(ctx.block_number().await)) + .await + .storage_proof[0] + .value; - #[allow(elided_named_lifetimes)] - pub fn generate_extraction_proof_inputs<'a>( - &'a self, - ctx: &'a mut TestContext, - contract: &'a Contract, - proof_key: ProofKey, - ) -> BoxFuture> { - async move { - let ProofKey::ValueExtraction((id, bn)) = proof_key else { - bail!("key wrong"); - }; - let id_a = id.clone() + "_a"; - let id_b = id + "_b"; - // generate the value extraction proof for the both table individually - let (extract_single, _) = self - .single - .generate_extraction_proof_inputs( - ctx, - contract, - ProofKey::ValueExtraction((id_a, bn)), - ) - .await?; - let ExtractionProofInput::Single(extract_a) = extract_single else { - bail!("can't merge non single tables") - }; - let (extract_mappping, _) = self - .mapping - .generate_extraction_proof_inputs( - ctx, - contract, - ProofKey::ValueExtraction((id_b, bn)), - ) - .await?; - let ExtractionProofInput::Single(extract_b) = extract_mappping else { - bail!("can't merge non single tables") - }; + let value_bytes = value.to_be_bytes(); + evm_word_col.column_info().iter().for_each(|col_info| { + let bytes = extract_value(&value_bytes, col_info); + let value = U256::from_be_bytes(bytes); + debug!( + "Mapping extract value: column: {:?}, value = {}", + col_info, value, + ); - // add the metadata hashes together - this is mostly for debugging - let md = merge_metadata_hash::( - contract.address, - contract.chain_id, - vec![], - TableSource::SingleValues(self.single.clone()).slot_input(), - TableSource::Mapping((self.mapping.clone(), None)).slot_input(), - ); - assert!(extract_a != extract_b); - Ok(( - ExtractionProofInput::Merge(MergeExtractionProof { - single: extract_a, - mapping: extract_b, - }), - md, - )) + extracted_values.push(value); + }); } - .boxed() - } -} -/// Length extraction arguments (C.2) -#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] -pub(crate) struct LengthExtractionArgs { - /// Length slot - pub(crate) slot: u8, - /// Length value - pub(crate) value: u8, -} -/// Contract extraction arguments (C.3) -#[derive(Debug)] -pub(crate) struct ContractExtractionArgs { - /// Storage slot - pub(crate) slot: StorageSlot, -} + V::from_u256_slice(&extracted_values) + } -static SHIFT: AtomicU64 = AtomicU64::new(0); -static ROTATOR: AtomicUsize = AtomicUsize::new(0); + fn table_info(&self, contract: &Contract) -> Vec { + table_info(contract, self.slot_inputs.clone()) + } -use lazy_static::lazy_static; -lazy_static! { - pub(crate) static ref BASE_VALUE: U256 = U256::from(10); - pub static ref DEFAULT_ADDRESS: Address = - Address::from_str("0xBA401cdAc1A3B6AEede21c9C4A483bE6c29F88C4").unwrap(); + fn evm_word_column_info(&self, contract: &Contract) -> Vec { + let table_info = self.table_info(contract); + evm_word_column_info(&table_info) + } } -// can only be either 0 or 1 -pub fn rotate() -> usize { - ROTATOR.fetch_add(1, std::sync::atomic::Ordering::Relaxed) % 2 -} -pub fn next_mapping_key() -> U256 { - next_value() +/// Contruct the table information by the contract and slot inputs. +fn table_info(contract: &Contract, slot_inputs: Vec) -> Vec { + compute_table_info(slot_inputs, &contract.address, contract.chain_id, vec![]) } -pub fn next_address() -> Address { - let shift = SHIFT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(shift); - let slice = rng.gen::<[u8; 20]>(); - Address::from_slice(&slice) -} -pub fn next_value() -> U256 { - let shift = SHIFT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let bv: U256 = *BASE_VALUE; - bv + U256::from(shift) -} - -/// Construct the storage slot information for the simple variable slots. -// bool public s1 -// uint256 public s2 -// string public s3 -// address public s4 -pub(crate) fn single_var_slot_info( - contract_address: &Address, - chain_id: u64, -) -> Vec { - const SINGLE_SLOTS: [u8; 4] = [0, 1, 2, 3]; - // bool, uint256, string, address - const SINGLE_SLOT_LENGTHS: [usize; 4] = [1, 32, 32, 20]; - - let table_info = SINGLE_SLOTS - .into_iter() - .zip_eq(SINGLE_SLOT_LENGTHS) - .map(|(slot, length)| { - let slot_input = SlotInput::new( - slot, // byte_offset - 0, // bit_offset - length, // length - 0, // evm_word - 0, - ); - let identifier = - identifier_for_value_column(&slot_input, contract_address, chain_id, vec![]); - ColumnInfo::new(slot, identifier, 0, 0, length, 0) - }) - .collect_vec(); - - SINGLE_SLOTS - .into_iter() - .enumerate() - .map(|(i, slot)| { - // Create the simple slot. - let slot = StorageSlot::Simple(slot as usize); - - // Create the metadata gadget. - let metadata = MetadataGadget::new( - table_info.clone(), - slice::from_ref(&table_info[i].identifier().to_canonical_u64()), - 0, - ); +/// Construct the column information for each slot and EVM word. +fn evm_word_column_info(table_info: &[ColumnInfo]) -> Vec { + // Initialize a mapping of `(slot, evm_word) -> column_Identifier`. + let mut column_info_map = HashMap::new(); + table_info.iter().for_each(|col| { + column_info_map + .entry((col.slot(), col.evm_word())) + .and_modify(|cols: &mut Vec<_>| cols.push(col.clone())) + .or_insert(vec![col.clone()]); + }); - StorageSlotInfo::new(slot, metadata, None, None) - }) - .collect_vec() + column_info_map + .values() + .cloned() + .map(SlotEvmWordColumns::new) + // This sort is used for the storage slot Struct extraction (in generic), + // since we need to collect the Struct field in the right order. + .sorted_by_key(|info| info.evm_word()) + .collect() } diff --git a/mp2-v1/tests/common/celltree.rs b/mp2-v1/tests/common/celltree.rs index 00740cfc2..073f65e44 100644 --- a/mp2-v1/tests/common/celltree.rs +++ b/mp2-v1/tests/common/celltree.rs @@ -256,7 +256,7 @@ impl TestContext { // only move the cells tree proof of the actual cells, not the secondary index ! // CellsCollection is a bit weird because it has to contain as well the secondary // index to be able to search in it in JSON - if *id == table.columns.secondary_column().identifier { + if *id == table.columns.secondary_column().identifier() { return (*id, new_cell); } @@ -265,7 +265,7 @@ impl TestContext { " --- CELL TREE key {} index of {id} vs secondary id {} vs table.secondary_id {}", tree_key, previous_row.payload.secondary_index_column, - table.columns.secondary.identifier + table.columns.secondary.identifier() ); // we need to update the primary on the impacted cells at least, OR on all the cells if // we are moving all the proofs to a new row key which happens when doing an DELETE + @@ -309,7 +309,7 @@ impl TestContext { ); Ok(RowPayload { - secondary_index_column: table.columns.secondary_column().identifier, + secondary_index_column: table.columns.secondary_column().identifier(), cell_root_key: Some(root_key), cell_root_hash: Some(tree_hash), cell_root_column: Some( diff --git a/mp2-v1/tests/common/final_extraction.rs b/mp2-v1/tests/common/final_extraction.rs index 0ae8db58a..af97b2793 100644 --- a/mp2-v1/tests/common/final_extraction.rs +++ b/mp2-v1/tests/common/final_extraction.rs @@ -1,8 +1,11 @@ use log::debug; -use mp2_common::{digest::TableDimension, proof::ProofWithVK, types::HashOutput, utils::ToFields}; +use mp2_common::{ + group_hashing::weierstrass_to_point, proof::ProofWithVK, types::HashOutput, utils::ToFields, F, +}; use mp2_v1::{ - api, + api, contract_extraction, final_extraction::{CircuitInput, PublicInputs}, + values_extraction, }; use super::TestContext; @@ -11,7 +14,6 @@ use anyhow::Result; #[derive(Clone, Debug, Eq, PartialEq)] pub struct ExtractionTableProof { pub value_proof: Vec, - pub dimension: TableDimension, pub length_proof: Option>, } @@ -44,12 +46,25 @@ impl TestContext { inputs.length_proof.unwrap(), ) } - ExtractionProofInput::Single(inputs) => CircuitInput::new_simple_input( - block_proof, - contract_proof, - inputs.value_proof, - inputs.dimension, - ), + ExtractionProofInput::Single(inputs) => { + { + let value_proof = ProofWithVK::deserialize(&inputs.value_proof).unwrap(); + let value_pi = values_extraction::PublicInputs::::new( + &value_proof.proof().public_inputs, + ); + let contract_proof = ProofWithVK::deserialize(&contract_proof).unwrap(); + let contract_pi = contract_extraction::PublicInputs::from_slice( + &contract_proof.proof().public_inputs, + ); + debug!( + "BEFORE proving final extraction:\n\tvalues_ex_md = {:?}\n\tcontract_md = {:?}\n\texpected_final_md = {:?}", + value_pi.metadata_digest(), + contract_pi.metadata_point(), + (weierstrass_to_point(&value_pi.metadata_digest()) + weierstrass_to_point(&contract_pi.metadata_point())).to_weierstrass(), + ); + } + CircuitInput::new_simple_input(block_proof, contract_proof, inputs.value_proof) + } // NOTE hardcoded for single and mapping right now ExtractionProofInput::Merge(inputs) => CircuitInput::new_merge_single_and_mapping( block_proof, @@ -76,7 +91,11 @@ impl TestContext { assert_eq!(pis.block_number(), block.header.number); assert_eq!(pis.block_hash_raw(), block_hash.to_fields()); assert_eq!(pis.prev_block_hash_raw(), prev_block_hash.to_fields()); - debug!(" FINAL EXTRACTION MPT - digest: {:?}", pis.value_point()); + debug!( + " FINAL EXTRACTION MPT -\n\tvalues digest: {:?}\n\tmetadata digest: {:?}", + pis.value_point(), + pis.metadata_point(), + ); Ok(proof) } diff --git a/mp2-v1/tests/common/index_tree.rs b/mp2-v1/tests/common/index_tree.rs index cde815d9b..9fd473a28 100644 --- a/mp2-v1/tests/common/index_tree.rs +++ b/mp2-v1/tests/common/index_tree.rs @@ -1,5 +1,4 @@ use alloy::primitives::U256; - use log::{debug, info}; use mp2_common::{poseidon::empty_poseidon_hash, proof::ProofWithVK}; use mp2_v1::{ @@ -19,6 +18,7 @@ use ryhope::{ }, MerkleTreeKvDb, }; +use verifiable_db::block_tree::compute_final_digest; use crate::common::proof_storage::{IndexProofIdentifier, ProofKey}; @@ -83,14 +83,19 @@ impl TestContext { let ext_pi = mp2_v1::final_extraction::PublicInputs::from_slice( &ext_proof.proof().public_inputs, ); - // TODO: Fix the rows digest in rows tree according to values extraction update. - // + let is_merge = ext_pi.merge_flag(); + let final_db_digest = compute_final_digest(is_merge, &row_pi).to_weierstrass(); assert_eq!( - row_pi.individual_digest_point(), + final_db_digest, ext_pi.value_point(), - "values extracted vs value in db don't match (left row, right mpt (block {})", + "Block (DB) values digest and values extraction don't match (left DB, right MPT, is_merge {} block {})", + is_merge, node.value.0.to::() ); + debug!( + "NodeIndex Proving - multiplier digest: {:?}", + row_pi.multiplier_digest_point(), + ); } let proof = if context.is_leaf() { info!( diff --git a/mp2-v1/tests/common/length_extraction.rs b/mp2-v1/tests/common/length_extraction.rs index 01468fc92..ec3608f3b 100644 --- a/mp2-v1/tests/common/length_extraction.rs +++ b/mp2-v1/tests/common/length_extraction.rs @@ -1,14 +1,14 @@ -use alloy::{eips::BlockNumberOrTag, primitives::Address}; +use alloy::{eips::BlockNumberOrTag, primitives::Address, providers::Provider}; use log::info; use mp2_common::{ eth::StorageSlot, mpt_sequential::utils::bytes_to_nibbles, proof::ProofWithVK, types::GFp, }; -use mp2_v1::length_extraction::PublicInputs; +use mp2_v1::{length_extraction::PublicInputs, values_extraction::StorageSlotInfo}; use plonky2::field::types::Field; use crate::common::storage_trie::TestStorageTrie; -use super::{StorageSlotInfo, TestContext}; +use super::TestContext; impl TestContext { /// Generate the Values Extraction (C.2) proof for single variables. @@ -29,7 +29,8 @@ impl TestContext { // Query the slot and add the node path to the trie. trie.query_proof_and_add_slot(self, contract_address, bn, slot_info) .await; - let proof = trie.prove_length(value, self.params(), &self.b); + let chain_id = self.rpc.get_chain_id().await.unwrap(); + let proof = trie.prove_length(contract_address, chain_id, value, self.params(), &self.b); // Check the public inputs. let pi = PublicInputs::from_slice(&proof.proof().public_inputs); diff --git a/mp2-v1/tests/common/mod.rs b/mp2-v1/tests/common/mod.rs index 6e2b27657..14620ddb0 100644 --- a/mp2-v1/tests/common/mod.rs +++ b/mp2-v1/tests/common/mod.rs @@ -2,10 +2,9 @@ use alloy::primitives::Address; use anyhow::Result; use cases::table_source::TableSource; -use itertools::Itertools; -use mp2_v1::api::{merge_metadata_hash, metadata_hash, MetadataHash, SlotInput, SlotInputs}; +use mp2_v1::api::{merge_metadata_hash, metadata_hash, MetadataHash, SlotInputs}; use serde::{Deserialize, Serialize}; -use table::TableColumns; +use table::{TableColumns, TableRowUniqueID}; pub mod benchmarker; pub mod bindings; mod block_extraction; @@ -32,13 +31,11 @@ use mp2_common::{proof::ProofWithVK, types::HashOutput}; use plonky2::plonk::config::GenericHashOut; /// Testing maximum columns -const TEST_MAX_COLUMNS: usize = 32; +pub(crate) const TEST_MAX_COLUMNS: usize = 32; /// Testing maximum fields for each EVM word -const TEST_MAX_FIELD_PER_EVM: usize = 32; +pub(crate) const TEST_MAX_FIELD_PER_EVM: usize = 32; type ColumnIdentifier = u64; -type StorageSlotInfo = - mp2_v1::values_extraction::StorageSlotInfo; type PublicParameters = mp2_v1::api::PublicParameters; fn cell_tree_proof_to_hash(proof: &[u8]) -> HashOutput { @@ -76,6 +73,7 @@ pub fn mkdir_all(params_path_str: &str) -> Result<()> { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TableInfo { pub columns: TableColumns, + pub row_unique_id: TableRowUniqueID, // column to do queries over for numerical values, NOT secondary index pub value_column: String, pub public_name: String, @@ -87,65 +85,36 @@ pub struct TableInfo { impl TableInfo { pub fn metadata_hash(&self) -> MetadataHash { match &self.source { - TableSource::Mapping((mapping, _)) => { - let slot_input = SlotInputs::Mapping(vec![SlotInput::new( - mapping.slot, // byte_offset - 0, // bit_offset - 0, // length - 0, // evm_word - 0, - )]); + TableSource::Single(args) => { + let slot = SlotInputs::Simple(args.slot_inputs.clone()); metadata_hash::( - slot_input, + slot, &self.contract_address, self.chain_id, vec![], ) } - // mapping with length not tested right now - TableSource::SingleValues(args) => { - let inputs = args - .slots - .iter() - .flat_map(|slot_info| { - slot_info - .metadata() - .extracted_table_info() - .iter() - .map(Into::into) - .collect_vec() - }) - .collect(); - let slot = SlotInputs::Simple(inputs); + TableSource::MappingValues(args, _) => { + let slot_inputs = SlotInputs::Mapping(args.slot_inputs().to_vec()); metadata_hash::( - slot, + slot_inputs, + &self.contract_address, + self.chain_id, + vec![], + ) + } + TableSource::MappingStruct(args, _) => { + let slot_inputs = SlotInputs::Mapping(args.slot_inputs().to_vec()); + metadata_hash::( + slot_inputs, &self.contract_address, self.chain_id, vec![], ) } - TableSource::Merge(merge) => { - let inputs = merge - .single - .slots - .iter() - .flat_map(|slot_info| { - slot_info - .metadata() - .extracted_table_info() - .iter() - .map(Into::into) - .collect_vec() - }) - .collect(); - let single = SlotInputs::Simple(inputs); - let mapping = SlotInputs::Mapping(vec![SlotInput::new( - merge.mapping.slot, // byte_offset - 0, // bit_offset - 0, // length - 0, // evm_word - 0, - )]); + TableSource::Merge(source) => { + let single = SlotInputs::Simple(source.single.slot_inputs.clone()); + let mapping = SlotInputs::Mapping(source.mapping.slot_inputs().to_vec()); merge_metadata_hash::( self.contract_address, self.chain_id, diff --git a/mp2-v1/tests/common/rowtree.rs b/mp2-v1/tests/common/rowtree.rs index 9c454babe..dfe894346 100644 --- a/mp2-v1/tests/common/rowtree.rs +++ b/mp2-v1/tests/common/rowtree.rs @@ -1,6 +1,6 @@ use alloy::primitives::U256; use log::debug; -use mp2_common::proof::ProofWithVK; +use mp2_common::{proof::ProofWithVK, types::MAPPING_KEY_LEN}; use mp2_v1::{ api::{self, CircuitInput}, indexing::{ @@ -9,8 +9,12 @@ use mp2_v1::{ index::IndexNode, row::{RowPayload, RowTree, RowTreeKey, ToNonce}, }, + values_extraction::{ + row_unique_data_for_mapping_leaf, row_unique_data_for_mapping_of_mappings_leaf, + row_unique_data_for_single_leaf, + }, }; -use plonky2::{field::types::Sample, hash::hash_types::HashOut, plonk::config::GenericHashOut}; +use plonky2::plonk::config::GenericHashOut; use ryhope::{ storage::{ pgsql::PgsqlStorage, @@ -19,9 +23,12 @@ use ryhope::{ }, MerkleTreeKvDb, }; -use verifiable_db::{cells_tree, row_tree::extract_hash_from_proof}; +use verifiable_db::{ + cells_tree, + row_tree::{self, extract_hash_from_proof}, +}; -use crate::common::row_tree_proof_to_hash; +use crate::common::{row_tree_proof_to_hash, table::TableRowUniqueID}; use super::{ proof_storage::{CellProofIdentifier, ProofKey, ProofStorage, RowProofIdentifier}, @@ -90,7 +97,43 @@ impl TestContext { let id = row.secondary_index_column; // Sec. index value let value = row.secondary_index_value(); - let multiplier = table.columns.column_info(id).multiplier; + let column_info = table.columns.column_info(id); + let multiplier = column_info.multiplier; + let row_unique_data = match table.row_unique_id { + TableRowUniqueID::Single => row_unique_data_for_single_leaf(), + TableRowUniqueID::Mapping(key_column_id) => { + let mapping_key: [_; MAPPING_KEY_LEN] = row + .column_value(key_column_id) + .unwrap_or_else(|| { + panic!("Cannot fetch the mapping key: key_column_id = {key_column_id}") + }) + .to_be_bytes(); + debug!( + "FETCHED mapping key to compute row_unique_data: mapping_key = {:?}", + hex::encode(mapping_key), + ); + row_unique_data_for_mapping_leaf(&mapping_key) + } + TableRowUniqueID::MappingOfMappings(outer_key_column_id, inner_key_column_id) => { + let [outer_mapping_key, inner_mapping_key]: [[_; MAPPING_KEY_LEN]; 2] = [outer_key_column_id, inner_key_column_id].map(|key_column_id| { + row.column_value(key_column_id) + .unwrap_or_else(|| { + panic!("Cannot fetch the key of mapping of mappings: key_column_id = {key_column_id}") + }) + .to_be_bytes() + }); + debug!( + "FETCHED mapping of mappings keys to compute row_unique_data: outer_key = {:?}, inner_key = {:?}", + hex::encode(outer_mapping_key), + hex::encode(inner_mapping_key), + ); + + row_unique_data_for_mapping_of_mappings_leaf( + &outer_mapping_key, + &inner_mapping_key, + ) + } + }; // NOTE remove that when playing more with sec. index assert!(!multiplier, "secondary index should be individual type"); // find where the root cells proof has been stored. This comes from looking up the @@ -126,15 +169,15 @@ impl TestContext { row.cells, ); - { - let pvk = ProofWithVK::deserialize(&cell_tree_proof)?; - let pis = cells_tree::PublicInputs::from_slice(&pvk.proof().public_inputs); - debug!( - " Cell Root SPLIT digest: multiplier {:?}, individual {:?}", - pis.multiplier_values_digest_point(), - pis.individual_values_digest_point() - ); - } + let cells_tree_proof_with_vk = ProofWithVK::deserialize(&cell_tree_proof)?; + let cells_tree_pi = cells_tree::PublicInputs::from_slice( + &cells_tree_proof_with_vk.proof().public_inputs, + ); + debug!( + " Cell Root SPLIT digest:\n\tindividual_value {:?}\n\tmultiplier_value {:?}", + cells_tree_pi.individual_values_digest_point(), + cells_tree_pi.multiplier_values_digest_point(), + ); let proof = if context.is_leaf() { // Prove a leaf @@ -150,18 +193,29 @@ impl TestContext { id, value, multiplier, - // TODO: row_unique_data - HashOut::rand().into(), + row_unique_data, cell_tree_proof, ) .unwrap(), ); debug!("Before proving leaf node row tree key {:?}", k); - self.b + let proof = self + .b .bench("indexing::row_tree::leaf", || { api::generate_proof(self.params(), inputs) }) - .expect("while proving leaf") + .expect("while proving leaf"); + let pproof = ProofWithVK::deserialize(&proof).unwrap(); + let pi = verifiable_db::row_tree::PublicInputs::from_slice( + &pproof.proof().public_inputs, + ); + debug!( + "FINISH proving row leaf -->\n\tid = {:?}\n\tindividual digest = {:?}\n\tmultiplier digest = {:?}", + id, + pi.individual_digest_point(), + pi.multiplier_digest_point(), + ); + proof } else if context.is_partial() { let child_key = context .left @@ -181,6 +235,16 @@ impl TestContext { .storage .get_proof_exact(&ProofKey::Row(proof_key.clone())) .expect("UT guarantees proving in order"); + { + let child_pi = ProofWithVK::deserialize(&child_proof).unwrap(); + let child_pi = + row_tree::PublicInputs::from_slice(&child_pi.proof().public_inputs); + debug!( + "BEFORE proving row partial node -->\n\tis_mulitplier = {}\n\tchild_individual_digest = {:?}", + multiplier, + child_pi.individual_digest_point(), + ); + } let inputs = CircuitInput::RowsTree( verifiable_db::row_tree::CircuitInput::partial( @@ -188,8 +252,7 @@ impl TestContext { value, multiplier, context.left.is_some(), - // TODO: row_unique_data - HashOut::rand().into(), + row_unique_data, child_proof, cell_tree_proof, ) @@ -232,8 +295,7 @@ impl TestContext { id, value, multiplier, - // TODO: row_unique_data - HashOut::rand().into(), + row_unique_data, left_proof, right_proof, cell_tree_proof, @@ -320,7 +382,7 @@ impl TestContext { ); Ok(IndexNode { - identifier: table.columns.primary_column().identifier, + identifier: table.columns.primary_column().identifier(), value: U256::from(primary).into(), row_tree_root_key: root_proof_key.tree_key, row_tree_hash: table.row.root_data().await?.unwrap().hash, diff --git a/mp2-v1/tests/common/storage_trie.rs b/mp2-v1/tests/common/storage_trie.rs index 5f2a2bcb5..af1b3b428 100644 --- a/mp2-v1/tests/common/storage_trie.rs +++ b/mp2-v1/tests/common/storage_trie.rs @@ -1,10 +1,14 @@ //! Storage trie for proving tests -use super::{benchmarker::Benchmarker, PublicParameters, StorageSlotInfo, TestContext}; +use super::{ + benchmarker::Benchmarker, PublicParameters, TestContext, TEST_MAX_COLUMNS, + TEST_MAX_FIELD_PER_EVM, +}; use alloy::{ eips::BlockNumberOrTag, primitives::{Address, U256}, }; +use itertools::Itertools; use log::debug; use mp2_common::{ eth::{ProofQuery, StorageSlot, StorageSlotNode}, @@ -14,8 +18,10 @@ use mp2_common::{ }; use mp2_v1::{ api::{generate_proof, CircuitInput}, - length_extraction, values_extraction, + length_extraction, + values_extraction::{self, StorageSlotInfo}, }; +use plonky2::field::types::PrimeField64; use rlp::{Prototype, Rlp}; use std::collections::HashMap; @@ -31,6 +37,8 @@ type SerializedProof = Vec; /// The context during proving #[derive(Clone, Copy)] struct ProvingContext<'a> { + contract_address: &'a Address, + chain_id: u64, params: &'a PublicParameters, slots: &'a HashMap, b: &'a Benchmarker, @@ -40,12 +48,16 @@ struct ProvingContext<'a> { impl<'a> ProvingContext<'a> { /// Initialize the proving context. fn new( + contract_address: &'a Address, + chain_id: u64, params: &'a PublicParameters, slots: &'a HashMap, variable_slot: Option, bench: &'a Benchmarker, ) -> Self { Self { + contract_address, + chain_id, params, slots, variable_slot, @@ -193,7 +205,6 @@ impl TrieNode { // Find the storage slot information for this leaf node. let slot_info = ctx.slots.get(&node).unwrap(); - let metadata = slot_info.metadata().clone(); // Build the leaf circuit input. let (name, input) = match slot_info.slot() { @@ -203,7 +214,8 @@ impl TrieNode { values_extraction::CircuitInput::new_single_variable_leaf( node.clone(), *slot as u8, - metadata, + slot_info.evm_word(), + slot_info.table_info().to_vec(), ), ), // Mapping variable @@ -213,8 +225,11 @@ impl TrieNode { node.clone(), *slot as u8, mapping_key.clone(), - slot_info.outer_key_id(), - metadata, + slot_info + .outer_key_id(ctx.contract_address, ctx.chain_id, vec![]) + .unwrap(), + slot_info.evm_word(), + slot_info.table_info().to_vec(), ), ), StorageSlot::Node(StorageSlotNode::Struct(parent, _)) => match &**parent { @@ -224,7 +239,8 @@ impl TrieNode { values_extraction::CircuitInput::new_single_variable_leaf( node.clone(), *slot as u8, - metadata, + slot_info.evm_word(), + slot_info.table_info().to_vec(), ), ), // Mapping Struct @@ -234,8 +250,11 @@ impl TrieNode { node.clone(), *slot as u8, mapping_key.clone(), - slot_info.outer_key_id(), - metadata, + slot_info + .outer_key_id(ctx.contract_address, ctx.chain_id, vec![]) + .unwrap(), + slot_info.evm_word(), + slot_info.table_info().to_vec(), ), ), // Mapping of mappings Struct @@ -246,11 +265,20 @@ impl TrieNode { values_extraction::CircuitInput::new_mapping_of_mappings_leaf( node.clone(), *slot as u8, - outer_mapping_key.clone(), - inner_mapping_key.clone(), - slot_info.outer_key_id(), - slot_info.inner_key_id(), - metadata, + ( + outer_mapping_key.clone(), + slot_info + .outer_key_id(ctx.contract_address, ctx.chain_id, vec![]) + .unwrap(), + ), + ( + inner_mapping_key.clone(), + slot_info + .inner_key_id(ctx.contract_address, ctx.chain_id, vec![]) + .unwrap(), + ), + slot_info.evm_word(), + slot_info.table_info().to_vec(), ), ), _ => unreachable!(), @@ -272,10 +300,16 @@ impl TrieNode { let list: Vec> = rlp::decode_list(&node); let value: Vec = rlp::decode(&list[1]).unwrap(); debug!( - "[+] [+] MPT SLOT {:?} -> value {:?} value.digest() = {:?}", + "[+] [+] MPT SLOT {} -> identifiers {:?} value {:?} value.digest() = {:?}", slot_info.slot().slot(), + slot_info + .metadata::() + .extracted_table_info() + .iter() + .map(|info| info.identifier().to_canonical_u64()) + .collect_vec(), U256::from_be_slice(&value), - pi.values_digest() + pi.values_digest(), ); proof } @@ -423,10 +457,10 @@ impl TestStorageTrie { bn: BlockNumberOrTag, slot_info: StorageSlotInfo, ) { - let slot = slot_info.slot().slot() as usize; - log::debug!("Querying the simple slot `{slot:?}` of the contract `{contract_address}` from the test context's RPC"); + let storage_slot = slot_info.slot(); + log::debug!("Querying the slot `{storage_slot:?}` of the contract `{contract_address}` from the test context's RPC"); - let query = ProofQuery::new_simple_slot(*contract_address, slot); + let query = ProofQuery::new(*contract_address, storage_slot.clone()); let response = ctx.query_mpt_proof(&query, bn).await; // Get the nodes to prove. Reverse to the sequence from leaf to root. @@ -437,10 +471,8 @@ impl TestStorageTrie { .map(|node| node.to_vec()) .collect(); - let slot = StorageSlot::Simple(slot); - log::debug!( - "Simple slot {slot:?} queried, appending `{}` proof nodes to the trie", + "Storage slot {storage_slot:?} queried, appending `{}` proof nodes to the trie", nodes.len() ); @@ -450,11 +482,20 @@ impl TestStorageTrie { /// Generate the proof for the trie. pub(crate) fn prove_length( &self, + contract_address: &Address, + chain_id: u64, variable_slot: u8, params: &PublicParameters, b: &Benchmarker, ) -> ProofWithVK { - let ctx = ProvingContext::new(params, &self.slots, Some(variable_slot), b); + let ctx = ProvingContext::new( + contract_address, + chain_id, + params, + &self.slots, + Some(variable_slot), + b, + ); // Must prove with 1 slot at least. let proof = self.root.as_ref().unwrap().prove_length(ctx); @@ -463,8 +504,14 @@ impl TestStorageTrie { } /// Generate the proof for the trie. - pub(crate) fn prove_value(&self, params: &PublicParameters, b: &Benchmarker) -> ProofWithVK { - let ctx = ProvingContext::new(params, &self.slots, None, b); + pub(crate) fn prove_value( + &self, + contract_address: &Address, + chain_id: u64, + params: &PublicParameters, + b: &Benchmarker, + ) -> ProofWithVK { + let ctx = ProvingContext::new(contract_address, chain_id, params, &self.slots, None, b); // Must prove with 1 slot at least. let proof = self.root.as_ref().unwrap().prove_value(ctx); @@ -476,11 +523,21 @@ impl TestStorageTrie { fn check_new_slot(&self, new_slot: &StorageSlot, new_nodes: &[RawNode]) { if let Some((_, slot)) = self.slots.iter().next() { // The new slot must be the same type. - match (slot.slot(), new_slot) { - (&StorageSlot::Simple(_), &StorageSlot::Simple(_)) => (), - (&StorageSlot::Mapping(_, slot), &StorageSlot::Mapping(_, new_slot)) => { - // Must have the same slot number for the mapping type. - assert_eq!(slot, new_slot); + let current_slot = slot.slot(); + match (current_slot.is_simple_slot(), new_slot.is_simple_slot()) { + // We could combine the different simple slots. + (true, true) => (), + (false, false) => { + assert_eq!( + current_slot.slot(), + new_slot.slot(), + "Mapping slot number must be same in a storage trie", + ); + assert_eq!( + current_slot.mapping_keys().len(), + new_slot.mapping_keys().len(), + "Mapping keys must have the same number in a storage trie", + ); } _ => panic!("Add the different type of storage slots: {slot:?}, {new_slot:?}"), } diff --git a/mp2-v1/tests/common/table.rs b/mp2-v1/tests/common/table.rs index f8a66fc22..5a5ce3b8f 100644 --- a/mp2-v1/tests/common/table.rs +++ b/mp2-v1/tests/common/table.rs @@ -1,4 +1,4 @@ -use anyhow::{ensure, Context}; +use anyhow::{ensure, Context, Result}; use bb8::Pool; use bb8_postgres::{tokio_postgres::NoTls, PostgresConnectionManager}; use futures::{ @@ -7,14 +7,18 @@ use futures::{ }; use itertools::Itertools; use log::debug; -use mp2_v1::indexing::{ - block::{BlockPrimaryIndex, BlockTreeKey}, - cell::{self, Cell, CellTreeKey, MerkleCell, MerkleCellTree}, - index::IndexNode, - row::{CellCollection, Row, RowTreeKey}, - ColumnID, +use mp2_v1::{ + indexing::{ + block::{BlockPrimaryIndex, BlockTreeKey}, + cell::{self, Cell, CellTreeKey, MerkleCell, MerkleCellTree}, + index::IndexNode, + row::{CellCollection, Row, RowTreeKey}, + ColumnID, + }, + values_extraction::gadgets::column_info::ColumnInfo, }; use parsil::symbols::{ColumnKind, ContextProvider, ZkColumn, ZkTable}; +use plonky2::field::types::PrimeField64; use ryhope::{ storage::{ pgsql::{SqlServerConnection, SqlStorageSettings}, @@ -56,13 +60,32 @@ impl IndexType { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TableColumn { pub name: String, - pub identifier: ColumnID, + pub info: ColumnInfo, pub index: IndexType, /// multiplier means if this columns come from a "merged" table, then it either come from a /// table a or table b. One of these table is the "multiplier" table, the other is not. pub multiplier: bool, } +impl TableColumn { + pub fn identifier(&self) -> ColumnID { + self.info.identifier().to_canonical_u64() + } +} + +/// Table Row unique ID is used to compute the unique data of a row when proving for the cells. +/// It corresponds to the different types of storage slot as: +/// Single slot - row_unique_data_for_single_leaf() +/// Mapping slot - row_unique_data_for_mapping_leaf(mapping_key) +/// Mapping of mappings slot - row_unique_data_for_mapping_of_mappings_leaf(outer_mapping_key, inner_mapping_key) +/// We save the column IDs for fetching the cell value to compute this row unique data. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum TableRowUniqueID { + Single, + Mapping(ColumnID), + MappingOfMappings(ColumnID, ColumnID), +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct TableColumns { pub primary: TableColumn, @@ -80,13 +103,13 @@ impl TableColumns { self.rest.clone() } pub fn column_id_of_cells_index(&self, key: CellTreeKey) -> Option { - self.rest.get(key - 1).map(|tc| tc.identifier) + self.rest.get(key - 1).map(|tc| tc.identifier()) } pub fn column_info(&self, identifier: ColumnIdentifier) -> TableColumn { self.rest .iter() .chain(once(&self.secondary)) - .find(|c| c.identifier == identifier) + .find(|c| c.identifier() == identifier) .unwrap_or_else(|| panic!("can't find cell from identifier {}", identifier)) .clone() } @@ -103,17 +126,21 @@ impl TableColumns { pub fn cells_tree_index_of(&self, identifier: ColumnIdentifier) -> usize { match identifier { // TODO this will be problematic in the CSV case - _ if identifier == self.primary.identifier => panic!( - "should not call the position on primary index since should not be included in cells tree" + _ if identifier == self.primary.identifier() => panic!( + "should not call the position on primary index since should not be included in cells tree: {} == {}", + identifier, + self.primary.identifier(), ), - _ if identifier == self.secondary.identifier => panic!( - "should not call the position on secondary index since should not be included in cells tree" + _ if identifier == self.secondary.identifier() => panic!( + "should not call the position on secondary index since should not be included in cells tree: {} == {}", + identifier, + self.secondary.identifier(), ), _ => self .rest .iter() .enumerate() - .find(|(_, c)| c.identifier == identifier) + .find(|(_, c)| c.identifier() == identifier) // + 1 because sbbst starts at 1 not zero .map(|(i, _)| i+1) .expect("can't find index of identfier"), @@ -121,9 +148,9 @@ impl TableColumns { } pub fn self_assert(&self) { for column in self.non_indexed_columns() { - let idx = self.cells_tree_index_of(column.identifier); + let idx = self.cells_tree_index_of(column.identifier()); let id = self.column_id_of_cells_index(idx).unwrap(); - assert!(column.identifier == id); + assert!(column.identifier() == id); } } } @@ -131,12 +158,12 @@ impl TableColumns { impl From<&TableColumns> for ColumnIDs { fn from(columns: &TableColumns) -> Self { ColumnIDs::new( - columns.primary.identifier, - columns.secondary.identifier, + columns.primary.identifier(), + columns.secondary.identifier(), columns .non_indexed_columns() .into_iter() - .map(|column| column.identifier) + .map(|column| column.identifier()) .collect_vec(), ) } @@ -153,10 +180,12 @@ async fn new_db_pool(db_url: &str) -> anyhow::Result { .context("while creating the db_pool")?; Ok(db_pool) } + pub struct Table { pub(crate) genesis_block: BlockPrimaryIndex, pub(crate) public_name: TableID, pub(crate) columns: TableColumns, + pub(crate) row_unique_id: TableRowUniqueID, // NOTE: there is no cell tree because it's small and can be reconstructed // on the fly very quickly. Otherwise, we would need to store one cell tree per row // and that means one sql table per row which would be untenable. @@ -175,7 +204,11 @@ fn index_table_name(name: &str) -> String { } impl Table { - pub async fn load(public_name: String, columns: TableColumns) -> anyhow::Result { + pub async fn load( + public_name: String, + columns: TableColumns, + row_unique_id: TableRowUniqueID, + ) -> Result { let db_url = std::env::var("DB_URL").unwrap_or("host=localhost dbname=storage".to_string()); let row_tree = MerkleRowTree::new( InitSettings::MustExist, @@ -201,6 +234,7 @@ impl Table { Ok(Self { db_pool: new_db_pool(&db_url).await?, columns, + row_unique_id, genesis_block: genesis as BlockPrimaryIndex, public_name, row: row_tree, @@ -212,7 +246,12 @@ impl Table { row_table_name(&self.public_name) } - pub async fn new(genesis_block: u64, root_table_name: String, columns: TableColumns) -> Self { + pub async fn new( + genesis_block: u64, + root_table_name: String, + columns: TableColumns, + row_unique_id: TableRowUniqueID, + ) -> Self { let db_url = std::env::var("DB_URL").unwrap_or("host=localhost dbname=storage".to_string()); let db_settings_index = SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url.clone()), @@ -241,6 +280,7 @@ impl Table { .await .expect("unable to create db pool"), columns, + row_unique_id, genesis_block: genesis_block as BlockPrimaryIndex, public_name: root_table_name, row: row_tree, @@ -261,7 +301,7 @@ impl Table { .columns .non_indexed_columns() .iter() - .map(|tc| tc.identifier) + .map(|tc| tc.identifier()) .filter_map(|id| cells.find_by_column(id).map(|info| (id, info))) .map(|(id, info)| cell::MerkleCell::new(id, info.value, info.primary)) .collect::>(); @@ -603,7 +643,7 @@ impl TableColumns { impl TableColumn { pub fn to_zkcolumn(&self) -> ZkColumn { ZkColumn { - id: self.identifier, + id: self.identifier(), kind: match self.index { IndexType::Primary => ColumnKind::PrimaryIndex, IndexType::Secondary => ColumnKind::SecondaryIndex, diff --git a/mp2-v1/tests/common/values_extraction.rs b/mp2-v1/tests/common/values_extraction.rs index 78d0ebe62..4366a10a6 100644 --- a/mp2-v1/tests/common/values_extraction.rs +++ b/mp2-v1/tests/common/values_extraction.rs @@ -1,32 +1,16 @@ //! Test utilities for Values Extraction (C.1) use super::{storage_trie::TestStorageTrie, TestContext}; -use crate::common::StorageSlotInfo; -use alloy::{ - eips::BlockNumberOrTag, - primitives::{Address, U256}, -}; +use alloy::{eips::BlockNumberOrTag, primitives::Address, providers::Provider}; +use itertools::Itertools; use log::info; -use mp2_common::{ - eth::{ProofQuery, StorageSlot}, - mpt_sequential::utils::bytes_to_nibbles, - F, -}; -use mp2_v1::{ - api::SlotInput, - values_extraction::{ - gadgets::{column_info::ColumnInfo, metadata_gadget::MetadataGadget}, - identifier_for_value_column, - public_inputs::PublicInputs, - }, -}; +use mp2_common::{mpt_sequential::utils::bytes_to_nibbles, F}; +use mp2_v1::values_extraction::{public_inputs::PublicInputs, StorageSlotInfo}; use plonky2::field::types::Field; -type MappingKey = Vec; - impl TestContext { - /// Generate the Values Extraction (C.1) proof for single variables. - pub(crate) async fn prove_single_values_extraction( + /// Generate the Values Extraction proof for single or mapping variables. + pub(crate) async fn prove_values_extraction( &self, contract_address: &Address, bn: BlockNumberOrTag, @@ -42,19 +26,19 @@ impl TestContext { .await; } - info!("Prove the test storage trie including the simple slots {slots:?}"); - let proof_value = trie.prove_value(self.params(), &self.b); + let chain_id = self.rpc.get_chain_id().await.unwrap(); + let proof_value = trie.prove_value(contract_address, chain_id, self.params(), &self.b); // Check the public inputs. let pi = PublicInputs::new(&proof_value.proof().public_inputs); - assert_eq!(pi.n(), F::from_canonical_usize(slots.len())); assert_eq!(pi.root_hash(), trie.root_hash()); + assert_eq!(pi.n(), F::from_canonical_usize(slots.len())); { - let exp_key = StorageSlot::Simple(slots[0].slot().slot() as usize).mpt_key_vec(); - let exp_key: Vec<_> = bytes_to_nibbles(&exp_key) + let exp_key = slots[0].slot().mpt_key_vec(); + let exp_key = bytes_to_nibbles(&exp_key) .into_iter() .map(F::from_canonical_u8) - .collect(); + .collect_vec(); let (key, ptr) = pi.mpt_key_info(); assert_eq!(key, exp_key); @@ -63,88 +47,4 @@ impl TestContext { proof_value.serialize().unwrap() } - - /// Generate the Values Extraction (C.1) proof for mapping variables. - pub(crate) async fn prove_mapping_values_extraction( - &self, - contract_address: &Address, - chain_id: u64, - slot_input: &SlotInput, - mapping_keys: Vec, - ) -> Vec { - let first_mapping_key = mapping_keys[0].clone(); - let storage_slot_number = mapping_keys.len(); - - // Initialize the test trie. - let mut trie = TestStorageTrie::new(); - info!("mapping mpt proving: Initialized the test storage trie"); - - // Compute the column identifier for the value column. - let column_identifier = - identifier_for_value_column(slot_input, contract_address, chain_id, vec![]); - // Compute the table metadata information. - let slot = slot_input.slot(); - let evm_word = slot_input.evm_word(); - let table_info = vec![ColumnInfo::new( - slot, - column_identifier, - slot_input.byte_offset(), - slot_input.bit_offset(), - slot_input.length(), - evm_word, - )]; - let metadata = MetadataGadget::new(table_info, &[column_identifier], evm_word); - - // Query the slot and add the node path to the trie. - let slot = slot as usize; - for mapping_key in mapping_keys { - let query = ProofQuery::new_mapping_slot(*contract_address, slot, mapping_key.clone()); - let response = self - .query_mpt_proof(&query, BlockNumberOrTag::Number(self.block_number().await)) - .await; - - // Get the nodes to prove. Reverse to the sequence from leaf to root. - let nodes: Vec<_> = response.storage_proof[0] - .proof - .iter() - .rev() - .map(|node| node.to_vec()) - .collect(); - - let sslot = StorageSlot::Mapping(mapping_key.clone(), slot); - info!( - "Save the mapping key {:?} (value {}) on slot {} to the test storage trie", - U256::from_be_slice(&mapping_key), - response.storage_proof[0].value, - slot - ); - - // TODO: Check if we could use the column identifier as the - // outer key ID for mapping values. - let outer_key_id = Some(column_identifier); - let slot_info = StorageSlotInfo::new(sslot, metadata.clone(), outer_key_id, None); - trie.add_slot(slot_info, nodes); - } - - info!("Prove the test storage trie including the mapping slots ({slot}, ...)"); - let proof = trie.prove_value(self.params(), &self.b); - - // Check the public inputs. - let pi = PublicInputs::new(&proof.proof().public_inputs); - assert_eq!(pi.n(), F::from_canonical_usize(storage_slot_number)); - assert_eq!(pi.root_hash(), trie.root_hash()); - { - let exp_key = StorageSlot::Mapping(first_mapping_key, slot).mpt_key_vec(); - let exp_key: Vec<_> = bytes_to_nibbles(&exp_key) - .into_iter() - .map(F::from_canonical_u8) - .collect(); - - let (key, ptr) = pi.mpt_key_info(); - assert_eq!(key, exp_key); - assert_eq!(ptr, F::NEG_ONE); - } - - proof.serialize().expect("can't serialize mpt proof") - } } diff --git a/mp2-v1/tests/integrated_tests.rs b/mp2-v1/tests/integrated_tests.rs index 8ce01bcb4..98f4e565e 100644 --- a/mp2-v1/tests/integrated_tests.rs +++ b/mp2-v1/tests/integrated_tests.rs @@ -86,6 +86,7 @@ async fn integrated_indexing() -> Result<()> { info!("Params built"); // NOTE: to comment to avoid very long tests... + let (mut single, genesis) = TableIndexing::single_value_test_case(&mut ctx).await?; let changes = vec![ ChangeType::Update(UpdateType::Rest), @@ -93,13 +94,24 @@ async fn integrated_indexing() -> Result<()> { ChangeType::Update(UpdateType::SecondaryIndex), ]; single.run(&mut ctx, genesis, changes.clone()).await?; - let (mut mapping, genesis) = TableIndexing::mapping_test_case(&mut ctx).await?; + + let (mut mapping, genesis) = TableIndexing::mapping_value_test_case(&mut ctx).await?; let changes = vec![ ChangeType::Insertion, ChangeType::Update(UpdateType::Rest), + ChangeType::Update(UpdateType::SecondaryIndex), + ChangeType::Deletion, ChangeType::Silent, + ]; + mapping.run(&mut ctx, genesis, changes).await?; + + let (mut mapping, genesis) = TableIndexing::mapping_struct_test_case(&mut ctx).await?; + let changes = vec![ + ChangeType::Insertion, + ChangeType::Update(UpdateType::Rest), ChangeType::Update(UpdateType::SecondaryIndex), ChangeType::Deletion, + ChangeType::Silent, ]; mapping.run(&mut ctx, genesis, changes).await?; @@ -107,7 +119,6 @@ async fn integrated_indexing() -> Result<()> { let changes = vec![ ChangeType::Insertion, ChangeType::Update(UpdateType::Rest), - ChangeType::Update(UpdateType::Rest), ChangeType::Silent, ChangeType::Deletion, ]; @@ -116,6 +127,7 @@ async fn integrated_indexing() -> Result<()> { // save columns information and table information in JSON so querying test can pick up write_table_info(MAPPING_TABLE_INFO_FILE, mapping.table_info())?; write_table_info(MERGE_TABLE_INFO_FILE, merged.table_info())?; + Ok(()) } @@ -126,7 +138,12 @@ async fn integrated_querying(table_info: TableInfo) -> Result<()> { info!("Building querying params"); ctx.build_params(ParamsType::Query).unwrap(); info!("Params built"); - let table = Table::load(table_info.public_name.clone(), table_info.columns.clone()).await?; + let table = Table::load( + table_info.public_name.clone(), + table_info.columns.clone(), + table_info.row_unique_id.clone(), + ) + .await?; dbg!(&table.public_name); test_query(&mut ctx, table, table_info).await?; Ok(()) diff --git a/parsil/src/executor.rs b/parsil/src/executor.rs index 597617b9e..f597a4940 100644 --- a/parsil/src/executor.rs +++ b/parsil/src/executor.rs @@ -263,12 +263,26 @@ pub fn convert_number_string(expr: &mut Expr) -> Result<()> { Ok(()) } -/// When a function that may return a float is encountered, it is wrapped in a -/// call to `FLOOR`. +/// When a function that may return a float is encountered, (i.e., AVG), it is replaced +/// with a call to integer division DIV. fn convert_funcalls(expr: &mut Expr) -> Result<()> { if let Some(replacement) = match expr { Expr::Function(Function { name, .. }) => match name.to_string().to_uppercase().as_str() { - "AVG" => funcall("FLOOR", vec![expr.clone()]).into(), + "AVG" => { + // Replace AVG(expr) with DIV(SUM(expr)/COUNT(expr)) + // replace AVG in `expr` with `SUM` + let mut sum_expr = expr.clone(); + if let Expr::Function(Function { name, .. }) = &mut sum_expr { + *name = ObjectName(vec![Ident::from("SUM")]); + } + // replace AVG in `expr` with `COUNT` + let mut count_expr = expr.clone(); + if let Expr::Function(Function { name, .. }) = &mut count_expr { + *name = ObjectName(vec![Ident::from("COUNT")]); + } + // Add DIV operation + funcall("DIV", vec![sum_expr, count_expr]).into() + } _ => None, }, _ => None, diff --git a/verifiable-db/src/block_tree/api.rs b/verifiable-db/src/block_tree/api.rs index 46788488b..e8c7d33dd 100644 --- a/verifiable-db/src/block_tree/api.rs +++ b/verifiable-db/src/block_tree/api.rs @@ -275,8 +275,8 @@ mod tests { }; use crate::{ block_tree::{ + compute_final_digest, leaf::tests::{compute_expected_hash, compute_expected_set_digest}, - tests::compute_final_digest, }, extraction, row_tree, }; diff --git a/verifiable-db/src/block_tree/leaf.rs b/verifiable-db/src/block_tree/leaf.rs index 5a9cb1617..809113fe0 100644 --- a/verifiable-db/src/block_tree/leaf.rs +++ b/verifiable-db/src/block_tree/leaf.rs @@ -207,7 +207,10 @@ pub mod tests { *, }; use crate::{ - block_tree::tests::{compute_final_digest, TestPIField, TestPITargets}, + block_tree::{ + compute_final_digest, + tests::{TestPIField, TestPITargets}, + }, extraction, }; use alloy::primitives::U256; diff --git a/verifiable-db/src/block_tree/mod.rs b/verifiable-db/src/block_tree/mod.rs index 8e27cf5a1..4c07d6462 100644 --- a/verifiable-db/src/block_tree/mod.rs +++ b/verifiable-db/src/block_tree/mod.rs @@ -11,17 +11,28 @@ use crate::{ row_tree, }; pub use api::{CircuitInput, PublicParameters}; +use itertools::Itertools; use mp2_common::{ - group_hashing::{circuit_hashed_scalar_mul, CircuitBuilderGroupHashing}, - poseidon::{empty_poseidon_hash, hash_to_int_target, H}, + group_hashing::{ + circuit_hashed_scalar_mul, field_hashed_scalar_mul, weierstrass_to_point, + CircuitBuilderGroupHashing, + }, + poseidon::{empty_poseidon_hash, hash_to_int_target, hash_to_int_value, H}, types::CBuilder, - utils::ToTargets, + utils::{ToFields, ToTargets}, CHasher, D, F, }; -use plonky2::{iop::target::Target, plonk::circuit_builder::CircuitBuilder}; +use plonky2::{ + field::types::Field, + iop::target::Target, + plonk::{circuit_builder::CircuitBuilder, config::Hasher}, +}; use plonky2_ecdsa::gadgets::nonnative::CircuitBuilderNonNative; -use plonky2_ecgfp5::gadgets::curve::{CircuitBuilderEcGFp5, CurveTarget}; +use plonky2_ecgfp5::{ + curve::{curve::Point, scalar_field::Scalar}, + gadgets::curve::{CircuitBuilderEcGFp5, CurveTarget}, +}; pub use public_inputs::PublicInputs; /// Common function to compute the digest of the block tree which uses a special format using @@ -37,6 +48,34 @@ pub(crate) fn compute_index_digest( b.curve_scalar_mul(base, &scalar) } +/// Compute the final digest value. +pub fn compute_final_digest( + is_merge_case: bool, + rows_tree_pi: &row_tree::PublicInputs, +) -> Point { + let individual_digest = weierstrass_to_point(&rows_tree_pi.individual_digest_point()); + if !is_merge_case { + return individual_digest; + } + // Compute the final row digest from rows_tree_proof for merge case: + // row_id_multiplier = H2Int(H("") || rows_tree_proof.multiplier_counter) + let empty_hash = empty_poseidon_hash(); + let inputs = empty_hash + .to_fields() + .into_iter() + .chain(once(rows_tree_pi.multiplier_counter())) + .collect_vec(); + let hash = H::hash_no_pad(&inputs); + let row_id_multiplier = hash_to_int_value(hash); + // multiplier_digest = rows_tree_proof.row_id_multiplier * rows_tree_proof.multiplier_vd + let multiplier_vd = weierstrass_to_point(&rows_tree_pi.multiplier_digest_point()); + let row_id_multiplier = Scalar::from_noncanonical_biguint(row_id_multiplier); + let multiplier_digest = multiplier_vd * row_id_multiplier; + // rows_digest_merge = multiplier_digest * rows_tree_proof.DR + let individual_digest = weierstrass_to_point(&rows_tree_pi.individual_digest_point()); + field_hashed_scalar_mul(multiplier_digest.to_fields(), individual_digest) +} + /// Compute the final digest target. pub(crate) fn compute_final_digest_target( b: &mut CBuilder, @@ -56,7 +95,7 @@ where .collect(); let hash = b.hash_n_to_hash_no_pad::(inputs); let row_id_multiplier = hash_to_int_target(b, hash); - // multiplier_digest = rows_tree_proof.row_id_multiplier * rows_tree_proof.multiplier_vd + // multiplier_digest = row_id_multiplier * rows_tree_proof.multiplier_vd let multiplier_vd = rows_tree_pi.multiplier_digest_target(); let row_id_multiplier = b.biguint_to_nonnative(&row_id_multiplier); let multiplier_digest = b.curve_scalar_mul(multiplier_vd, &row_id_multiplier); @@ -93,11 +132,8 @@ pub(crate) mod tests { use super::*; use crate::row_tree; use alloy::primitives::U256; - use itertools::Itertools; use mp2_common::{ - group_hashing::{field_hashed_scalar_mul, weierstrass_to_point}, keccak::PACKED_HASH_LEN, - poseidon::hash_to_int_value, types::CBuilder, utils::{FromFields, ToFields}, C, F, @@ -113,11 +149,10 @@ pub(crate) mod tests { target::Target, witness::{PartialWitness, WitnessWrite}, }, - plonk::config::Hasher, }; - use plonky2_ecgfp5::curve::{curve::Point, scalar_field::Scalar}; + use plonky2_ecgfp5::curve::curve::Point; use rand::{rngs::ThreadRng, thread_rng, Rng}; - use std::{array, iter::once}; + use std::array; pub(crate) type TestPITargets<'a> = crate::extraction::test::PublicInputs<'a, Target>; pub(crate) type TestPIField<'a> = crate::extraction::test::PublicInputs<'a, F>; @@ -184,34 +219,6 @@ pub(crate) mod tests { .to_vec() } - /// Compute the final digest value. - pub(crate) fn compute_final_digest( - is_merge_case: bool, - rows_tree_pi: &row_tree::PublicInputs, - ) -> Point { - let individual_digest = weierstrass_to_point(&rows_tree_pi.individual_digest_point()); - if !is_merge_case { - return individual_digest; - } - // Compute the final row digest from rows_tree_proof for merge case: - // row_id_multiplier = H2Int(H("") || rows_tree_proof.multiplier_counter) - let empty_hash = empty_poseidon_hash(); - let inputs = empty_hash - .to_fields() - .into_iter() - .chain(once(rows_tree_pi.multiplier_counter())) - .collect_vec(); - let hash = H::hash_no_pad(&inputs); - let row_id_multiplier = hash_to_int_value(hash); - // multiplier_digest = rows_tree_proof.row_id_multiplier * rows_tree_proof.multiplier_vd - let multiplier_vd = weierstrass_to_point(&rows_tree_pi.multiplier_digest_point()); - let row_id_multiplier = Scalar::from_noncanonical_biguint(row_id_multiplier); - let multiplier_digest = multiplier_vd * row_id_multiplier; - // rows_digest_merge = multiplier_digest * rows_tree_proof.DR - let individual_digest = weierstrass_to_point(&rows_tree_pi.individual_digest_point()); - field_hashed_scalar_mul(multiplier_digest.to_fields(), individual_digest) - } - #[derive(Clone, Debug)] struct TestFinalDigestCircuit<'a> { extraction_pi: &'a [F], diff --git a/verifiable-db/src/block_tree/parent.rs b/verifiable-db/src/block_tree/parent.rs index b0e1c7c3f..08f1ee52c 100644 --- a/verifiable-db/src/block_tree/parent.rs +++ b/verifiable-db/src/block_tree/parent.rs @@ -270,8 +270,9 @@ where #[cfg(test)] mod tests { use crate::block_tree::{ + compute_final_digest, leaf::tests::{compute_expected_hash, compute_expected_set_digest}, - tests::{compute_final_digest, TestPIField, TestPITargets}, + tests::{TestPIField, TestPITargets}, }; use super::{ diff --git a/verifiable-db/src/row_tree/api.rs b/verifiable-db/src/row_tree/api.rs index a198bff0a..932152aea 100644 --- a/verifiable-db/src/row_tree/api.rs +++ b/verifiable-db/src/row_tree/api.rs @@ -246,6 +246,7 @@ mod test { use crate::cells_tree; use itertools::Itertools; use mp2_common::{ + group_hashing::weierstrass_to_point, poseidon::{empty_poseidon_hash, H}, utils::ToFields, F, @@ -404,7 +405,8 @@ mod test { // Check individual digest assert_eq!( pi.individual_digest_point(), - row_digest.individual_vd.to_weierstrass() + (row_digest.individual_vd + weierstrass_to_point(&child_pi.individual_digest_point())) + .to_weierstrass() ); // Check multiplier digest assert_eq!( @@ -469,7 +471,10 @@ mod test { // Check individual digest assert_eq!( pi.individual_digest_point(), - row_digest.individual_vd.to_weierstrass() + (row_digest.individual_vd + + weierstrass_to_point(&left_pi.individual_digest_point()) + + weierstrass_to_point(&right_pi.individual_digest_point())) + .to_weierstrass() ); // Check multiplier digest assert_eq!( diff --git a/verifiable-db/src/row_tree/full_node.rs b/verifiable-db/src/row_tree/full_node.rs index 79ebbafd7..f1da5b0d3 100644 --- a/verifiable-db/src/row_tree/full_node.rs +++ b/verifiable-db/src/row_tree/full_node.rs @@ -74,9 +74,15 @@ impl FullNodeCircuit { .collect::>(); let hash = b.hash_n_to_hash_no_pad::(inputs); + let individual_vd = b.add_curve_point(&[ + digest.individual_vd, + min_child.individual_digest_target(), + max_child.individual_digest_target(), + ]); + PublicInputs::new( &hash.to_targets(), - &digest.individual_vd.to_targets(), + &individual_vd.to_targets(), &digest.multiplier_vd.to_targets(), &node_min.to_targets(), &node_max.to_targets(), @@ -145,7 +151,7 @@ pub(crate) mod test { use super::*; use alloy::primitives::U256; use itertools::Itertools; - use mp2_common::{utils::ToFields, C, D, F}; + use mp2_common::{group_hashing::weierstrass_to_point, utils::ToFields, C, D, F}; use mp2_test::circuit::{run_circuit, UserCircuit}; use plonky2::{field::types::PrimeField64, iop::witness::WitnessWrite, plonk::config::Hasher}; @@ -233,7 +239,10 @@ pub(crate) mod test { // Check individual digest assert_eq!( pi.individual_digest_point(), - row_digest.individual_vd.to_weierstrass() + (row_digest.individual_vd + + weierstrass_to_point(&left_pi.individual_digest_point()) + + weierstrass_to_point(&right_pi.individual_digest_point())) + .to_weierstrass() ); // Check multiplier digest assert_eq!( diff --git a/verifiable-db/src/row_tree/partial_node.rs b/verifiable-db/src/row_tree/partial_node.rs index e1e386f1a..b33d741db 100644 --- a/verifiable-db/src/row_tree/partial_node.rs +++ b/verifiable-db/src/row_tree/partial_node.rs @@ -108,9 +108,12 @@ impl PartialNodeCircuit { &rest, ); + let individual_vd = + b.add_curve_point(&[digest.individual_vd, child_pi.individual_digest_target()]); + PublicInputs::new( &node_hash, - &digest.individual_vd.to_targets(), + &individual_vd.to_targets(), &digest.multiplier_vd.to_targets(), &node_min.to_targets(), &node_max.to_targets(), @@ -183,6 +186,7 @@ pub mod test { use alloy::primitives::U256; use itertools::Itertools; use mp2_common::{ + group_hashing::weierstrass_to_point, poseidon::{empty_poseidon_hash, H}, types::CBuilder, utils::ToFields, @@ -332,7 +336,8 @@ pub mod test { // Check individual digest assert_eq!( pi.individual_digest_point(), - row_digest.individual_vd.to_weierstrass() + (row_digest.individual_vd + weierstrass_to_point(&child_pi.individual_digest_point())) + .to_weierstrass() ); // Check multiplier digest assert_eq!( diff --git a/verifiable-db/src/row_tree/secondary_index_cell.rs b/verifiable-db/src/row_tree/secondary_index_cell.rs index 4de071b42..27bb0b70e 100644 --- a/verifiable-db/src/row_tree/secondary_index_cell.rs +++ b/verifiable-db/src/row_tree/secondary_index_cell.rs @@ -122,10 +122,10 @@ impl SecondaryIndexCellWire { .collect(); let hash = b.hash_n_to_hash_no_pad::(inputs); let row_id_individual = hash_to_int_target(b, hash); - let row_id_individual = b.biguint_to_nonnative(&row_id_individual); // Multiply row ID to individual value digest: // individual_vd = row_id_individual * individual_vd + let row_id_individual = b.biguint_to_nonnative(&row_id_individual); let individual_vd = b.curve_scalar_mul(values_digests.individual, &row_id_individual); let multiplier_vd = values_digests.multiplier;