diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index d15ca53..87f5df6 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -213,6 +213,37 @@ impl EscrowContract { env.storage().instance().set(&DataKey::MatchCount, &next_id); env.storage().persistent().set(&DataKey::GameId(m.game_id.clone()), &true); + // Add match ID to both players' match lists + let mut player1_matches: soroban_sdk::Vec = env + .storage() + .persistent() + .get(&DataKey::PlayerMatches(player1.clone())) + .unwrap_or_else(|| soroban_sdk::vec![&env]); + player1_matches.push_back(id); + env.storage() + .persistent() + .set(&DataKey::PlayerMatches(player1.clone()), &player1_matches); + env.storage().persistent().extend_ttl( + &DataKey::PlayerMatches(player1), + MATCH_TTL_LEDGERS, + MATCH_TTL_LEDGERS, + ); + + let mut player2_matches: soroban_sdk::Vec = env + .storage() + .persistent() + .get(&DataKey::PlayerMatches(player2.clone())) + .unwrap_or_else(|| soroban_sdk::vec![&env]); + player2_matches.push_back(id); + env.storage() + .persistent() + .set(&DataKey::PlayerMatches(player2.clone()), &player2_matches); + env.storage().persistent().extend_ttl( + &DataKey::PlayerMatches(player2), + MATCH_TTL_LEDGERS, + MATCH_TTL_LEDGERS, + ); + env.events().publish( (Symbol::new(&env, "match"), symbol_short!("created")), (id, m.player1, m.player2, stake_amount), @@ -595,6 +626,24 @@ impl EscrowContract { Ok(live_matches) } + + /// Return the total number of matches created. + pub fn get_match_count(env: Env) -> Result { + Ok(env + .storage() + .instance() + .get(&DataKey::MatchCount) + .unwrap_or(0)) + } + + /// Return all match IDs for a given player (past and present). + pub fn get_player_matches(env: Env, player: Address) -> Result, Error> { + Ok(env + .storage() + .persistent() + .get(&DataKey::PlayerMatches(player)) + .unwrap_or_else(|| soroban_sdk::vec![&env])) + } } #[cfg(test)] diff --git a/contracts/escrow/src/tests/mod.rs b/contracts/escrow/src/tests/mod.rs index 5d2eb49..3c050f0 100644 --- a/contracts/escrow/src/tests/mod.rs +++ b/contracts/escrow/src/tests/mod.rs @@ -11,6 +11,7 @@ mod admin; mod events; mod index; mod lifecycle; +mod pagination; mod ttl; mod token_allowlist; diff --git a/contracts/escrow/src/tests/pagination.rs b/contracts/escrow/src/tests/pagination.rs new file mode 100644 index 0000000..f779031 --- /dev/null +++ b/contracts/escrow/src/tests/pagination.rs @@ -0,0 +1,228 @@ +use super::*; + +/// Test #580: player-match pagination handles empty and partial pages +#[test] +fn test_player_match_pagination_handles_empty_and_partial_pages() { + let (env, contract_id, _oracle, player1, player2, token, _admin) = setup(); + let client = EscrowContractClient::new(&env, &contract_id); + + // Create 25 matches for player1 + let mut match_ids = Vec::new(); + for i in 0..25 { + let match_id = client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, &format!("game_{}", i)), + &Platform::Lichess, + ); + match_ids.push(match_id); + } + + // Query player1's matches + let player1_matches = client.get_player_matches(&player1); + assert_eq!(player1_matches.len(), 25); + + // Verify all match IDs are present in order + for (i, match_id) in match_ids.iter().enumerate() { + assert_eq!(player1_matches.get(i as u32).unwrap(), *match_id); + } + + // Query player2's matches (should have 25 as well) + let player2_matches = client.get_player_matches(&player2); + assert_eq!(player2_matches.len(), 25); + + // Query a player with no matches + let player3 = Address::generate(&env); + let player3_matches = client.get_player_matches(&player3); + assert_eq!(player3_matches.len(), 0); +} + +/// Test #579: player history index excludes unrelated matches for other players +#[test] +fn test_player_history_index_excludes_unrelated_matches() { + let (env, contract_id, _oracle, player1, player2, token, _admin) = setup(); + let client = EscrowContractClient::new(&env, &contract_id); + + let player3 = Address::generate(&env); + let player4 = Address::generate(&env); + + // Mint tokens for player3 and player4 + let token_client = TokenClient::new(&env, &token); + token_client.mint(&player3, &1000); + token_client.mint(&player4, &1000); + + // Create matches for player1 and player2 + let match_id_1 = client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "game_1"), + &Platform::Lichess, + ); + + let match_id_2 = client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "game_2"), + &Platform::Lichess, + ); + + // Create matches for player3 and player4 + let match_id_3 = client.create_match( + &player3, + &player4, + &100, + &token, + &String::from_str(&env, "game_3"), + &Platform::Lichess, + ); + + let match_id_4 = client.create_match( + &player3, + &player4, + &100, + &token, + &String::from_str(&env, "game_4"), + &Platform::Lichess, + ); + + // Assert player1 only receives their own match IDs + let player1_matches = client.get_player_matches(&player1); + assert_eq!(player1_matches.len(), 2); + assert_eq!(player1_matches.get(0).unwrap(), match_id_1); + assert_eq!(player1_matches.get(1).unwrap(), match_id_2); + + // Assert player2 only receives their own match IDs + let player2_matches = client.get_player_matches(&player2); + assert_eq!(player2_matches.len(), 2); + assert_eq!(player2_matches.get(0).unwrap(), match_id_1); + assert_eq!(player2_matches.get(1).unwrap(), match_id_2); + + // Assert player3 only receives their own match IDs + let player3_matches = client.get_player_matches(&player3); + assert_eq!(player3_matches.len(), 2); + assert_eq!(player3_matches.get(0).unwrap(), match_id_3); + assert_eq!(player3_matches.get(1).unwrap(), match_id_4); + + // Assert player4 only receives their own match IDs + let player4_matches = client.get_player_matches(&player4); + assert_eq!(player4_matches.len(), 2); + assert_eq!(player4_matches.get(0).unwrap(), match_id_3); + assert_eq!(player4_matches.get(1).unwrap(), match_id_4); +} + +/// Test #578: get_player_matches preserves insertion order +#[test] +fn test_get_player_matches_preserves_insertion_order() { + let (env, contract_id, _oracle, player1, player2, token, _admin) = setup(); + let client = EscrowContractClient::new(&env, &contract_id); + + // Create multiple matches for the same player + let match_id_1 = client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "game_1"), + &Platform::Lichess, + ); + + let match_id_2 = client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "game_2"), + &Platform::Lichess, + ); + + let match_id_3 = client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "game_3"), + &Platform::Lichess, + ); + + let match_id_4 = client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "game_4"), + &Platform::Lichess, + ); + + // Assert returned IDs are in expected order + let player1_matches = client.get_player_matches(&player1); + assert_eq!(player1_matches.len(), 4); + assert_eq!(player1_matches.get(0).unwrap(), match_id_1); + assert_eq!(player1_matches.get(1).unwrap(), match_id_2); + assert_eq!(player1_matches.get(2).unwrap(), match_id_3); + assert_eq!(player1_matches.get(3).unwrap(), match_id_4); +} + +/// Test #577: get_match_count increments correctly +#[test] +fn test_get_match_count_increments_correctly() { + let (env, contract_id, _oracle, player1, player2, token, _admin) = setup(); + let client = EscrowContractClient::new(&env, &contract_id); + + // Initial count should be 0 + let count = client.get_match_count(); + assert_eq!(count, 0); + + // Create first match + client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "game_1"), + &Platform::Lichess, + ); + let count = client.get_match_count(); + assert_eq!(count, 1); + + // Create second match + client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "game_2"), + &Platform::Lichess, + ); + let count = client.get_match_count(); + assert_eq!(count, 2); + + // Create third match + client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "game_3"), + &Platform::Lichess, + ); + let count = client.get_match_count(); + assert_eq!(count, 3); + + // Create fourth match + client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "game_4"), + &Platform::Lichess, + ); + let count = client.get_match_count(); + assert_eq!(count, 4); +}