diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index 325efe4..8c5a8e5 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -22,6 +22,14 @@ const DEFAULT_MATCH_TIMEOUT_LEDGERS: u32 = MATCH_TTL_LEDGERS; /// Both formats fit well within this limit. const MAX_GAME_ID_LEN: u32 = 64; +/// Extend instance storage TTL on every invocation so Admin, Oracle, Paused, and other +/// instance keys never expire. +fn extend_instance_ttl(env: &Env) { + env.storage() + .instance() + .extend_ttl(MATCH_TTL_LEDGERS / 2, MATCH_TTL_LEDGERS); +} + #[contract] pub struct EscrowContract; @@ -40,6 +48,7 @@ impl EscrowContract { /// Pause the contract — admin only. Blocks create_match, deposit, and submit_result. pub fn pause(env: Env) -> Result<(), Error> { + extend_instance_ttl(&env); let admin: Address = env .storage() .instance() @@ -54,6 +63,7 @@ impl EscrowContract { /// Unpause the contract — admin only. pub fn unpause(env: Env) -> Result<(), Error> { + extend_instance_ttl(&env); let admin: Address = env .storage() .instance() @@ -143,6 +153,7 @@ impl EscrowContract { game_id: String, platform: Platform, ) -> Result { + extend_instance_ttl(&env); player1.require_auth(); if env @@ -257,6 +268,7 @@ impl EscrowContract { /// Player deposits their stake into escrow. pub fn deposit(env: Env, match_id: u64, player: Address) -> Result<(), Error> { + extend_instance_ttl(&env); player.require_auth(); if env @@ -430,6 +442,7 @@ impl EscrowContract { /// Cancel a pending match and refund any deposits. /// Either player can cancel a pending match. pub fn cancel_match(env: Env, match_id: u64, caller: Address) -> Result<(), Error> { + extend_instance_ttl(&env); let mut m: Match = env .storage() .persistent() @@ -481,6 +494,7 @@ impl EscrowContract { /// Expire a pending match that has not been fully funded within MATCH_TIMEOUT_LEDGERS. /// Anyone can call this; funds are returned to whoever deposited. pub fn expire_match(env: Env, match_id: u64) -> Result<(), Error> { + extend_instance_ttl(&env); let mut m: Match = env .storage() .persistent() diff --git a/contracts/escrow/src/tests/ttl.rs b/contracts/escrow/src/tests/ttl.rs index 9688647..87397b0 100644 --- a/contracts/escrow/src/tests/ttl.rs +++ b/contracts/escrow/src/tests/ttl.rs @@ -360,3 +360,65 @@ fn test_player_match_index_ttl_refreshes_on_read() { }); assert_eq!(ttl, crate::MATCH_TTL_LEDGERS); } + +#[test] +fn test_get_player_matches_ttl_returns_correct_value() { + let (env, contract_id, _oracle, player1, player2, token, _admin) = setup(); + let client = EscrowContractClient::new(&env, &contract_id); + + // Before any matches, TTL should be 0 + let ttl_before = client.get_player_matches_ttl(&player1); + assert_eq!(ttl_before, 0); + + // Create a match + client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "ttl_getter_test"), + &Platform::Lichess, + ); + + // After creating a match, TTL should be set to MATCH_TTL_LEDGERS + let ttl_after = client.get_player_matches_ttl(&player1); + assert_eq!(ttl_after, crate::MATCH_TTL_LEDGERS); + + // Advance ledger by 1000 + env.ledger().set(soroban_sdk::testutils::LedgerInfo { + sequence_number: env.ledger().sequence() + 1000, + timestamp: env.ledger().timestamp() + 5000, + protocol_version: 22, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_ttl: 1, + min_persistent_entry_ttl: 1, + max_entry_ttl: crate::MATCH_TTL_LEDGERS + 2000, + }); + + // TTL should have decreased by approximately 1000 ledgers + let ttl_decreased = client.get_player_matches_ttl(&player1); + assert!( + ttl_decreased < ttl_after, + "TTL should decrease after ledger advancement" + ); + assert!( + ttl_decreased >= ttl_after - 1000, + "TTL should be approximately 1000 less" + ); + + // Reading player matches should refresh TTL back to MATCH_TTL_LEDGERS + client.get_player_matches(&player1); + let ttl_refreshed = client.get_player_matches_ttl(&player1); + assert_eq!(ttl_refreshed, crate::MATCH_TTL_LEDGERS); +} + +#[test] +fn test_get_player_matches_ttl_for_nonexistent_player() { + let (env, contract_id, _oracle, _player1, _player2, _token, _admin) = setup(); + let client = EscrowContractClient::new(&env, &contract_id); + + let random_player = Address::generate(&env); + let ttl = client.get_player_matches_ttl(&random_player); + assert_eq!(ttl, 0, "TTL should be 0 for player with no match history"); +}