From a2f85f489d0da92727db3f9c11e06df6a8479119 Mon Sep 17 00:00:00 2001 From: Phanuella Date: Mon, 1 Jun 2026 00:48:09 +0000 Subject: [PATCH] fix: pending matches issues --- contracts/escrow/src/lib.rs | 107 ++++++++++++++++++------ contracts/escrow/src/tests.rs | 33 ++++++++ contracts/escrow/src/tests/lifecycle.rs | 8 +- contracts/escrow/src/tests/ttl.rs | 4 +- 4 files changed, 121 insertions(+), 31 deletions(-) diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index 325efe4..d3146ed 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -312,6 +312,7 @@ impl EscrowContract { (Symbol::new(&env, "match"), symbol_short!("activated")), match_id, ); + Self::add_live_match(env.clone(), match_id); } env.storage() @@ -378,6 +379,8 @@ impl EscrowContract { } } + Self::remove_live_match(env.clone(), match_id); + m.state = MatchState::Completed; m.completed_ledger = Some(env.ledger().sequence()); env.storage() @@ -643,31 +646,91 @@ impl EscrowContract { Ok(depositors * m.stake_amount) } + fn get_live_match_ids(env: Env) -> soroban_sdk::Vec { + let has_key = env.storage().persistent().has(&DataKey::LiveMatches); + let ids: soroban_sdk::Vec = env + .storage() + .persistent() + .get(&DataKey::LiveMatches) + .unwrap_or_else(|| soroban_sdk::vec![&env]); + if has_key { + env.storage().persistent().extend_ttl( + &DataKey::LiveMatches, + MATCH_TTL_LEDGERS, + MATCH_TTL_LEDGERS, + ); + } + ids + } + + fn set_live_match_ids(env: Env, ids: &soroban_sdk::Vec) { + env.storage().persistent().set(&DataKey::LiveMatches, ids); + env.storage().persistent().extend_ttl( + &DataKey::LiveMatches, + MATCH_TTL_LEDGERS, + MATCH_TTL_LEDGERS, + ); + } + + fn add_live_match(env: Env, match_id: u64) { + let ids = Self::get_live_match_ids(env.clone()); + let mut updated = soroban_sdk::vec![&env]; + let mut inserted = false; + + for i in 0..ids.len() { + let current = *ids.get(i).unwrap(); + if !inserted && current > match_id { + updated.push_back(match_id); + inserted = true; + } + if current != match_id { + updated.push_back(current); + } else { + inserted = true; + } + } + + if !inserted { + updated.push_back(match_id); + } + + Self::set_live_match_ids(env, &updated); + } + + fn remove_live_match(env: Env, match_id: u64) { + let ids = Self::get_live_match_ids(env.clone()); + let mut updated = soroban_sdk::vec![&env]; + + for i in 0..ids.len() { + let current = *ids.get(i).unwrap(); + if current != match_id { + updated.push_back(current); + } + } + + Self::set_live_match_ids(env, &updated); + } + /// Return all matches that are in Active state (fully funded). pub fn get_live_matches(env: Env) -> Result, Error> { + let ids = Self::get_live_match_ids(env.clone()); let mut live_matches = soroban_sdk::vec![&env]; - let count: u64 = env - .storage() - .instance() - .get(&DataKey::MatchCount) - .unwrap_or(0); - for i in 0..count { + for i in 0..ids.len() { + let match_id = *ids.get(i).unwrap(); if let Ok(m) = env .storage() .persistent() - .get::(&DataKey::Match(i)) + .get::(&DataKey::Match(match_id)) { - if m.state == MatchState::Active { - live_matches.push_back(m); - } + live_matches.push_back(m); } } Ok(live_matches) } - /// Return the total number of active matches created, ordered by match ID ascending. + /// Return all active matches ordered by match ID ascending. pub fn get_active_matches(env: Env) -> Result, Error> { Self::get_live_matches(env) } @@ -683,27 +746,21 @@ impl EscrowContract { return Ok(active_matches); } - let count: u64 = env - .storage() - .instance() - .get(&DataKey::MatchCount) - .unwrap_or(0); + let ids = Self::get_live_match_ids(env.clone()); let mut skipped = 0u32; let mut added = 0u32; - for i in 0..count { + for i in 0..ids.len() { + if skipped < offset { + skipped = skipped.saturating_add(1); + continue; + } + let match_id = *ids.get(i).unwrap(); if let Ok(m) = env .storage() .persistent() - .get::(&DataKey::Match(i)) + .get::(&DataKey::Match(match_id)) { - if m.state != MatchState::Active { - continue; - } - if skipped < offset { - skipped = skipped.saturating_add(1); - continue; - } active_matches.push_back(m); added = added.saturating_add(1); if added >= limit { diff --git a/contracts/escrow/src/tests.rs b/contracts/escrow/src/tests.rs index a060b26..3670463 100644 --- a/contracts/escrow/src/tests.rs +++ b/contracts/escrow/src/tests.rs @@ -1714,4 +1714,37 @@ fn test_get_live_matches_returns_only_fully_funded_matches() { let live_matches = client.get_live_matches(); assert_eq!(live_matches.len(), 1); assert_eq!(live_matches.get(0).unwrap().id, active_id); + assert_ne!(live_matches.get(0).unwrap().id, pending_id); +} + +#[test] +fn test_get_active_matches_excludes_pending_matches() { + let (env, contract_id, _oracle, player1, player2, token, _admin) = setup(); + let client = EscrowContractClient::new(&env, &contract_id); + + // Create pending match and active match. + let pending_id = client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "pending_match_exclusion"), + &Platform::Lichess, + ); + + let active_id = client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "active_match_exclusion"), + &Platform::Lichess, + ); + client.deposit(&active_id, &player1); + client.deposit(&active_id, &player2); + + let active_matches = client.get_active_matches(); + assert_eq!(active_matches.len(), 1); + assert_eq!(active_matches.get(0).unwrap().id, active_id); + assert_ne!(active_matches.get(0).unwrap().id, pending_id); } diff --git a/contracts/escrow/src/tests/lifecycle.rs b/contracts/escrow/src/tests/lifecycle.rs index 8923246..566d9a0 100644 --- a/contracts/escrow/src/tests/lifecycle.rs +++ b/contracts/escrow/src/tests/lifecycle.rs @@ -973,7 +973,7 @@ fn test_expire_match_refunds_depositor_after_timeout() { .extend_ttl_for_code(token.clone(), MATCH_TTL_LEDGERS, MATCH_TTL_LEDGERS); env.as_contract(&contract_id, || { env.storage().persistent().extend_ttl( - &DataKey::ActiveMatches, + &DataKey::LiveMatches, MATCH_TTL_LEDGERS, MATCH_TTL_LEDGERS, ); @@ -1304,7 +1304,7 @@ fn test_get_match_returns_cancelled_after_expire_match() { } env.as_contract(&contract_id, || { env.storage().persistent().extend_ttl( - &DataKey::ActiveMatches, + &DataKey::LiveMatches, MATCH_TTL_LEDGERS, MATCH_TTL_LEDGERS, ); @@ -1323,7 +1323,7 @@ fn test_get_match_returns_cancelled_after_expire_match() { } env.as_contract(&contract_id, || { env.storage().persistent().extend_ttl( - &DataKey::ActiveMatches, + &DataKey::LiveMatches, MATCH_TTL_LEDGERS, MATCH_TTL_LEDGERS, ); @@ -1535,7 +1535,7 @@ fn test_expire_match_refunds_both_players_when_both_deposited_but_still_pending( .extend_ttl_for_code(token.clone(), MATCH_TTL_LEDGERS, MATCH_TTL_LEDGERS); env.as_contract(&contract_id, || { env.storage().persistent().extend_ttl( - &DataKey::ActiveMatches, + &DataKey::LiveMatches, MATCH_TTL_LEDGERS, MATCH_TTL_LEDGERS, ); diff --git a/contracts/escrow/src/tests/ttl.rs b/contracts/escrow/src/tests/ttl.rs index 9688647..142fa61 100644 --- a/contracts/escrow/src/tests/ttl.rs +++ b/contracts/escrow/src/tests/ttl.rs @@ -80,7 +80,7 @@ fn test_active_matches_ttl_refreshed_on_append_and_removal() { ); let ttl_after_append = env.as_contract(&contract_id, || { - env.storage().persistent().get_ttl(&DataKey::ActiveMatches) + env.storage().persistent().get_ttl(&DataKey::LiveMatches) }); assert_eq!(ttl_after_append, crate::MATCH_TTL_LEDGERS); @@ -101,7 +101,7 @@ fn test_active_matches_ttl_refreshed_on_append_and_removal() { client.submit_result(&match1, &Winner::Player1); let ttl_after_removal = env.as_contract(&contract_id, || { - env.storage().persistent().get_ttl(&DataKey::ActiveMatches) + env.storage().persistent().get_ttl(&DataKey::LiveMatches) }); assert_eq!(ttl_after_removal, crate::MATCH_TTL_LEDGERS); }