diff --git a/circuits/integration/zk_circuits/valid_match_mpc.rs b/circuits/integration/zk_circuits/valid_match_mpc.rs index ef014b461..6beb73247 100644 --- a/circuits/integration/zk_circuits/valid_match_mpc.rs +++ b/circuits/integration/zk_circuits/valid_match_mpc.rs @@ -336,356 +336,8 @@ async fn test_valid_match__non_integral_price(test_args: IntegrationTestArgs) -> Ok(()) } -/// Test the case in which the two parties attempt to match on different mints -async fn test_valid_match__different_mints(test_args: IntegrationTestArgs) -> Result<()> { - let fabric = &test_args.mpc_fabric; - let party_id = fabric.party_id(); - let price = *DUMMY_PRICE; - let mut my_order = create_test_order(party_id); - let my_balance = create_test_balance(party_id); - let amount = compute_max_amount(price, &my_order, &my_balance); - - // One party switches the quote mint of their order - if fabric.party_id() == PARTY0 { - my_order.quote_mint = 42u8.into(); - } - - // Validate that the constraints are not satisfied - let witness = setup_witness( - price, - amount, - my_order, - my_balance.clone(), - test_args.mpc_fabric.clone(), - ) - .await; - if constraints_satisfied(witness, test_args.mpc_fabric.clone()).await? { - return Err(eyre!("Constraints satisfied on invalid witness")); - } - - // Now test with base mint switched - let mut my_order = create_test_order(party_id); - if fabric.party_id() == PARTY0 { - my_order.base_mint = 42u8.into(); - } - - // Validate that the constraints are not satisfied - let witness = setup_witness( - price, - amount, - my_order, - my_balance, - test_args.mpc_fabric.clone(), - ) - .await; - if constraints_satisfied(witness, test_args.mpc_fabric.clone()).await? { - return Err(eyre!("Constraints satisfied on invalid witness")); - } - - Ok(()) -} - -/// Test the case in which the parties sit on the same side of the book -async fn test_valid_match__same_side(test_args: IntegrationTestArgs) -> Result<()> { - let fabric = &test_args.mpc_fabric; - let party_id = fabric.party_id(); - let price = *DUMMY_PRICE; - let mut my_order = create_test_order(party_id); - let my_balance = create_test_balance(party_id); - let amount = compute_max_amount(price, &my_order, &my_balance); - - // Switch the side of the market that the first party sits on - if fabric.party_id() == PARTY0 { - my_order.side = my_order.side.opposite(); - } - - // Validate that the constraints are not satisfied - let witness = setup_witness( - price, - amount, - my_order, - my_balance, - test_args.mpc_fabric.clone(), - ) - .await; - if constraints_satisfied(witness, test_args.mpc_fabric.clone()).await? { - return Err(eyre!("Constraints satisfied on invalid witness")); - } - - Ok(()) -} - -/// Test the case in which the balance provided to the matching engine is not -/// for the correct asset -async fn test_valid_match__invalid_balance_mint(test_args: IntegrationTestArgs) -> Result<()> { - let fabric = &test_args.mpc_fabric; - let party_id = fabric.party_id(); - let price = *DUMMY_PRICE; - let my_order = create_test_order(party_id); - let mut my_balance = create_test_balance(party_id); - let amount = compute_max_amount(price, &my_order, &my_balance); - - // Switch the mint of the balance to be the wrong asset in the pair - if party_id == PARTY0 { - if my_balance.mint == my_order.quote_mint { - my_balance.mint = my_order.base_mint.clone(); - } else { - my_balance.mint = my_order.quote_mint.clone(); - } - } - - // Validate that the constraints are not satisfied - let witness = setup_witness( - price, - amount, - my_order, - my_balance, - test_args.mpc_fabric.clone(), - ) - .await; - if constraints_satisfied(witness, test_args.mpc_fabric.clone()).await? { - return Err(eyre!("Constraints satisfied on invalid witness")); - } - - Ok(()) -} - -/// Test the case in which the balance provided does not cover the advertised -/// amount -async fn test_valid_match__insufficient_balance(test_args: IntegrationTestArgs) -> Result<()> { - let fabric = &test_args.mpc_fabric; - let party_id = fabric.party_id(); - let price = *DUMMY_PRICE; - let my_order = create_test_order(party_id); - let mut my_balance = create_test_balance(party_id); - let amount = compute_max_amount(price, &my_order, &my_balance); - - // Reduce the balance to be less than the amount - if my_balance.mint == my_order.base_mint { - my_balance.amount = 1; - } - - // Validate that the constraints are not satisfied - let witness = setup_witness( - price, - amount, - my_order, - my_balance, - test_args.mpc_fabric.clone(), - ) - .await; - if constraints_satisfied(witness, test_args.mpc_fabric.clone()).await? { - return Err(eyre!("Constraints satisfied on invalid witness")); - } - - Ok(()) -} - -/// Test the case in which the matched amount exceeds the order size for a party -async fn test_valid_match__amount_exceeds_order(test_args: IntegrationTestArgs) -> Result<()> { - let fabric = &test_args.mpc_fabric; - let party_id = fabric.party_id(); - let price = *DUMMY_PRICE; - let my_order = create_test_order(party_id); - let my_balance = create_test_balance(party_id); - - // Both parties give an excessive amount so that the minimum amount certainly - // exceeds the order size - let amount = my_order.amount + 1; - - // Validate that the constraints are not satisfied - let witness = setup_witness( - price, - amount, - my_order, - my_balance, - test_args.mpc_fabric.clone(), - ) - .await; - if constraints_satisfied(witness, test_args.mpc_fabric.clone()).await? { - return Err(eyre!("Constraints satisfied on invalid witness")); - } - - Ok(()) -} - -/// Test the case in which the `max_minus_min` field is incorrectly computed -async fn test_valid_match__incorrect_max_minus_min(test_args: IntegrationTestArgs) -> Result<()> { - let fabric = &test_args.mpc_fabric; - let party_id = fabric.party_id(); - let price = *DUMMY_PRICE; - let my_order = create_test_order(party_id); - let my_balance = create_test_balance(party_id); - let amount = compute_max_amount(price, &my_order, &my_balance); - - // Validate that the constraints are not satisfied - let mut witness = setup_witness( - price, - amount, - my_order, - my_balance, - test_args.mpc_fabric.clone(), - ) - .await; - let mut rng = thread_rng(); - witness.match_res.max_minus_min_amount = Scalar::random(&mut rng) - .to_linkable() - .allocate(PARTY0, fabric); - - if constraints_satisfied(witness, test_args.mpc_fabric.clone()).await? { - return Err(eyre!("Constraints satisfied on invalid witness")); - } - - Ok(()) -} - -/// Test the case in which the `max_minus_min` field is switched to be -/// `min - max` (i.e. negative) -async fn test_valid_match__max_minus_min_negative(test_args: IntegrationTestArgs) -> Result<()> { - let fabric = &test_args.mpc_fabric; - let party_id = fabric.party_id(); - let price = *DUMMY_PRICE; - let my_order = create_test_order(party_id); - let my_balance = create_test_balance(party_id); - let amount = compute_max_amount(price, &my_order, &my_balance); - - let party0_amount = amount - .share_public(PARTY0, test_args.mpc_fabric.clone()) - .await; - let party1_amount = amount - .share_public(PARTY1, test_args.mpc_fabric.clone()) - .await; - let min_minus_max_amount = Scalar::from(cmp::min(party0_amount, party1_amount)) - - Scalar::from(cmp::max(party0_amount, party1_amount)); - - // Validate that the constraints are not satisfied - let mut witness = setup_witness( - price, - amount, - my_order, - my_balance, - test_args.mpc_fabric.clone(), - ) - .await; - witness.match_res.max_minus_min_amount = - min_minus_max_amount.to_linkable().allocate(PARTY0, fabric); - - if constraints_satisfied(witness, test_args.mpc_fabric.clone()).await? { - return Err(eyre!("Constraints satisfied on invalid witness")); - } - - Ok(()) -} - -/// Test the case in which the `min_amount_order_index` field is incorrectly -/// computed -async fn test_valid_match__incorrect_min_amount_order_index( - test_args: IntegrationTestArgs, -) -> Result<()> { - let fabric = &test_args.mpc_fabric; - let party_id = fabric.party_id(); - let price = *DUMMY_PRICE; - let my_order = create_test_order(party_id); - let my_balance = create_test_balance(party_id); - let amount = compute_max_amount(price, &my_order, &my_balance); - - // Validate that the constraints are not satisfied - let mut witness = setup_witness( - price, - amount, - my_order, - my_balance, - test_args.mpc_fabric.clone(), - ) - .await; - let min_order_index = witness.match_res.min_amount_order_index.open().await?.val; - - // Invert the index - witness.match_res.min_amount_order_index = (Scalar::one() - min_order_index) - .to_linkable() - .allocate(PARTY0, fabric); - - if constraints_satisfied(witness, test_args.mpc_fabric.clone()).await? { - return Err(eyre!("Constraints satisfied on invalid witness")); - } - - Ok(()) -} - -/// Test the case in which the execution price exceeds the buy side price -/// protection -async fn test_valid_match__price_protection_violated_buy_side( - test_args: IntegrationTestArgs, -) -> Result<()> { - let fabric = &test_args.mpc_fabric; - let party_id = fabric.party_id(); - let my_order = create_test_order(party_id); - let my_balance = create_test_balance(party_id); - - // Execution price exceeds the buy side maximum price - let price = *DUMMY_BUY_SIDE_WORST_PRICE + FixedPoint::from_integer(1); - let amount = compute_max_amount(price, &my_order, &my_balance); - - // Validate that the constraints are not satisfied - let witness = setup_witness( - price, - amount, - my_order, - my_balance, - test_args.mpc_fabric.clone(), - ) - .await; - - if constraints_satisfied(witness, test_args.mpc_fabric.clone()).await? { - return Err(eyre!("Constraints satisfied on invalid witness")); - } - - Ok(()) -} - -/// Test the case in which the execution price falls sort of the sell -/// side price protection -async fn test_valid_match__price_protection_violated_sell_side( - test_args: IntegrationTestArgs, -) -> Result<()> { - let fabric = &test_args.mpc_fabric; - let party_id = fabric.party_id(); - let my_order = create_test_order(party_id); - let my_balance = create_test_balance(party_id); - - // Execution price falls short of the sell side minimum price - let price = *DUMMY_SELL_SIDE_WORST_PRICE - FixedPoint::from_integer(1); - let amount = compute_max_amount(price, &my_order, &my_balance); - - // Validate that the constraints are not satisfied - let witness = setup_witness( - price, - amount, - my_order, - my_balance, - test_args.mpc_fabric.clone(), - ) - .await; - - if constraints_satisfied(witness, test_args.mpc_fabric.clone()).await? { - return Err(eyre!("Constraints satisfied on invalid witness")); - } - - Ok(()) -} - // Take inventory integration_test_async!(test_valid_match_mpc_valid); integration_test_async!(test_valid_match_undercapitalized__base_side); integration_test_async!(test_valid_match_undercapitalized__quote_side); integration_test_async!(test_valid_match__non_integral_price); -integration_test_async!(test_valid_match__different_mints); -integration_test_async!(test_valid_match__same_side); -integration_test_async!(test_valid_match__invalid_balance_mint); -integration_test_async!(test_valid_match__insufficient_balance); -integration_test_async!(test_valid_match__amount_exceeds_order); -integration_test_async!(test_valid_match__incorrect_max_minus_min); -integration_test_async!(test_valid_match__max_minus_min_negative); -integration_test_async!(test_valid_match__incorrect_min_amount_order_index); -integration_test_async!(test_valid_match__price_protection_violated_buy_side); -integration_test_async!(test_valid_match__price_protection_violated_sell_side); diff --git a/circuits/src/zk_circuits/valid_match_settle/mod.rs b/circuits/src/zk_circuits/valid_match_settle/mod.rs index ebad81da4..331bc48e4 100644 --- a/circuits/src/zk_circuits/valid_match_settle/mod.rs +++ b/circuits/src/zk_circuits/valid_match_settle/mod.rs @@ -358,9 +358,10 @@ pub mod test_helpers { mod tests { #![allow(non_snake_case)] use ark_mpc::PARTY0; - use circuit_types::traits::MpcBaseType; + use circuit_types::{fixed_point::FixedPoint, order::OrderSide, traits::MpcBaseType}; use constants::Scalar; + use rand::{thread_rng, Rng, RngCore}; use test_helpers::mpc_network::execute_mock_mpc; use crate::{ @@ -394,6 +395,174 @@ mod tests { assert!(res.is_ok()) } + // --------------- + // | Match Tests | + // --------------- + + /// Randomly perform one of two operation + macro_rules! rand_branch { + ($x:expr, $y:expr) => { + if rand::thread_rng().gen_bool(0.5) { + $x; + } else { + $y; + } + }; + } + + /// Test the case in which the two parties attempt to match on different + /// mints + #[test] + fn test_valid_match__different_mints() { + let (original_witness, statement) = dummy_witness_and_statement(); + + // One party switches the quote mint of their order + let mut witness = original_witness.clone(); + rand_branch!( + witness.order1.quote_mint += 1u8, + witness.order2.quote_mint += 1u8 + ); + + // Validate that the constraints are not satisfied + assert!(!check_constraint_satisfaction::( + &witness, &statement + )); + + // Now test with base mint switched + let mut witness = original_witness; + rand_branch!( + witness.order1.base_mint += 1u8, + witness.order2.base_mint += 1u8 + ); + assert!(!check_constraint_satisfaction::( + &witness, &statement + )); + } + + /// Test the case in which the parties sit on the same side of the book + #[test] + fn test_valid_match__same_side() { + let (mut witness, statement) = dummy_witness_and_statement(); + + // Swap the sides of one of the orders + rand_branch!( + witness.order1.side = witness.order1.side.opposite(), + witness.order2.side = witness.order2.side.opposite() + ); + assert!(!check_constraint_satisfaction::( + &witness, &statement + )); + } + + /// Test the case in which the balance provided to the matching engine is + /// not for the correct asset + #[test] + fn test_valid_match__invalid_balance_mint() { + let (mut witness, statement) = dummy_witness_and_statement(); + + // Corrupt the mint + rand_branch!(witness.balance1.mint += 1u8, witness.balance2.mint += 1u8); + assert!(!check_constraint_satisfaction::( + &witness, &statement + )); + } + + /// Test the case in which the balance provided does not cover the + /// advertised amount + #[test] + fn test_valid_match__insufficient_balance() { + let (mut witness, statement) = dummy_witness_and_statement(); + + // Reduce the balance to be less than the amount matched + rand_branch!(witness.balance1.amount = 1, witness.balance2.amount = 1); + assert!(!check_constraint_satisfaction::( + &witness, &statement + )); + } + + /// Test the case in which the matched amount exceeds the order size for a + /// party + #[test] + fn test_valid_match__amount_exceeds_order() { + let (mut witness, statement) = dummy_witness_and_statement(); + + // Place one party's order amount below the min amount + rand_branch!( + witness.order1.amount = witness.match_res.base_amount - 1, + witness.order2.amount = witness.match_res.base_amount - 1 + ); + + assert!(!check_constraint_satisfaction::( + &witness, &statement + )); + } + + /// Test the case in which the `max_minus_min` field is incorrectly computed + #[test] + fn test_valid_match__incorrect_max_minus_min() { + let mut rng = thread_rng(); + let (mut witness, statement) = dummy_witness_and_statement(); + + // Change the max minus min amount + witness.match_res.max_minus_min_amount = rng.next_u64(); + assert!(!check_constraint_satisfaction::( + &witness, &statement + )); + } + + /// Test the case in which the `min_amount_order_index` field is incorrectly + /// computed + #[test] + fn test_valid_match__incorrect_min_amount_order_index() { + let (mut witness, statement) = dummy_witness_and_statement(); + + // Invert the index + witness.match_res.min_amount_order_index = !witness.match_res.min_amount_order_index; + assert!(!check_constraint_satisfaction::( + &witness, &statement + )); + } + + /// Test the case in which the execution price exceeds the buy side price + /// protection + #[test] + fn test_valid_match__price_protection_violated_buy_side() { + let (mut witness, statement) = dummy_witness_and_statement(); + let execution_price = + (witness.match_res.quote_amount as f64) / (witness.match_res.base_amount as f64); + + // Execution price exceeds the buy side maximum price + let new_price = FixedPoint::from_f64_round_down(execution_price - 1.); + match witness.order1.side { + OrderSide::Buy => witness.price1 = new_price, + OrderSide::Sell => witness.price2 = new_price, + }; + + assert!(!check_constraint_satisfaction::( + &witness, &statement + )); + } + + /// Test the case in which the execution price falls sort of the sell + /// side price protection + #[test] + fn test_valid_match__price_protection_violated_sell_side() { + let (mut witness, statement) = dummy_witness_and_statement(); + let execution_price = + (witness.match_res.quote_amount as f64) / (witness.match_res.base_amount as f64); + + // Execution price exceeds the buy side maximum price + let new_price = FixedPoint::from_f64_round_down(execution_price + 1.); + match witness.order1.side { + OrderSide::Sell => witness.price1 = new_price, + OrderSide::Buy => witness.price2 = new_price, + }; + + assert!(!check_constraint_satisfaction::( + &witness, &statement + )); + } + // -------------------- // | Settlement Tests | // --------------------