From db0c85a98928e8fde3fcddb742f9218ed16529f9 Mon Sep 17 00:00:00 2001 From: afurious Date: Sat, 28 Mar 2026 13:00:02 +0100 Subject: [PATCH] Add comprehensive majority threshold boundary tests - Add table-driven tests for exact 6000 bps (60%) threshold validation - Test boundary cases: 59.99%, 60.00%, 60.01% with precise assertions - Add precision tests with large numbers to validate calculation accuracy - Ensure NoMajorityReached error for failing cases - Covers edge cases including ties and overwhelming majorities Fixes testing gap for majority threshold boundary behavior in resolution module --- contracts/predict-iq/Cargo.toml | 1 + .../src/test_multi_outcome_voting.rs | 157 ++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/contracts/predict-iq/Cargo.toml b/contracts/predict-iq/Cargo.toml index 2ac57ec..25da3f2 100644 --- a/contracts/predict-iq/Cargo.toml +++ b/contracts/predict-iq/Cargo.toml @@ -19,3 +19,4 @@ testutils = ["soroban-sdk/testutils"] [[test]] name = "gas_benchmark" path = "benches/gas_benchmark.rs" + diff --git a/contracts/predict-iq/src/test_multi_outcome_voting.rs b/contracts/predict-iq/src/test_multi_outcome_voting.rs index 8ce6dc4..1268013 100644 --- a/contracts/predict-iq/src/test_multi_outcome_voting.rs +++ b/contracts/predict-iq/src/test_multi_outcome_voting.rs @@ -447,6 +447,163 @@ fn test_three_outcomes_just_below_60_percent_threshold() { client.finalize_resolution(&market_id); } +/// Table-driven tests for exact majority threshold boundary behavior +/// Validates the 6000 bps (60%) threshold with precise boundary assertions +#[test] +fn test_majority_threshold_boundary_behavior() { + let (e, _admin, _contract_id, client) = setup_test_env(); + + // Setup governance token + let token_admin = Address::generate(&e); + let token_id = e.register_stellar_asset_contract_v2(token_admin.clone()); + let token_address = token_id.address(); + let token_client = token::StellarAssetClient::new(&e, &token_address); + + client.set_governance_token(&token_address); + + // Test cases: (outcome1_votes, outcome0_votes, expected_success, description) + let test_cases = vec![ + // Just below threshold: 59.99% vs 40.01% + (5999, 4001, false, "59.99% - should fail (below 60% threshold)"), + + // Exactly at threshold: 60.00% vs 40.00% + (6000, 4000, true, "60.00% - should succeed (exactly at threshold)"), + + // Just above threshold: 60.01% vs 39.99% + (6001, 3999, true, "60.01% - should succeed (above 60% threshold)"), + + // Clear majority: 75.00% vs 25.00% + (7500, 2500, true, "75.00% - should succeed (clear majority)"), + + // Far below threshold: 50.00% vs 50.00% + (5000, 5000, false, "50.00% - should fail (tie, below threshold)"), + + // Edge case: 99.99% vs 0.01% + (9999, 1, true, "99.99% - should succeed (overwhelming majority)"), + ]; + + for (outcome1_votes, outcome0_votes, expected_success, description) in test_cases { + // Create fresh market for each test case + let resolution_deadline = 2000; + let market_id = create_multi_outcome_market(&client, &e, 2, resolution_deadline); + + client.set_oracle_result(&market_id, &0); + + e.ledger().with_mut(|li| { + li.timestamp = resolution_deadline; + }); + + client.attempt_oracle_resolution(&market_id); + + // File dispute + let disputer = Address::generate(&e); + e.ledger().with_mut(|li| { + li.timestamp = resolution_deadline + 10000; + }); + + client.file_dispute(&disputer, &market_id); + + // Cast votes according to test case + let voter1 = Address::generate(&e); + let voter2 = Address::generate(&e); + + token_client.mint(&voter1, &outcome1_votes); + token_client.mint(&voter2, &outcome0_votes); + + client.cast_vote(&voter1, &market_id, &1, &outcome1_votes); + client.cast_vote(&voter2, &market_id, &0, &outcome0_votes); + + // Advance time by 72 hours + e.ledger().with_mut(|li| { + li.timestamp = resolution_deadline + 10000 + 259200; + }); + + // Test the finalization result + let result = client.try_finalize_resolution(&market_id); + + if expected_success { + // Should succeed + assert!(result.is_ok(), "Test case failed: {} - expected success but got error {:?}", description, result); + + let market = client.get_market(&market_id).unwrap(); + assert_eq!(market.status, types::MarketStatus::Resolved); + assert_eq!(market.winning_outcome, Some(1)); + } else { + // Should fail with NoMajorityReached error + assert_eq!(result, Err(Ok(ErrorCode::NoMajorityReached)), + "Test case failed: {} - expected NoMajorityReached error but got {:?}", description, result); + } + } +} + +/// Test precise threshold calculation with larger numbers to validate division precision +#[test] +fn test_majority_threshold_precision_with_large_numbers() { + let (e, _admin, _contract_id, client) = setup_test_env(); + + // Setup governance token + let token_admin = Address::generate(&e); + let token_id = e.register_stellar_asset_contract_v2(token_admin.clone()); + let token_address = token_id.address(); + let token_client = token::StellarAssetClient::new(&e, &token_address); + + client.set_governance_token(&token_address); + + // Test with larger numbers to ensure precision is maintained + let large_test_cases = vec![ + // Scale up by 1000x: 59.99% vs 40.01% + (5_999_000, 4_001_000, false, "59.99% with large numbers - should fail"), + + // Scale up by 1000x: 60.00% vs 40.00% + (6_000_000, 4_000_000, true, "60.00% with large numbers - should succeed"), + + // Scale up by 1000x: 60.01% vs 39.99% + (6_001_000, 3_999_000, true, "60.01% with large numbers - should succeed"), + ]; + + for (outcome1_votes, outcome0_votes, expected_success, description) in large_test_cases { + let resolution_deadline = 2000; + let market_id = create_multi_outcome_market(&client, &e, 2, resolution_deadline); + + client.set_oracle_result(&market_id, &0); + + e.ledger().with_mut(|li| { + li.timestamp = resolution_deadline; + }); + + client.attempt_oracle_resolution(&market_id); + + let disputer = Address::generate(&e); + e.ledger().with_mut(|li| { + li.timestamp = resolution_deadline + 10000; + }); + + client.file_dispute(&disputer, &market_id); + + let voter1 = Address::generate(&e); + let voter2 = Address::generate(&e); + + token_client.mint(&voter1, &outcome1_votes); + token_client.mint(&voter2, &outcome0_votes); + + client.cast_vote(&voter1, &market_id, &1, &outcome1_votes); + client.cast_vote(&voter2, &market_id, &0, &outcome0_votes); + + e.ledger().with_mut(|li| { + li.timestamp = resolution_deadline + 10000 + 259200; + }); + + let result = client.try_finalize_resolution(&market_id); + + if expected_success { + assert!(result.is_ok(), "Large number test failed: {} - expected success", description); + } else { + assert_eq!(result, Err(Ok(ErrorCode::NoMajorityReached)), + "Large number test failed: {} - expected NoMajorityReached", description); + } + } +} + /// Test single voter with 100% of votes #[test] fn test_three_outcomes_single_voter_100_percent() {