From ecf6e56c3713500022575893b88ab2679e76d2b0 Mon Sep 17 00:00:00 2001 From: benjaminjohnsonfin-afk Date: Sat, 30 May 2026 15:13:17 +0000 Subject: [PATCH 1/5] feat: add get_match_count() public getter - Adds get_match_count() function to return total number of matches created - Updates DataKey enum to include PlayerMatches and OracleRecord variants - Increments match count on each create_match call --- contracts/escrow/src/lib.rs | 49 +++++++++++++++++++++++++++++++++++ contracts/escrow/src/types.rs | 2 ++ 2 files changed, 51 insertions(+) diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index b9a21a4..85c9083 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -150,6 +150,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), @@ -493,6 +524,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/types.rs b/contracts/escrow/src/types.rs index d722260..b845ded 100644 --- a/contracts/escrow/src/types.rs +++ b/contracts/escrow/src/types.rs @@ -52,4 +52,6 @@ pub enum DataKey { Paused, GameId(String), LiveMatches, + PlayerMatches(Address), + OracleRecord(u64), } From 051e1ab02440dbe387977dfe258a4d6e225fb84a Mon Sep 17 00:00:00 2001 From: benjaminjohnsonfin-afk Date: Sat, 30 May 2026 15:13:24 +0000 Subject: [PATCH 2/5] test: add test for get_match_count increments correctly - Verifies match count starts at 0 - Asserts count increments after each create_match call - Tests multiple sequential creations --- contracts/escrow/src/tests/mod.rs | 1 + contracts/escrow/src/tests/pagination.rs | 60 ++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 contracts/escrow/src/tests/pagination.rs diff --git a/contracts/escrow/src/tests/mod.rs b/contracts/escrow/src/tests/mod.rs index 1d49584..e930b95 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; pub fn setup() -> (Env, Address, Address, Address, Address, Address, Address) { diff --git a/contracts/escrow/src/tests/pagination.rs b/contracts/escrow/src/tests/pagination.rs new file mode 100644 index 0000000..4382733 --- /dev/null +++ b/contracts/escrow/src/tests/pagination.rs @@ -0,0 +1,60 @@ +use super::*; + +/// 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); +} From eb944e8b460906a4824828ebb648d91c9feb3357 Mon Sep 17 00:00:00 2001 From: benjaminjohnsonfin-afk Date: Sat, 30 May 2026 15:14:06 +0000 Subject: [PATCH 3/5] test: add test for get_player_matches preserves insertion order - Creates multiple matches for the same player - Verifies returned match IDs maintain insertion order - Ensures stable ordering for frontend pagination --- contracts/escrow/src/tests/pagination.rs | 52 ++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/contracts/escrow/src/tests/pagination.rs b/contracts/escrow/src/tests/pagination.rs index 4382733..ea07b42 100644 --- a/contracts/escrow/src/tests/pagination.rs +++ b/contracts/escrow/src/tests/pagination.rs @@ -1,5 +1,57 @@ use super::*; +/// 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() { From 6a2dbe4a4f289f6d22ef7df065fc5b65064e124f Mon Sep 17 00:00:00 2001 From: benjaminjohnsonfin-afk Date: Sat, 30 May 2026 15:14:22 +0000 Subject: [PATCH 4/5] test: add test for player history index excludes unrelated matches - Creates separate matches for different player pairs - Verifies each player only sees their own match IDs - Ensures no data leakage between player indices --- contracts/escrow/src/tests/pagination.rs | 77 ++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/contracts/escrow/src/tests/pagination.rs b/contracts/escrow/src/tests/pagination.rs index ea07b42..8948e5b 100644 --- a/contracts/escrow/src/tests/pagination.rs +++ b/contracts/escrow/src/tests/pagination.rs @@ -1,5 +1,82 @@ use super::*; +/// 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() { From e833e6355f5e0d838148ab1cdb902590d6d758af Mon Sep 17 00:00:00 2001 From: benjaminjohnsonfin-afk Date: Sat, 30 May 2026 15:14:43 +0000 Subject: [PATCH 5/5] test: add test for player-match pagination handles empty and partial pages - Creates 25 matches to test pagination boundaries - Verifies all match IDs are returned in order - Tests empty result set for players with no matches - Ensures pagination handles partial pages correctly --- contracts/escrow/src/tests/pagination.rs | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/contracts/escrow/src/tests/pagination.rs b/contracts/escrow/src/tests/pagination.rs index 8948e5b..f779031 100644 --- a/contracts/escrow/src/tests/pagination.rs +++ b/contracts/escrow/src/tests/pagination.rs @@ -1,5 +1,44 @@ 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() {