diff --git a/backend/examples/verify_inclusion.rs b/backend/examples/verify_inclusion.rs index a795e6c1..4262fed0 100644 --- a/backend/examples/verify_inclusion.rs +++ b/backend/examples/verify_inclusion.rs @@ -32,12 +32,14 @@ fn main() { const N_BYTES: usize = 8; let ptau_path = "./ptau/hermez-raw-11"; + let asset_csv_path = "src/apis/csv/assets.csv"; let entry_csv_path = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; // CEX Generate the Merkle Sum Tree and then initialize the circuit. // Note that `signature_csv` is empty because this is only needed to generate π of Solvency, which is not the case here. let snapshot = - Snapshot::::new(&entry_csv_path, &ptau_path, 1).unwrap(); + Snapshot::::new(&asset_csv_path, &entry_csv_path, &ptau_path) + .unwrap(); let inclusion_proof = snapshot.generate_proof_of_inclusion(0 as usize).unwrap(); diff --git a/backend/src/apis/csv/assets.csv b/backend/src/apis/csv/assets.csv new file mode 100644 index 00000000..bb2e38f0 --- /dev/null +++ b/backend/src/apis/csv/assets.csv @@ -0,0 +1,3 @@ +chain;asset_name;amount +ETH;ETH;556863 +ETH;USDT;556863 diff --git a/backend/src/apis/csv_parser.rs b/backend/src/apis/csv_parser.rs index c3488527..1bc379a7 100644 --- a/backend/src/apis/csv_parser.rs +++ b/backend/src/apis/csv_parser.rs @@ -1,12 +1,15 @@ use std::{error::Error, fs::File, path::Path}; -use ethers::{abi::AbiEncode, types::Bytes}; +use ethers::{ + abi::AbiEncode, + types::{Bytes, U256}, +}; use serde::Deserialize; -use crate::contracts::generated::summa_contract::AddressOwnershipProof; +use crate::contracts::generated::summa_contract::{AddressOwnershipProof, Asset}; #[derive(Debug, Deserialize)] -struct Record { +struct EntriesRecord { chain: String, address: String, signature: String, @@ -22,7 +25,7 @@ pub fn parse_signature_csv>( let mut address_ownership_proofs = Vec::::new(); for result in rdr.deserialize() { - let record: Record = result?; + let record: EntriesRecord = result?; address_ownership_proofs.push(AddressOwnershipProof { cex_address: record.address.to_string(), @@ -35,12 +38,47 @@ pub fn parse_signature_csv>( Ok(address_ownership_proofs) } +#[derive(Debug, Deserialize)] +struct AssetRecord { + chain: String, + asset_name: String, + amount: String, +} + +pub fn parse_asset_csv, const N_ASSETS: usize>( + path: P, +) -> Result<[Asset; N_ASSETS], Box> { + let file = File::open(path)?; + let mut rdr = csv::ReaderBuilder::new().delimiter(b';').from_reader(file); + + let mut assets_vec = Vec::with_capacity(N_ASSETS); + + for result in rdr.deserialize() { + let record: AssetRecord = result?; + + assets_vec.push(Asset { + asset_name: record.asset_name, + chain: record.chain, + amount: U256::from_dec_str(&record.amount)?, + }); + } + + let assets_array: [Asset; N_ASSETS] = assets_vec.try_into().map_err(|v: Vec| { + format!( + "Number of assets in CSV does not match the number of assets in the contract! {:?}", + v + ) + })?; + + Ok(assets_array) +} + #[cfg(test)] mod tests { use super::*; #[test] - fn test_parse_csv_to_assets() { + fn test_parse_csv_to_signature() { let path = "src/apis/csv/signatures.csv"; let address_ownership = parse_signature_csv(path).unwrap(); @@ -54,4 +92,27 @@ mod tests { assert_eq!(address_ownership[0], first_address_ownership); } + + #[test] + fn test_parse_csv_to_assets() { + let path = "src/apis/csv/assets.csv"; + let assets = parse_asset_csv::<&str, 2>(path).unwrap(); + + assert_eq!( + assets[0], + Asset { + chain: "ETH".to_string(), + asset_name: "ETH".to_string(), + amount: U256::from(556863), + } + ); + assert_eq!( + assets[1], + Asset { + chain: "ETH".to_string(), + asset_name: "USDT".to_string(), + amount: U256::from(556863), + } + ); + } } diff --git a/backend/src/apis/round.rs b/backend/src/apis/round.rs index 92574ffd..08c2a5e4 100644 --- a/backend/src/apis/round.rs +++ b/backend/src/apis/round.rs @@ -7,10 +7,10 @@ use halo2_proofs::{ plonk::{ProvingKey, VerifyingKey}, poly::kzg::commitment::ParamsKZG, }; -use serde_json::to_string_pretty; use snark_verifier_sdk::CircuitExt; use std::error::Error; +use super::csv_parser::parse_asset_csv; use crate::contracts::{generated::summa_contract::summa::Asset, signer::SummaSigner}; use summa_solvency::{ circuits::{ @@ -61,12 +61,13 @@ impl MstInclusionProof { pub struct Snapshot { mst: MerkleSumTree, - timestamp: usize, + assets_state: [Asset; N_ASSETS], trusted_setup: [SetupArtifacts; 2], } pub struct Round { - snapshot: Option>, + timestamp: u64, + snapshot: Snapshot, signer: SummaSigner, } @@ -81,38 +82,29 @@ where chain_id: u64, rpc_url: &str, summa_sc_address: Address, + entry_csv_path: &str, + asset_csv_path: &str, + params_path: &str, + timestamp: u64, ) -> Result, Box> { Ok(Round { - snapshot: None, + timestamp, + snapshot: Snapshot::::new( + asset_csv_path, + entry_csv_path, + params_path, + ) + .unwrap(), signer: SummaSigner::new(&vec![], signer_key, chain_id, rpc_url, summa_sc_address), }) } - pub fn build_snapshot(&mut self, entry_csv_path: &str, params_path: &str, timestamp: usize) { - let snapshot = - Snapshot::::new(entry_csv_path, params_path, timestamp) - .unwrap(); - self.snapshot = Some(snapshot); + pub fn get_timestamp(&self) -> u64 { + self.timestamp } - pub async fn dispatch_solvency_proof( - &mut self, - assets: [Asset; N_ASSETS], - ) -> Result<(), &'static str> { - if self.snapshot.is_none() { - return Err("snapshot is not built yet"); - } - let snapshot = self.snapshot.as_ref().unwrap(); - - // Convert U256 to Fp for generating proof of solvency - let asset_sum: [Fp; N_ASSETS] = assets - .iter() - .map(|asset| Fp::from_raw(asset.amount.0) as Fp) - .collect::>() - .try_into() - .unwrap(); - - let proof: SolvencyProof = match snapshot.generate_proof_of_solvency(asset_sum) { + pub async fn dispatch_solvency_proof(&mut self) -> Result<(), &'static str> { + let proof: SolvencyProof = match self.snapshot.generate_proof_of_solvency() { Ok(p) => p, Err(_) => return Err("Failed to generate proof of solvency"), }; @@ -121,9 +113,9 @@ where .signer .submit_proof_of_solvency( proof.public_inputs[0], - assets.to_vec(), + self.snapshot.assets_state.to_vec(), proof.proof_calldata, - U256::from(snapshot.get_timestamp()), + U256::from(self.get_timestamp()), ) .await; @@ -134,12 +126,10 @@ where &self, user_index: usize, ) -> Result { - let snapshot = self.snapshot.as_ref().unwrap(); - if snapshot.mst.entries().len() < user_index { - return Err("user_index is out of range"); - } - - Ok(snapshot.generate_proof_of_inclusion(user_index).unwrap()) + Ok(self + .snapshot + .generate_proof_of_inclusion(user_index) + .unwrap()) } } @@ -150,10 +140,11 @@ where [usize; 2 * (1 + N_ASSETS)]: Sized, { pub fn new( + asset_csv_path: &str, entry_csv_path: &str, params_path: &str, - timestamp: usize, ) -> Result, Box> { + let assets_state = parse_asset_csv::<&str, N_ASSETS>(asset_csv_path).unwrap(); let mst = MerkleSumTree::::new(entry_csv_path).unwrap(); let mst_inclusion_circuit = MstInclusionCircuit::::init_empty(); @@ -177,19 +168,19 @@ where Ok(Snapshot { mst, - timestamp, + assets_state, trusted_setup, }) } - pub fn get_timestamp(&self) -> usize { - self.timestamp - } - - pub fn generate_proof_of_solvency( - &self, - asset_sums: [Fp; N_ASSETS], - ) -> Result { + pub fn generate_proof_of_solvency(&self) -> Result { + let asset_sums = self + .assets_state + .iter() + .map(|asset| Fp::from_raw(asset.amount.0) as Fp) + .collect::>() + .try_into() + .unwrap(); let circuit = SolvencyCircuit::::init(self.mst.clone(), asset_sums); let calldata = gen_proof_solidity_calldata( diff --git a/backend/src/contracts/generated/mod.rs b/backend/src/contracts/generated/mod.rs index a944f3aa..a4524590 100644 --- a/backend/src/contracts/generated/mod.rs +++ b/backend/src/contracts/generated/mod.rs @@ -1,3 +1,3 @@ -pub mod summa_contract; +pub mod inclusion_verifier; pub mod solvency_verifier; -pub mod inclusion_verifier; \ No newline at end of file +pub mod summa_contract; diff --git a/backend/src/tests.rs b/backend/src/tests.rs index d83d133d..834bbefe 100644 --- a/backend/src/tests.rs +++ b/backend/src/tests.rs @@ -70,30 +70,24 @@ pub async fn initialize_anvil() -> ( (anvil, cex_addr_1, cex_addr_2, client, mock_erc20) } +#[cfg(test)] mod test { - use serde_json::from_str; - use std::{fs::read_to_string, sync::Arc}; + use std::sync::Arc; use ethers::{ abi::AbiEncode, - providers::Middleware, - types::{Address, Bytes, Filter, U256}, - utils::{keccak256, to_checksum, Anvil}, + types::{Bytes, U256}, + utils::to_checksum, }; - use halo2_proofs::halo2curves::bn256::Fr as Fp; use crate::apis::{address_ownership::AddressOwnership, round::Round}; - use crate::contracts::{ - generated::{ - inclusion_verifier::InclusionVerifier, - solvency_verifier::SolvencyVerifier, - summa_contract::{ - AddressOwnershipProof, AddressOwnershipProofSubmittedFilter, Asset, - SolvencyProofSubmittedFilter, Summa, - }, + use crate::contracts::generated::{ + inclusion_verifier::InclusionVerifier, + solvency_verifier::SolvencyVerifier, + summa_contract::{ + AddressOwnershipProof, AddressOwnershipProofSubmittedFilter, Asset, + SolvencyProofSubmittedFilter, Summa, }, - mock::mock_erc20, - signer::SummaSigner, }; use crate::tests::initialize_anvil; @@ -125,21 +119,6 @@ mod test { .await .unwrap(); - // Dispatch proof of address ownership - let owned_addresses = vec![AddressOwnershipProof { - chain: "ETH".to_string(), - cex_address: to_checksum(&cex_addr_1, None), - signature: - ("0x089b32327d332c295dc3b8873c205b72153211de6dc1c51235782b091cefb9d06d6df2661b86a7d441cd322f125b84901486b150e684221a7b7636eb8182af551b").parse().unwrap(), - message: "Summa proof of solvency for CryptoExchange".encode().into(), - },AddressOwnershipProof { - chain: "ETH".to_string(), - cex_address: to_checksum(&cex_addr_2, None), - signature: - ("0xb17a9e25265d3b88de7bfad81e7accad6e3d5612308ff83cc0fef76a34152b0444309e8fc3dea5139e49b6fc83a8553071a7af3d0cfd3fb8c1aea2a4c171729c1c").parse().unwrap(), - message: "Summa proof of solvency for CryptoExchange".encode().into(), - }]; - let mut address_ownership_client = AddressOwnership::new( "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", anvil.chain_id(), @@ -183,20 +162,22 @@ mod test { ); // Initialize round + let asset_csv = "src/apis/csv/assets.csv"; + let entry_csv = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; + let params_path = "ptau/hermez-raw-11"; + let mut round = Round::<4, 2, 14>::new( "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // anvil account [0] anvil.chain_id(), anvil.endpoint().as_str(), summa_contract.address(), + entry_csv, + asset_csv, + params_path, + 1, ) .unwrap(); - let entry_csv = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; - let params_path = "ptau/hermez-raw-11"; - - // Build snapshot - round.build_snapshot(entry_csv, params_path, 1); - // Verify solvency proof let mut logs = summa_contract .solvency_proof_submitted_filter() @@ -219,7 +200,7 @@ mod test { }, ]; - assert_eq!(round.dispatch_solvency_proof(assets).await.unwrap(), ()); + assert_eq!(round.dispatch_solvency_proof().await.unwrap(), ()); // After sending transaction of proof of solvency, logs should be updated logs = summa_contract @@ -238,18 +219,7 @@ mod test { mst_root: "0x2E021D9BF99C5BD7267488B6A7A5CF5F7D00222A41B6A9B971899C44089E0C5" .parse() .unwrap(), - assets: vec![ - Asset { - asset_name: "ETH".to_string(), - chain: "ETH".to_string(), - amount: U256::from(556863) - }, - Asset { - asset_name: "USDT".to_string(), - chain: "ETH".to_string(), - amount: U256::from(556863) - } - ], + assets: assets.to_vec() } ); @@ -258,7 +228,7 @@ mod test { let proof = Bytes::from(inclusion_proof.get_proof().clone()); let public_input_vec = inclusion_proof.get_public_inputs(); - // Fixing endianness + // Adjust for endianness let mut leaf_hash = public_input_vec[0][0].to_bytes(); let mut root_hash = public_input_vec[0][1].to_bytes(); leaf_hash.reverse(); @@ -269,10 +239,12 @@ mod test { U256::from_big_endian(&root_hash), ]; + // Ensure the root hash matches the one from the contract let onchain_mstroot = summa_contract.mst_roots(U256::from(1)).await.unwrap(); + assert_eq!(onchain_mstroot, U256::from_big_endian(&root_hash)); // Verify inclusion proof with onchain function - summa_contract + let _result = summa_contract .verify_inclusion_proof(proof, public_inputs, U256::from(1)) .await;