diff --git a/backend/examples/summa_solvency_flow.rs b/backend/examples/summa_solvency_flow.rs index b7eb0f3d..b3fb3fe5 100644 --- a/backend/examples/summa_solvency_flow.rs +++ b/backend/examples/summa_solvency_flow.rs @@ -20,7 +20,7 @@ const USER_INDEX: usize = 0; #[tokio::main] async fn main() -> Result<(), Box> { // Initialize test environment without `address_ownership` instance from `initialize_test_env` function. - let (anvil, _, _, _, summa_contract) = initialize_test_env().await; + let (anvil, _, _, _, summa_contract) = initialize_test_env(None).await; // 1. Submit ownership proof // diff --git a/backend/src/contracts/signer.rs b/backend/src/contracts/signer.rs index 1c8d27b1..503aeb05 100644 --- a/backend/src/contracts/signer.rs +++ b/backend/src/contracts/signer.rs @@ -5,14 +5,8 @@ use ethers::{ types::Address, }; use serde_json::Value; -use std::{ - error::Error, - fs::File, - io::BufReader, - path::Path, - str::FromStr, - sync::{Arc, Mutex}, -}; +use std::{error::Error, fs::File, io::BufReader, path::Path, str::FromStr, sync::Arc}; +use tokio::sync::Mutex; use super::generated::summa_contract::{AddressOwnershipProof, Asset}; use crate::contracts::generated::summa_contract::Summa; @@ -42,9 +36,8 @@ impl SummaSigner { address_input: AddressInput, ) -> Self { let wallet: LocalWallet = LocalWallet::from_str(signer_key).unwrap(); - let client = Arc::new(SignerMiddleware::new( - provider, + provider.clone(), wallet.with_chain_id(chain_id), )); @@ -94,17 +87,19 @@ impl SummaSigner { &self, address_ownership_proofs: Vec, ) -> Result<(), Box> { + let lock_guard = self.nonce_lock.lock().await; + let submit_proof_of_address_ownership = &self .summa_contract .submit_proof_of_address_ownership(address_ownership_proofs); // To prevent nonce collision, we lock the nonce before sending the transaction - let _lock = self.nonce_lock.lock().unwrap(); let tx = submit_proof_of_address_ownership.send().await?; // Wait for the pending transaction to be mined tx.await?; + drop(lock_guard); Ok(()) } @@ -115,17 +110,20 @@ impl SummaSigner { proof: ethers::types::Bytes, timestamp: ethers::types::U256, ) -> Result<(), Box> { + let lock_guard = self.nonce_lock.lock().await; + let submit_proof_of_solvency_call = &self .summa_contract .submit_proof_of_solvency(mst_root, assets, proof, timestamp); // To prevent nonce collision, we lock the nonce before sending the transaction - let _lock = self.nonce_lock.lock().unwrap(); let tx = submit_proof_of_solvency_call.send().await?; // Wait for the pending transaction to be mined tx.await?; + drop(lock_guard); + Ok(()) } } diff --git a/backend/src/tests.rs b/backend/src/tests.rs index a725139b..1cce8cf1 100644 --- a/backend/src/tests.rs +++ b/backend/src/tests.rs @@ -16,16 +16,25 @@ use crate::contracts::generated::{ use crate::contracts::mock::mock_erc20::{MockERC20, MOCKERC20_ABI, MOCKERC20_BYTECODE}; // Setup test environment on the anvil instance -pub async fn initialize_test_env() -> ( +pub async fn initialize_test_env( + block_time: Option, +) -> ( AnvilInstance, H160, H160, Arc, LocalWallet>>, Summa, LocalWallet>>, ) { - let anvil: ethers::utils::AnvilInstance = Anvil::new() - .mnemonic("test test test test test test test test test test test junk") - .spawn(); + // Initiate anvil by following assign block time or instant mining + let anvil = match block_time { + Some(interval) => Anvil::new() + .mnemonic("test test test test test test test test test test test junk") + .block_time(interval) + .spawn(), + None => Anvil::new() + .mnemonic("test test test test test test test test test test test junk") + .spawn(), + }; // Extracting two exchange addresses from the Anvil instance let cex_addr_1 = anvil.addresses()[1]; @@ -43,13 +52,6 @@ pub async fn initialize_test_env() -> ( wallet.with_chain_id(anvil.chain_id()), )); - // Creating a factory to deploy a mock ERC20 contract - let factory = ContractFactory::new( - MOCKERC20_ABI.to_owned(), - MOCKERC20_BYTECODE.to_owned(), - Arc::clone(&client), - ); - // Send RPC requests with `anvil_setBalance` method via provider to set ETH balance of `cex_addr_1` and `cex_addr_2` // This is for meeting `proof_of_solvency` test conditions for addr in [cex_addr_1, cex_addr_2].iter().copied() { @@ -59,6 +61,14 @@ pub async fn initialize_test_env() -> ( .await; } + // TODO: Mock ERC20 contract deployment following the attributes of `summa_contract` + // Creating a factory to deploy a mock ERC20 contract + let factory = ContractFactory::new( + MOCKERC20_ABI.to_owned(), + MOCKERC20_BYTECODE.to_owned(), + Arc::clone(&client), + ); + // Deploy Mock ERC20 contract let mock_erc20_deployment = factory.deploy(()).unwrap().send().await.unwrap(); @@ -71,6 +81,10 @@ pub async fn initialize_test_env() -> ( time::sleep(Duration::from_millis(500)).await; + if block_time != None { + time::sleep(Duration::from_secs(block_time.unwrap())).await; + }; + // Deploy verifier contracts before deploy Summa contract let solvency_verifer_contract = SolvencyVerifier::deploy(Arc::clone(&client), ()) .unwrap() @@ -78,12 +92,20 @@ pub async fn initialize_test_env() -> ( .await .unwrap(); + if block_time != None { + time::sleep(Duration::from_secs(block_time.unwrap())).await; + }; + let inclusion_verifer_contract = InclusionVerifier::deploy(Arc::clone(&client), ()) .unwrap() .send() .await .unwrap(); + if block_time != None { + time::sleep(Duration::from_secs(block_time.unwrap())).await; + }; + // Deploy Summa contract let summa_contract = Summa::deploy( Arc::clone(&client), @@ -97,13 +119,24 @@ pub async fn initialize_test_env() -> ( .await .unwrap(); + time::sleep(Duration::from_secs(3)).await; + (anvil, cex_addr_1, cex_addr_2, client, summa_contract) } #[cfg(test)] mod test { - use ethers::{abi::AbiEncode, providers::Provider, types::U256, utils::to_checksum}; + use ethers::{ + abi::AbiEncode, + providers::{Http, Middleware, Provider}, + types::{U256, U64}, + utils::to_checksum, + }; use std::{convert::TryFrom, error::Error, sync::Arc}; + use tokio::{ + join, + time::{sleep, Duration}, + }; use crate::apis::{address_ownership::AddressOwnership, round::Round}; use crate::contracts::{ @@ -117,7 +150,7 @@ mod test { #[tokio::test] async fn test_deployed_address() -> Result<(), Box> { - let (anvil, _, _, _, summa_contract) = initialize_test_env().await; + let (anvil, _, _, _, summa_contract) = initialize_test_env(None).await; // Hardhat development environment, usually updates the address of a deployed contract in the `artifacts` directory. // However, in our custom deployment script, `contracts/scripts/deploy.ts`, @@ -137,9 +170,66 @@ mod test { Ok(()) } + #[tokio::test] + async fn test_concurrent_proof_submissions() -> Result<(), Box> { + let (anvil, _, _, _, summa_contract) = initialize_test_env(Some(1)).await; + + let provider = Arc::new(Provider::try_from(anvil.endpoint().as_str())?); + + let signer = SummaSigner::new( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + anvil.chain_id(), + provider, + AddressInput::Address(summa_contract.address()), + ); + + // At least one address ownership proof should be submitted before submitting solvency proof + let mut address_ownership_client = + AddressOwnership::new(&signer, "src/apis/csv/signatures.csv").unwrap(); + + address_ownership_client + .dispatch_proof_of_address_ownership() + .await?; + + // Do sumbit solvency proofs simultaneously + 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_one = + Round::<4, 2, 14>::new(&signer, entry_csv, asset_csv, params_path, 1).unwrap(); + let mut round_two = + Round::<4, 2, 14>::new(&signer, entry_csv, asset_csv, params_path, 2).unwrap(); + + // Checking block number before sending transaction of proof of solvency + let outer_provider: Provider = Provider::try_from(anvil.endpoint().as_str())?; + let start_block_number = outer_provider.get_block_number().await?; + + // Send two solvency proofs simultaneously + let (round_one_result, round_two_result) = join!( + round_one.dispatch_solvency_proof(), + round_two.dispatch_solvency_proof() + ); + + // Make sure the block has been mined at least 2 blocks + for _ in 0..5 { + sleep(Duration::from_millis(500)).await; + let updated_block_number = outer_provider.get_block_number().await?; + if (updated_block_number - start_block_number) > U64::from(2) { + break; + } + } + + // Check two rounds' result are both Ok + assert!(round_one_result.is_ok()); + assert!(round_two_result.is_ok()); + + Ok(()) + } + #[tokio::test] async fn test_round_features() -> Result<(), Box> { - let (anvil, cex_addr_1, cex_addr_2, _, summa_contract) = initialize_test_env().await; + let (anvil, cex_addr_1, cex_addr_2, _, summa_contract) = initialize_test_env(None).await; let provider = Arc::new(Provider::try_from(anvil.endpoint().as_str())?); let signer = SummaSigner::new(