Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions contracts/escrow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,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.
Expand Down
52 changes: 52 additions & 0 deletions contracts/escrow/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -390,6 +428,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();
Expand Down
Loading