diff --git a/contracts/contracts/boxmeout/src/market.rs b/contracts/contracts/boxmeout/src/market.rs index 157415f..c92ba57 100644 --- a/contracts/contracts/boxmeout/src/market.rs +++ b/contracts/contracts/boxmeout/src/market.rs @@ -98,6 +98,21 @@ pub struct UserPredictionResult { pub predicted_outcome: u32, } +/// Market state summary for external queries +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MarketState { + pub status: u32, + pub closing_time: u64, + pub resolution_time: u64, + pub total_pool: i128, + pub yes_pool: i128, + pub no_pool: i128, + pub participant_count: u32, + pub winning_outcome: Option, + pub total_volume: i128, +} + /// PREDICTION MARKET - Manages individual market logic #[contract] pub struct PredictionMarket; @@ -687,17 +702,81 @@ impl PredictionMarket { /// Get market summary data /// - /// TODO: Get Market State - /// - Query market metadata from storage - /// - Return: market_id, creator, category, title, description - /// - Include timing: creation_time, closing_time, resolution_time, time_remaining - /// - Include current state: OPEN/CLOSED/RESOLVED/DISPUTED - /// - Include pools: yes_volume, no_volume, total_volume - /// - Include odds: yes_odds, no_odds - /// - Include resolution: winning_outcome (if resolved), timestamp - /// - Include user-specific data if user provided: their prediction, potential winnings - pub fn get_market_state(_env: Env, _market_id: BytesN<32>) -> Symbol { - todo!("See get market state TODO above") + /// Returns comprehensive market state including status, timing, pools, and resolution data. + /// This is a read-only function that requires no authentication. + /// + /// Returns: + /// - status: Market state (0=OPEN, 1=CLOSED, 2=RESOLVED) + /// - closing_time: Unix timestamp when market closes for predictions + /// - resolution_time: Unix timestamp when market can be resolved + /// - total_pool: Combined YES + NO pool amounts + /// - yes_pool: Total amount in YES pool + /// - no_pool: Total amount in NO pool + /// - participant_count: Number of pending predictions + /// - winning_outcome: Resolved outcome (Some(0/1)) or None if unresolved + /// - total_volume: Cumulative trading volume + pub fn get_market_state(env: Env, _market_id: BytesN<32>) -> MarketState { + // Query all market data from storage + let status: u32 = env + .storage() + .persistent() + .get(&Symbol::new(&env, MARKET_STATE_KEY)) + .unwrap_or(STATE_OPEN); + + let closing_time: u64 = env + .storage() + .persistent() + .get(&Symbol::new(&env, CLOSING_TIME_KEY)) + .unwrap_or(0); + + let resolution_time: u64 = env + .storage() + .persistent() + .get(&Symbol::new(&env, RESOLUTION_TIME_KEY)) + .unwrap_or(0); + + let yes_pool: i128 = env + .storage() + .persistent() + .get(&Symbol::new(&env, YES_POOL_KEY)) + .unwrap_or(0); + + let no_pool: i128 = env + .storage() + .persistent() + .get(&Symbol::new(&env, NO_POOL_KEY)) + .unwrap_or(0); + + let total_pool = yes_pool + no_pool; + + let participant_count: u32 = env + .storage() + .persistent() + .get(&Symbol::new(&env, PENDING_COUNT_KEY)) + .unwrap_or(0); + + let winning_outcome: Option = env + .storage() + .persistent() + .get(&Symbol::new(&env, WINNING_OUTCOME_KEY)); + + let total_volume: i128 = env + .storage() + .persistent() + .get(&Symbol::new(&env, TOTAL_VOLUME_KEY)) + .unwrap_or(0); + + MarketState { + status, + closing_time, + resolution_time, + total_pool, + yes_pool, + no_pool, + participant_count, + winning_outcome, + total_volume, + } } /// Get prediction records for a user in this market @@ -1376,14 +1455,12 @@ mod tests { let market_contract_id = env.register(PredictionMarket, ()); let market_client = PredictionMarketClient::new(&env, &market_contract_id); let oracle_contract_id = env.register(MockOracle, ()); - let token_admin = Address::generate(&env); - let usdc_client = create_token_contract(&env, &token_admin); market_client.initialize( &market_id_bytes, &Address::generate(&env), &Address::generate(&env), - &usdc_client.address, + &Address::generate(&env), &oracle_contract_id, &2000, &3000, @@ -1391,7 +1468,8 @@ mod tests { let user = Address::generate(&env); let result = market_client.get_user_prediction(&user, &market_id_bytes); - assert!(result.is_none()); + + assert_eq!(result, None); } #[test] @@ -1417,20 +1495,19 @@ mod tests { ); let user = Address::generate(&env); - let amount = 100_000_000i128; - let commit_hash = BytesN::from_array(&env, &[5u8; 32]); + let commit_hash = BytesN::from_array(&env, &[1u8; 32]); + usdc_client.mint(&user, &1000); - usdc_client.mint(&user, &amount); - usdc_client.approve(&user, &market_contract_id, &amount, &100); - market_client.commit_prediction(&user, &commit_hash, &amount); + market_client.commit_prediction(&user, &commit_hash, &500); let result = market_client.get_user_prediction(&user, &market_id_bytes); + assert!(result.is_some()); - let r = result.unwrap(); - assert_eq!(r.commitment_hash, commit_hash); - assert_eq!(r.amount, amount); - assert_eq!(r.status, PREDICTION_STATUS_COMMITTED); - assert_eq!(r.predicted_outcome, PREDICTION_OUTCOME_NONE); + let pred = result.unwrap(); + assert_eq!(pred.commitment_hash, commit_hash); + assert_eq!(pred.amount, 500); + assert_eq!(pred.status, PREDICTION_STATUS_COMMITTED); + assert_eq!(pred.predicted_outcome, PREDICTION_OUTCOME_NONE); } #[test] @@ -1456,22 +1533,26 @@ mod tests { ); let user = Address::generate(&env); - let amount = 500_000_000i128; - let outcome = 1u32; // YES + usdc_client.mint(&user, &1000); - market_client.test_set_prediction(&user, &outcome, &amount); + // Manually set revealed prediction + market_client.test_set_prediction(&user, &1u32, &500); let result = market_client.get_user_prediction(&user, &market_id_bytes); + assert!(result.is_some()); - let r = result.unwrap(); - assert_eq!(r.commitment_hash, BytesN::from_array(&env, &[0u8; 32])); - assert_eq!(r.amount, amount); - assert_eq!(r.status, PREDICTION_STATUS_REVEALED); - assert_eq!(r.predicted_outcome, outcome); + let pred = result.unwrap(); + assert_eq!(pred.amount, 500); + assert_eq!(pred.status, PREDICTION_STATUS_REVEALED); + assert_eq!(pred.predicted_outcome, 1); } + // ============================================================================ + // GET MARKET STATE TESTS + // ============================================================================ + #[test] - fn test_get_user_prediction_revealed_no_outcome() { + fn test_get_market_state_initial() { let env = Env::default(); env.mock_all_auths(); @@ -1479,26 +1560,259 @@ mod tests { let market_contract_id = env.register(PredictionMarket, ()); let market_client = PredictionMarketClient::new(&env, &market_contract_id); let oracle_contract_id = env.register(MockOracle, ()); - let token_admin = Address::generate(&env); - let usdc_client = create_token_contract(&env, &token_admin); + + let closing_time = 2000u64; + let resolution_time = 3000u64; market_client.initialize( &market_id_bytes, &Address::generate(&env), &Address::generate(&env), - &usdc_client.address, + &Address::generate(&env), + &oracle_contract_id, + &closing_time, + &resolution_time, + ); + + let state = market_client.get_market_state(&market_id_bytes); + + assert_eq!(state.status, STATE_OPEN); + assert_eq!(state.closing_time, closing_time); + assert_eq!(state.resolution_time, resolution_time); + assert_eq!(state.total_pool, 0); + assert_eq!(state.yes_pool, 0); + assert_eq!(state.no_pool, 0); + assert_eq!(state.participant_count, 0); + assert_eq!(state.winning_outcome, None); + assert_eq!(state.total_volume, 0); + } + + #[test] + fn test_get_market_state_with_predictions() { + let env = Env::default(); + env.mock_all_auths(); + + let market_id_bytes = BytesN::from_array(&env, &[0; 32]); + let market_contract_id = env.register(PredictionMarket, ()); + let market_client = PredictionMarketClient::new(&env, &market_contract_id); + let oracle_contract_id = env.register(MockOracle, ()); + + market_client.initialize( + &market_id_bytes, + &Address::generate(&env), + &Address::generate(&env), + &Address::generate(&env), &oracle_contract_id, &2000, &3000, ); - let user = Address::generate(&env); - market_client.test_set_prediction(&user, &0u32, &200i128); // NO outcome + // Manually set pool values to simulate predictions + env.storage() + .persistent() + .set(&Symbol::new(&env, YES_POOL_KEY), &1000i128); + env.storage() + .persistent() + .set(&Symbol::new(&env, NO_POOL_KEY), &500i128); + env.storage() + .persistent() + .set(&Symbol::new(&env, PENDING_COUNT_KEY), &5u32); + env.storage() + .persistent() + .set(&Symbol::new(&env, TOTAL_VOLUME_KEY), &1500i128); - let result = market_client.get_user_prediction(&user, &market_id_bytes); - assert!(result.is_some()); - let r = result.unwrap(); - assert_eq!(r.predicted_outcome, 0); - assert_eq!(r.amount, 200); + let state = market_client.get_market_state(&market_id_bytes); + + assert_eq!(state.status, STATE_OPEN); + assert_eq!(state.total_pool, 1500); + assert_eq!(state.yes_pool, 1000); + assert_eq!(state.no_pool, 500); + assert_eq!(state.participant_count, 5); + assert_eq!(state.total_volume, 1500); + assert_eq!(state.winning_outcome, None); + } + + #[test] + fn test_get_market_state_closed() { + let env = Env::default(); + env.mock_all_auths(); + + let market_id_bytes = BytesN::from_array(&env, &[0; 32]); + let market_contract_id = env.register(PredictionMarket, ()); + let market_client = PredictionMarketClient::new(&env, &market_contract_id); + let oracle_contract_id = env.register(MockOracle, ()); + + let closing_time = 2000u64; + + market_client.initialize( + &market_id_bytes, + &Address::generate(&env), + &Address::generate(&env), + &Address::generate(&env), + &oracle_contract_id, + &closing_time, + &3000, + ); + + // Advance time and close market + env.ledger().with_mut(|li| { + li.timestamp = closing_time + 10; + }); + market_client.close_market(&market_id_bytes); + + let state = market_client.get_market_state(&market_id_bytes); + + assert_eq!(state.status, STATE_CLOSED); + assert_eq!(state.winning_outcome, None); + } + + #[test] + fn test_get_market_state_resolved() { + let env = Env::default(); + env.mock_all_auths(); + + let market_id_bytes = BytesN::from_array(&env, &[0; 32]); + let market_contract_id = env.register(PredictionMarket, ()); + let market_client = PredictionMarketClient::new(&env, &market_contract_id); + let oracle_contract_id = env.register(MockOracle, ()); + let oracle_client = MockOracleClient::new(&env, &oracle_contract_id); + + let closing_time = 2000u64; + let resolution_time = 3000u64; + + market_client.initialize( + &market_id_bytes, + &Address::generate(&env), + &Address::generate(&env), + &Address::generate(&env), + &oracle_contract_id, + &closing_time, + &resolution_time, + ); + + // Set up pools + env.storage() + .persistent() + .set(&Symbol::new(&env, YES_POOL_KEY), &2000i128); + env.storage() + .persistent() + .set(&Symbol::new(&env, NO_POOL_KEY), &1000i128); + + // Close market + env.ledger().with_mut(|li| { + li.timestamp = closing_time + 10; + }); + market_client.close_market(&market_id_bytes); + + // Set oracle outcome + oracle_client.set_outcome_value(&1u32); + + // Resolve market + env.ledger().with_mut(|li| { + li.timestamp = resolution_time + 10; + }); + market_client.resolve_market(&market_id_bytes); + + let state = market_client.get_market_state(&market_id_bytes); + + assert_eq!(state.status, STATE_RESOLVED); + assert_eq!(state.total_pool, 3000); + assert_eq!(state.yes_pool, 2000); + assert_eq!(state.no_pool, 1000); + assert_eq!(state.winning_outcome, Some(1)); + } + + #[test] + fn test_get_market_state_no_auth_required() { + let env = Env::default(); + // Note: NOT calling env.mock_all_auths() to verify no auth is needed + + let market_id_bytes = BytesN::from_array(&env, &[0; 32]); + let market_contract_id = env.register(PredictionMarket, ()); + let market_client = PredictionMarketClient::new(&env, &market_contract_id); + let oracle_contract_id = env.register(MockOracle, ()); + + // Need to mock auth only for initialize + env.mock_all_auths(); + market_client.initialize( + &market_id_bytes, + &Address::generate(&env), + &Address::generate(&env), + &Address::generate(&env), + &oracle_contract_id, + &2000, + &3000, + ); + + // This should work without any auth + let state = market_client.get_market_state(&market_id_bytes); + + assert_eq!(state.status, STATE_OPEN); + } + + #[test] + fn test_get_market_state_empty_pools() { + let env = Env::default(); + env.mock_all_auths(); + + let market_id_bytes = BytesN::from_array(&env, &[0; 32]); + let market_contract_id = env.register(PredictionMarket, ()); + let market_client = PredictionMarketClient::new(&env, &market_contract_id); + let oracle_contract_id = env.register(MockOracle, ()); + + market_client.initialize( + &market_id_bytes, + &Address::generate(&env), + &Address::generate(&env), + &Address::generate(&env), + &oracle_contract_id, + &2000, + &3000, + ); + + let state = market_client.get_market_state(&market_id_bytes); + + // Verify empty pools return 0, not errors + assert_eq!(state.yes_pool, 0); + assert_eq!(state.no_pool, 0); + assert_eq!(state.total_pool, 0); + assert_eq!(state.participant_count, 0); + } + + #[test] + fn test_get_market_state_large_pools() { + let env = Env::default(); + env.mock_all_auths(); + + let market_id_bytes = BytesN::from_array(&env, &[0; 32]); + let market_contract_id = env.register(PredictionMarket, ()); + let market_client = PredictionMarketClient::new(&env, &market_contract_id); + let oracle_contract_id = env.register(MockOracle, ()); + + market_client.initialize( + &market_id_bytes, + &Address::generate(&env), + &Address::generate(&env), + &Address::generate(&env), + &oracle_contract_id, + &2000, + &3000, + ); + + // Set large pool values + let large_yes = 1_000_000_000i128; // 1 billion + let large_no = 500_000_000i128; // 500 million + env.storage() + .persistent() + .set(&Symbol::new(&env, YES_POOL_KEY), &large_yes); + env.storage() + .persistent() + .set(&Symbol::new(&env, NO_POOL_KEY), &large_no); + + let state = market_client.get_market_state(&market_id_bytes); + + assert_eq!(state.yes_pool, large_yes); + assert_eq!(state.no_pool, large_no); + assert_eq!(state.total_pool, large_yes + large_no); } } diff --git a/contracts/contracts/boxmeout/tests/market_test.rs b/contracts/contracts/boxmeout/tests/market_test.rs index 88a8900..5efbe8c 100644 --- a/contracts/contracts/boxmeout/tests/market_test.rs +++ b/contracts/contracts/boxmeout/tests/market_test.rs @@ -654,3 +654,384 @@ fn test_single_winner_gets_all() { // ============================================================================ // LIQUIDITY QUERY TESTS // ============================================================================ + +#[test] +fn test_get_market_liquidity_no_pool() { + let env = create_test_env(); + let (client, market_id, _creator, _admin, _usdc_address) = setup_test_market(&env); + + // Query liquidity when no pool exists (initial state) + let (yes_reserve, no_reserve, k_constant, yes_odds, no_odds) = + client.get_market_liquidity(&market_id); + + // Should return zeros for reserves and k + assert_eq!(yes_reserve, 0); + assert_eq!(no_reserve, 0); + assert_eq!(k_constant, 0); + + // Should return 50/50 odds (5000 basis points each) + assert_eq!(yes_odds, 5000); + assert_eq!(no_odds, 5000); +} + +#[test] +fn test_get_market_liquidity_balanced_pool() { + let env = create_test_env(); + let (client, market_id, _creator, _admin, _usdc_address) = setup_test_market(&env); + + // Manually set balanced pool reserves (simulating AMM pool creation) + let yes_reserve = 1_000_000_000u128; // 1000 USDC worth of YES + let no_reserve = 1_000_000_000u128; // 1000 USDC worth of NO + + // Store reserves in market storage (simulating AMM sync) + env.storage() + .persistent() + .set(&Symbol::new(&env, "yes_pool"), &yes_reserve); + env.storage() + .persistent() + .set(&Symbol::new(&env, "no_pool"), &no_reserve); + + // Query liquidity + let (returned_yes, returned_no, k_constant, yes_odds, no_odds) = + client.get_market_liquidity(&market_id); + + // Verify reserves + assert_eq!(returned_yes, yes_reserve); + assert_eq!(returned_no, no_reserve); + + // Verify k constant (x * y = k) + let expected_k = yes_reserve * no_reserve; + assert_eq!(k_constant, expected_k); + + // Verify odds are 50/50 for balanced pool + assert_eq!(yes_odds, 5000); + assert_eq!(no_odds, 5000); +} + +#[test] +fn test_get_market_liquidity_yes_favored() { + let env = create_test_env(); + let (client, market_id, _creator, _admin, _usdc_address) = setup_test_market(&env); + + // Set pool with YES favored (more NO reserve = higher YES price) + let yes_reserve = 400_000_000u128; // 400 USDC worth of YES + let no_reserve = 600_000_000u128; // 600 USDC worth of NO + + env.storage() + .persistent() + .set(&Symbol::new(&env, "yes_pool"), &yes_reserve); + env.storage() + .persistent() + .set(&Symbol::new(&env, "no_pool"), &no_reserve); + + // Query liquidity + let (returned_yes, returned_no, k_constant, yes_odds, no_odds) = + client.get_market_liquidity(&market_id); + + // Verify reserves + assert_eq!(returned_yes, yes_reserve); + assert_eq!(returned_no, no_reserve); + + // Verify k constant + assert_eq!(k_constant, yes_reserve * no_reserve); + + // Verify odds favor YES (YES should be > 50%) + // YES odds = (no_reserve / total) * 10000 = (600 / 1000) * 10000 = 6000 + assert_eq!(yes_odds, 6000); // 60% + assert_eq!(no_odds, 4000); // 40% + + // Verify odds sum to 10000 + assert_eq!(yes_odds + no_odds, 10000); +} + +#[test] +fn test_get_market_liquidity_no_favored() { + let env = create_test_env(); + let (client, market_id, _creator, _admin, _usdc_address) = setup_test_market(&env); + + // Set pool with NO favored (more YES reserve = higher NO price) + let yes_reserve = 700_000_000u128; // 700 USDC worth of YES + let no_reserve = 300_000_000u128; // 300 USDC worth of NO + + env.storage() + .persistent() + .set(&Symbol::new(&env, "yes_pool"), &yes_reserve); + env.storage() + .persistent() + .set(&Symbol::new(&env, "no_pool"), &no_reserve); + + // Query liquidity + let (returned_yes, returned_no, k_constant, yes_odds, no_odds) = + client.get_market_liquidity(&market_id); + + // Verify reserves + assert_eq!(returned_yes, yes_reserve); + assert_eq!(returned_no, no_reserve); + + // Verify k constant + assert_eq!(k_constant, yes_reserve * no_reserve); + + // Verify odds favor NO (NO should be > 50%) + // YES odds = (no_reserve / total) * 10000 = (300 / 1000) * 10000 = 3000 + assert_eq!(yes_odds, 3000); // 30% + assert_eq!(no_odds, 7000); // 70% + + // Verify odds sum to 10000 + assert_eq!(yes_odds + no_odds, 10000); +} + +#[test] +fn test_get_market_liquidity_extreme_yes() { + let env = create_test_env(); + let (client, market_id, _creator, _admin, _usdc_address) = setup_test_market(&env); + + // Set pool with extreme YES bias (95% YES) + let yes_reserve = 50_000_000u128; // 50 USDC worth of YES + let no_reserve = 950_000_000u128; // 950 USDC worth of NO + + env.storage() + .persistent() + .set(&Symbol::new(&env, "yes_pool"), &yes_reserve); + env.storage() + .persistent() + .set(&Symbol::new(&env, "no_pool"), &no_reserve); + + // Query liquidity + let (returned_yes, returned_no, k_constant, yes_odds, no_odds) = + client.get_market_liquidity(&market_id); + + // Verify reserves + assert_eq!(returned_yes, yes_reserve); + assert_eq!(returned_no, no_reserve); + + // Verify k constant + assert_eq!(k_constant, yes_reserve * no_reserve); + + // Verify extreme YES odds (95%) + assert_eq!(yes_odds, 9500); // 95% + assert_eq!(no_odds, 500); // 5% + + // Verify odds sum to 10000 + assert_eq!(yes_odds + no_odds, 10000); +} + +#[test] +fn test_get_market_liquidity_extreme_no() { + let env = create_test_env(); + let (client, market_id, _creator, _admin, _usdc_address) = setup_test_market(&env); + + // Set pool with extreme NO bias (95% NO) + let yes_reserve = 950_000_000u128; // 950 USDC worth of YES + let no_reserve = 50_000_000u128; // 50 USDC worth of NO + + env.storage() + .persistent() + .set(&Symbol::new(&env, "yes_pool"), &yes_reserve); + env.storage() + .persistent() + .set(&Symbol::new(&env, "no_pool"), &no_reserve); + + // Query liquidity + let (returned_yes, returned_no, k_constant, yes_odds, no_odds) = + client.get_market_liquidity(&market_id); + + // Verify reserves + assert_eq!(returned_yes, yes_reserve); + assert_eq!(returned_no, no_reserve); + + // Verify k constant + assert_eq!(k_constant, yes_reserve * no_reserve); + + // Verify extreme NO odds (95%) + assert_eq!(yes_odds, 500); // 5% + assert_eq!(no_odds, 9500); // 95% + + // Verify odds sum to 10000 + assert_eq!(yes_odds + no_odds, 10000); +} + +#[test] +fn test_get_market_liquidity_only_yes_reserve() { + let env = create_test_env(); + let (client, market_id, _creator, _admin, _usdc_address) = setup_test_market(&env); + + // Edge case: only YES reserve exists + let yes_reserve = 1_000_000_000u128; + let no_reserve = 0u128; + + env.storage() + .persistent() + .set(&Symbol::new(&env, "yes_pool"), &yes_reserve); + env.storage() + .persistent() + .set(&Symbol::new(&env, "no_pool"), &no_reserve); + + // Query liquidity + let (returned_yes, returned_no, k_constant, yes_odds, no_odds) = + client.get_market_liquidity(&market_id); + + // Verify reserves + assert_eq!(returned_yes, yes_reserve); + assert_eq!(returned_no, no_reserve); + + // k should be 0 (one-sided pool) + assert_eq!(k_constant, 0); + + // Odds should be 100% YES, 0% NO + assert_eq!(yes_odds, 10000); + assert_eq!(no_odds, 0); +} + +#[test] +fn test_get_market_liquidity_only_no_reserve() { + let env = create_test_env(); + let (client, market_id, _creator, _admin, _usdc_address) = setup_test_market(&env); + + // Edge case: only NO reserve exists + let yes_reserve = 0u128; + let no_reserve = 1_000_000_000u128; + + env.storage() + .persistent() + .set(&Symbol::new(&env, "yes_pool"), &yes_reserve); + env.storage() + .persistent() + .set(&Symbol::new(&env, "no_pool"), &no_reserve); + + // Query liquidity + let (returned_yes, returned_no, k_constant, yes_odds, no_odds) = + client.get_market_liquidity(&market_id); + + // Verify reserves + assert_eq!(returned_yes, yes_reserve); + assert_eq!(returned_no, no_reserve); + + // k should be 0 (one-sided pool) + assert_eq!(k_constant, 0); + + // Odds should be 0% YES, 100% NO + assert_eq!(yes_odds, 0); + assert_eq!(no_odds, 10000); +} + +#[test] +fn test_get_market_liquidity_large_numbers() { + let env = create_test_env(); + let (client, market_id, _creator, _admin, _usdc_address) = setup_test_market(&env); + + // Test with large liquidity amounts + let yes_reserve = 10_000_000_000_000u128; // 10 million USDC + let no_reserve = 10_000_000_000_000u128; // 10 million USDC + + env.storage() + .persistent() + .set(&Symbol::new(&env, "yes_pool"), &yes_reserve); + env.storage() + .persistent() + .set(&Symbol::new(&env, "no_pool"), &no_reserve); + + // Query liquidity + let (returned_yes, returned_no, k_constant, yes_odds, no_odds) = + client.get_market_liquidity(&market_id); + + // Verify reserves + assert_eq!(returned_yes, yes_reserve); + assert_eq!(returned_no, no_reserve); + + // Verify k constant (should handle large numbers) + assert_eq!(k_constant, yes_reserve * no_reserve); + + // Verify odds are still 50/50 + assert_eq!(yes_odds, 5000); + assert_eq!(no_odds, 5000); +} + +#[test] +fn test_get_market_liquidity_rounding_edge_case() { + let env = create_test_env(); + let (client, market_id, _creator, _admin, _usdc_address) = setup_test_market(&env); + + // Test with amounts that might cause rounding issues + let yes_reserve = 333_333_333u128; // 333.333... USDC + let no_reserve = 666_666_667u128; // 666.666... USDC + + env.storage() + .persistent() + .set(&Symbol::new(&env, "yes_pool"), &yes_reserve); + env.storage() + .persistent() + .set(&Symbol::new(&env, "no_pool"), &no_reserve); + + // Query liquidity + let (returned_yes, returned_no, k_constant, yes_odds, no_odds) = + client.get_market_liquidity(&market_id); + + // Verify reserves + assert_eq!(returned_yes, yes_reserve); + assert_eq!(returned_no, no_reserve); + + // Verify k constant + assert_eq!(k_constant, yes_reserve * no_reserve); + + // Verify odds sum to exactly 10000 (rounding adjustment applied) + assert_eq!(yes_odds + no_odds, 10000); + + // YES should be approximately 66.67% (6667 basis points) + // NO should be approximately 33.33% (3333 basis points) + assert!(yes_odds >= 6666 && yes_odds <= 6668); + assert!(no_odds >= 3332 && no_odds <= 3334); +} + +#[test] +fn test_get_market_liquidity_k_invariant_property() { + let env = create_test_env(); + let (client, market_id, _creator, _admin, _usdc_address) = setup_test_market(&env); + + // Set initial pool + let yes_reserve = 800_000_000u128; + let no_reserve = 200_000_000u128; + + env.storage() + .persistent() + .set(&Symbol::new(&env, "yes_pool"), &yes_reserve); + env.storage() + .persistent() + .set(&Symbol::new(&env, "no_pool"), &no_reserve); + + // Query liquidity + let (returned_yes, returned_no, k_constant, _yes_odds, _no_odds) = + client.get_market_liquidity(&market_id); + + // Verify k = x * y property + assert_eq!(k_constant, returned_yes * returned_no); + + // Verify k matches expected value + let expected_k = yes_reserve * no_reserve; + assert_eq!(k_constant, expected_k); +} + +#[test] +fn test_get_market_liquidity_multiple_queries_consistent() { + let env = create_test_env(); + let (client, market_id, _creator, _admin, _usdc_address) = setup_test_market(&env); + + // Set pool reserves + let yes_reserve = 500_000_000u128; + let no_reserve = 500_000_000u128; + + env.storage() + .persistent() + .set(&Symbol::new(&env, "yes_pool"), &yes_reserve); + env.storage() + .persistent() + .set(&Symbol::new(&env, "no_pool"), &no_reserve); + + // Query multiple times + let result1 = client.get_market_liquidity(&market_id); + let result2 = client.get_market_liquidity(&market_id); + let result3 = client.get_market_liquidity(&market_id); + + // All queries should return identical results (read-only operation) + assert_eq!(result1, result2); + assert_eq!(result2, result3); +}