From 4026c308f687fc5c7054fc1b38ae1ee0b3ba8a09 Mon Sep 17 00:00:00 2001 From: Andrew Kirillov <20803092+akirillo@users.noreply.github.com> Date: Tue, 8 Aug 2023 14:49:10 -0700 Subject: [PATCH] tests: verifier: scaffolding for verifier integration tests --- src/testing/tests/verifier_tests.cairo | 8 +- src/verifier.cairo | 10 +- tests/src/utils.rs | 47 +++++++- tests/src/verifier/utils.rs | 148 ++++++++++++++++++++++++- 4 files changed, 201 insertions(+), 12 deletions(-) diff --git a/src/testing/tests/verifier_tests.cairo b/src/testing/tests/verifier_tests.cairo index 60b8f0a8..5771c5a4 100644 --- a/src/testing/tests/verifier_tests.cairo +++ b/src/testing/tests/verifier_tests.cairo @@ -92,8 +92,8 @@ fn test_full_verification_ex_proof() { 'executing verification job'.print(); verifier.step_verification(11); - let verification_job = verifier.get_verification_job(11); - assert(verification_job.verified == Option::Some(true), 'verification failed'); + let verified = verifier.check_verification_job_status(11); + assert(verified == Option::Some(true), 'verification failed'); 'proof verified!'.print(); } @@ -119,8 +119,8 @@ fn test_full_verification_modified_proof() { 'executing verification job'.print(); verifier.step_verification(11); - let verification_job = verifier.get_verification_job(11); - assert(verification_job.verified == Option::Some(true), 'verification failed'); + let verified = verifier.check_verification_job_status(11); + assert(verified == Option::Some(true), 'verification failed'); } // 10x more gas for this test so that verification definitely completes, diff --git a/src/verifier.cairo b/src/verifier.cairo index 924e20aa..4bdc6812 100644 --- a/src/verifier.cairo +++ b/src/verifier.cairo @@ -22,7 +22,9 @@ trait IVerifier { ); fn step_verification(ref self: TContractState, verification_job_id: felt252); fn get_circuit_params(self: @TContractState) -> CircuitParams; - fn get_verification_job(self: @TContractState, verification_job_id: felt252) -> VerificationJob; + fn check_verification_job_status( + self: @TContractState, verification_job_id: felt252 + ) -> Option; } #[starknet::contract] @@ -295,10 +297,10 @@ mod Verifier { } } - fn get_verification_job( + fn check_verification_job_status( self: @ContractState, verification_job_id: felt252 - ) -> VerificationJob { - self.verification_queue.read(verification_job_id).inner + ) -> Option { + self.verification_queue.read(verification_job_id).inner.verified } } diff --git a/tests/src/utils.rs b/tests/src/utils.rs index 51e53477..d7274984 100644 --- a/tests/src/utils.rs +++ b/tests/src/utils.rs @@ -17,7 +17,7 @@ use starknet::{ providers::Provider, }; use starknet_scripts::commands::utils::ScriptAccount; -use std::{env, iter, sync::Once}; +use std::{env, sync::Once}; use tracing::debug; use tracing_subscriber::{fmt, EnvFilter}; @@ -259,13 +259,36 @@ impl MatchPayload { } } +pub struct CircuitParams { + pub n: usize, + pub n_plus: usize, + pub k: usize, + pub q: usize, + pub m: usize, + pub b: StarkPoint, + pub b_blind: StarkPoint, + pub w_l: SparseReducedMatrix, + pub w_r: SparseReducedMatrix, + pub w_o: SparseReducedMatrix, + pub w_v: SparseReducedMatrix, + pub c: SparseWeightRow, +} + pub trait CalldataSerializable { fn to_calldata(&self) -> Vec; } +impl CalldataSerializable for usize { + fn to_calldata(&self) -> Vec { + vec![FieldElement::from(*self)] + } +} + impl CalldataSerializable for Vec { fn to_calldata(&self) -> Vec { - iter::once(FieldElement::from(self.len())) + self.len() + .to_calldata() + .into_iter() .chain(self.iter().flat_map(|t| t.to_calldata())) .collect() } @@ -274,7 +297,9 @@ impl CalldataSerializable for Vec { // `(usize, Scalar)` represents an entry in a `SparseWeightRow` impl CalldataSerializable for (usize, Scalar) { fn to_calldata(&self) -> Vec { - iter::once(FieldElement::from(self.0)) + self.0 + .to_calldata() + .into_iter() .chain(self.1.to_calldata().into_iter()) .collect() } @@ -377,3 +402,19 @@ impl CalldataSerializable for MatchPayload { .collect() } } + +impl CalldataSerializable for CircuitParams { + fn to_calldata(&self) -> Vec { + [self.n, self.n_plus, self.k, self.q, self.m] + .iter() + .flat_map(|s| s.to_calldata()) + .chain([self.b, self.b_blind].iter().flat_map(|s| s.to_calldata())) + .chain( + [&self.w_l, &self.w_r, &self.w_o, &self.w_v] + .iter() + .flat_map(|s| s.to_calldata()), + ) + .chain(self.c.to_calldata().into_iter()) + .collect() + } +} diff --git a/tests/src/verifier/utils.rs b/tests/src/verifier/utils.rs index bdcc677f..ed378bbd 100644 --- a/tests/src/verifier/utils.rs +++ b/tests/src/verifier/utils.rs @@ -1,3 +1,4 @@ +use dojo_test_utils::sequencer::TestSequencer; use eyre::{eyre, Result}; use merlin::HashChainTranscript; use mpc_bulletproof::{ @@ -5,10 +6,155 @@ use mpc_bulletproof::{ BulletproofGens, PedersenGens, }; use mpc_stark::algebra::{scalar::Scalar, stark_curve::StarkPoint}; +use once_cell::sync::OnceCell; use rand::thread_rng; +use starknet::core::types::{DeclareTransactionResult, FieldElement}; +use starknet_scripts::commands::utils::{ + calculate_contract_address, declare, deploy, get_artifacts, initialize, ScriptAccount, +}; +use std::{env, iter}; use tracing::debug; -use crate::utils::TRANSCRIPT_SEED; +use crate::utils::{ + call_contract, global_setup, invoke_contract, CalldataSerializable, CircuitParams, + ARTIFACTS_PATH_ENV_VAR, TRANSCRIPT_SEED, +}; + +const VERIFIER_CONTRACT_NAME: &str = "renegade_contracts_Verifier"; + +const QUEUE_VERIFICATION_JOB_FN_NAME: &str = "queue_verification_job"; +const STEP_VERIFICATION_FN_NAME: &str = "step_verification"; +const CHECK_VERIFICATION_JOB_STATUS_FN_NAME: &str = "check_verification_job_status"; + +static VERIFIER_ADDRESS: OnceCell = OnceCell::new(); + +// --------------------- +// | META TEST HELPERS | +// --------------------- + +pub async fn setup_verifier_test<'t, 'g>( + verifier: &mut Verifier<'t, 'g>, + pc_gens: PedersenGens, +) -> Result { + let artifacts_path = env::var(ARTIFACTS_PATH_ENV_VAR).unwrap(); + + let sequencer = global_setup().await; + let account = sequencer.account(); + + debug!("Declaring & deploying verifier contract..."); + let verifier_address = deploy_verifier(artifacts_path, &account).await?; + if VERIFIER_ADDRESS.get().is_none() { + // When running multiple tests, it's possible for the OnceCell to already be set. + // However, we still want to deploy the contract, since each test gets its own sequencer. + VERIFIER_ADDRESS.set(verifier_address).unwrap(); + } + + debug!("Initializing verifier contract..."); + initialize_verifier(&account, verifier_address, verifier, pc_gens).await?; + + Ok(sequencer) +} + +pub async fn deploy_verifier( + artifacts_path: String, + account: &ScriptAccount, +) -> Result { + let (verifier_sierra_path, verifier_casm_path) = + get_artifacts(&artifacts_path, VERIFIER_CONTRACT_NAME); + let DeclareTransactionResult { class_hash, .. } = + declare(verifier_sierra_path, verifier_casm_path, account).await?; + + deploy(account, class_hash, &[]).await?; + Ok(calculate_contract_address(class_hash, &[])) +} + +// -------------------------------- +// | CONTRACT INTERACTION HELPERS | +// -------------------------------- + +pub async fn initialize_verifier<'t, 'g>( + account: &ScriptAccount, + verifier_address: FieldElement, + verifier: &Verifier<'t, 'g>, + pc_gens: PedersenGens, +) -> Result<()> { + let circuit_weights = verifier.get_weights(); + let circuit_params = CircuitParams { + n: DUMMY_CIRCUIT_N, + n_plus: DUMMY_CIRCUIT_N_PLUS, + k: DUMMY_CIRCUIT_K, + q: DUMMY_CIRCUIT_Q, + m: DUMMY_CIRCUIT_M, + b: pc_gens.B, + b_blind: pc_gens.B_blinding, + w_l: circuit_weights.w_l, + w_o: circuit_weights.w_o, + w_r: circuit_weights.w_r, + w_v: circuit_weights.w_v, + c: circuit_weights.c, + }; + let calldata = circuit_params.to_calldata(); + + initialize(account, verifier_address, calldata) + .await + .map(|_| ()) +} + +pub async fn queue_verification_job( + account: &ScriptAccount, + proof: &R1CSProof, + witness_commitments: &Vec, + verification_job_id: FieldElement, +) -> Result<()> { + let calldata = proof + .to_calldata() + .into_iter() + .chain(witness_commitments.to_calldata().into_iter()) + .chain(iter::once(verification_job_id)) + .collect(); + + invoke_contract( + account, + *VERIFIER_ADDRESS.get().unwrap(), + QUEUE_VERIFICATION_JOB_FN_NAME, + calldata, + ) + .await +} + +pub async fn step_verification( + account: &ScriptAccount, + verification_job_id: FieldElement, +) -> Result<()> { + invoke_contract( + account, + *VERIFIER_ADDRESS.get().unwrap(), + STEP_VERIFICATION_FN_NAME, + vec![verification_job_id], + ) + .await +} + +pub async fn check_verification_job_status( + account: &ScriptAccount, + verification_job_id: FieldElement, +) -> Result> { + call_contract( + account, + *VERIFIER_ADDRESS.get().unwrap(), + CHECK_VERIFICATION_JOB_STATUS_FN_NAME, + vec![verification_job_id], + ) + .await + .map(|r| { + if r[0] == FieldElement::ONE { + // This is how Cairo serializes an Option::None + None + } else { + Some(r[1] == FieldElement::ONE) + } + }) +} // ------------------------- // | DUMMY CIRCUIT HELPERS |