From 12266f0c19f83c0f1cd2127ed345a239bad2cc2e Mon Sep 17 00:00:00 2001 From: Blessings Abel Date: Mon, 30 Mar 2026 12:54:14 +0000 Subject: [PATCH] fix: reject contract's own address as oracle in initialize --- contracts/escrow/src/errors.rs | 1 + contracts/escrow/src/lib.rs | 13 ++++++--- contracts/escrow/src/tests.rs | 52 ++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/contracts/escrow/src/errors.rs b/contracts/escrow/src/errors.rs index fb67ccb..b247713 100644 --- a/contracts/escrow/src/errors.rs +++ b/contracts/escrow/src/errors.rs @@ -14,4 +14,5 @@ pub enum Error { ContractPaused = 9, InvalidAmount = 10, MatchAlreadyActive = 11, + InvalidAddress = 12, } diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index 0641057..fb32f1b 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -17,17 +17,22 @@ pub struct EscrowContract; impl EscrowContract { /// Initialize the contract with a trusted oracle address and an admin. /// - /// The `oracle` address must be a valid generated address for the current - /// environment. Callers should not use placeholder or otherwise invalid - /// oracle values. - pub fn initialize(env: Env, oracle: Address, admin: Address) { + /// The `oracle` must be an externally-owned account or a separate contract + /// address. It must not be the escrow contract's own address — passing the + /// contract's own address would allow anyone to satisfy `oracle.require_auth()` + /// trivially, permanently compromising result submission. + pub fn initialize(env: Env, oracle: Address, admin: Address) -> Result<(), Error> { if env.storage().instance().has(&DataKey::Oracle) { panic!("Contract already initialized"); } + if oracle == env.current_contract_address() { + return Err(Error::InvalidAddress); + } env.storage().instance().set(&DataKey::Oracle, &oracle); env.storage().instance().set(&DataKey::Admin, &admin); env.storage().instance().set(&DataKey::MatchCount, &0u64); env.storage().instance().set(&DataKey::Paused, &false); + Ok(()) } /// Pause the contract — admin only. Blocks create_match, deposit, and submit_result. diff --git a/contracts/escrow/src/tests.rs b/contracts/escrow/src/tests.rs index 9ce2f9c..b55b990 100644 --- a/contracts/escrow/src/tests.rs +++ b/contracts/escrow/src/tests.rs @@ -190,6 +190,44 @@ fn test_full_match_lifecycle_winner_and_draw_scenarios() { assert_eq!(client.get_escrow_balance(&draw_match_id), 150); } +#[test] +fn test_full_match_lifecycle() { + let (env, contract_id, _oracle, player1, player2, token, _admin) = setup(); + let client = EscrowContractClient::new(&env, &contract_id); + let token_client = TokenClient::new(&env, &token); + + // Step 1: create_match → Pending + let id = client.create_match( + &player1, + &player2, + &100, + &token, + &String::from_str(&env, "lifecycle_game"), + &Platform::Lichess, + ); + assert_eq!(client.get_match(&id).state, MatchState::Pending); + assert_eq!(client.get_escrow_balance(&id), 0); + + // Step 2: player1 deposits → still Pending + client.deposit(&id, &player1); + assert_eq!(client.get_match(&id).state, MatchState::Pending); + assert_eq!(token_client.balance(&player1), 900); + assert_eq!(client.get_escrow_balance(&id), 100); + + // Step 3: player2 deposits → Active + client.deposit(&id, &player2); + assert_eq!(client.get_match(&id).state, MatchState::Active); + assert_eq!(token_client.balance(&player2), 900); + assert_eq!(client.get_escrow_balance(&id), 200); + + // Step 4: submit_result → Completed, winner paid, escrow zeroed + client.submit_result(&id, &Winner::Player1); + assert_eq!(client.get_match(&id).state, MatchState::Completed); + assert_eq!(token_client.balance(&player1), 1100); // won the pot + assert_eq!(token_client.balance(&player2), 900); // lost stake + assert_eq!(client.get_escrow_balance(&id), 0); +} + #[test] fn test_payout_winner() { let (env, contract_id, _oracle, player1, player2, token, _admin) = setup(); @@ -369,6 +407,20 @@ fn test_initialize_accepts_valid_generated_oracle_address() { assert_eq!(stored_oracle, oracle); } +#[test] +fn test_initialize_rejects_contract_address_as_oracle() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let contract_id = env.register(EscrowContract, ()); + let client = EscrowContractClient::new(&env, &contract_id); + + // Passing the contract's own address as oracle must be rejected + let result = client.try_initialize(&contract_id, &admin); + assert_eq!(result, Err(Ok(Error::InvalidAddress))); +} + #[test] fn test_cancel_match_emits_event() { let (env, contract_id, _oracle, player1, player2, token, _admin) = setup();