diff --git a/contracts/game_contract/src/lib.rs b/contracts/game_contract/src/lib.rs index 7bcf9b3a..b27031eb 100644 --- a/contracts/game_contract/src/lib.rs +++ b/contracts/game_contract/src/lib.rs @@ -97,6 +97,8 @@ const MAX_STAKE: Symbol = symbol_short!("MAXSTAKE"); const FEE_BIPS: Symbol = symbol_short!("FEE_BIPS"); // u32 (0–1000, i.e. 0–10 %) const TREASURY_ADDR: Symbol = symbol_short!("TR_ADDR"); // Address const CONTRACT_ADMIN: Symbol = symbol_short!("CT_ADMIN"); // Address +const FEE_ADMINS: Symbol = symbol_short!("FEE_ADMS"); // Vec
+const FEE_THRESHOLD: Symbol = symbol_short!("FEE_THR"); // u32 // Dispute resolution system const DISPUTE_FEE: Symbol = symbol_short!("D_FEE"); // i128 - fee to file a dispute @@ -672,7 +674,12 @@ impl GameContract { env.storage().instance().set(&MAX_STAKE, &new_limit); } - pub fn configure_fees(env: Env, admin: Address, fee_bips: u32, treasury_address: Address) { + pub fn configure_fee_multisig( + env: Env, + caller: Address, + new_admins: Vec
, + threshold: u32, + ) { let current_admin: Address = env .storage() .instance() @@ -680,9 +687,73 @@ impl GameContract { .expect("Not initialized"); current_admin.require_auth(); - if admin != current_admin { + if caller != current_admin { panic!("Unauthorized admin address"); } + if threshold == 0 || threshold > new_admins.len() as u32 { + panic!("Invalid threshold"); + } + + let mut unique_set = Vec::new(&env); + for i in 0..new_admins.len() { + let admin = new_admins.get(i).unwrap(); + if unique_set.contains(&admin) { + panic!("Duplicate admins in list"); + } + unique_set.push_back(admin); + } + + env.storage().instance().set(&FEE_ADMINS, &new_admins); + env.storage().instance().set(&FEE_THRESHOLD, &threshold); + } + + pub fn configure_fees( + env: Env, + admins: Vec
, + fee_bips: u32, + treasury_address: Address, + ) { + let stored_admins: Vec
= + env.storage() + .instance() + .get(&FEE_ADMINS) + .unwrap_or_else(|| { + let current_admin: Address = env + .storage() + .instance() + .get(&CONTRACT_ADMIN) + .expect("Not initialized"); + let mut v = Vec::new(&env); + v.push_back(current_admin); + v + }); + let threshold: u32 = env.storage().instance().get(&FEE_THRESHOLD).unwrap_or(1); + + if admins.len() < threshold { + panic!("Not enough admin approvals"); + } + + let mut unique_approvals = 0; + let mut approved_set = Vec::new(&env); + + for i in 0..admins.len() { + let admin = admins.get(i).unwrap(); + if stored_admins.contains(&admin) { + if approved_set.contains(&admin) { + panic!("Duplicate admin approvals"); + } + admin.require_auth(); + approved_set.push_back(admin.clone()); + unique_approvals += 1; + } else { + panic!("Unauthorized admin address"); + } + } + + if unique_approvals < threshold { + panic!("Not enough valid admin approvals"); + } + if fee_bips > 1000 { panic!("Fee bips must be between 0 and 1000"); } @@ -1069,6 +1140,39 @@ impl GameContract { .get(dispute.game_id) .ok_or(ContractError::GameNotFound)?; + // Update dispute status + dispute.status = DisputeStatus::Resolved; + dispute.resolution = Some(resolution.clone()); + let game_id = dispute.game_id; + disputes.set(dispute_id, dispute); + env.storage().instance().set(&DISPUTES, &disputes); + + // Update game state and process payout based on arbitrator's decision + if let Some(ref winner_addr) = winner { + // Winner takes all + let mut games: Map = env + .storage() + .instance() + .get(&GAMES) + .ok_or(ContractError::GameNotFound)?; + + let mut game = games.get(game_id).ok_or(ContractError::GameNotFound)?; + + game.state = GameState::Completed; + game.winner = Some(winner_addr.clone()); + Self::process_payout(&env, &game, winner_addr)?; + + games.set(game_id, game); + env.storage().instance().set(&GAMES, &games); + } else { + // Draw - refund both players + let mut games: Map = env + .storage() + .instance() + .get(&GAMES) + .ok_or(ContractError::GameNotFound)?; + + let game = games.get(game_id).ok_or(ContractError::GameNotFound)?; if game.state != GameState::InProgress { return Err(ContractError::GameAlreadyCompleted); } @@ -1644,6 +1748,7 @@ mod tests { &0u32, &treasury_addr, ); + client.configure_dispute_system(&admin, &arbitrator, &50i128); client.configure_dispute_system(&admin, &arbitrator, &25i128); client.set_max_stake(&1_000i128); @@ -1693,6 +1798,7 @@ mod tests { &0u32, &treasury_addr, ); + client.configure_dispute_system(&admin, &arbitrator, &50i128); client.configure_dispute_system(&admin, &arbitrator, &0i128); client.set_max_stake(&1_000i128); @@ -1710,6 +1816,19 @@ mod tests { &resolution, ); + // Arbitrator resolves in favor of player1 + let resolution = Bytes::from_slice(&env, b"Player1 wins"); + client.resolve_dispute( + &dispute_id, + &arbitrator, + &Some(player1.clone()), + &resolution, + ); + + // Verify player1 received the payout + assert_eq!(token_client.balance(&player1), 1_050); + + // Verify dispute is resolved let dispute = client.get_dispute(&dispute_id); assert_eq!(dispute.status, DisputeStatus::Resolved); assert_eq!(token_client.balance(&player1), 1_100); @@ -1985,6 +2104,7 @@ mod tests { &0u32, &treasury_addr, ); + client.configure_dispute_system(&admin, &arbitrator, &50i128); client.configure_dispute_system(&admin, &arbitrator, &25i128); client.set_max_stake(&1_000i128); diff --git a/contracts/game_contract/src/test.rs b/contracts/game_contract/src/test.rs index 2ac456c1..d42a7ba7 100644 --- a/contracts/game_contract/src/test.rs +++ b/contracts/game_contract/src/test.rs @@ -305,14 +305,18 @@ fn test_configure_fees_permissioned() { // Update fees as admin let new_treasury = Address::generate(&env); - client.configure_fees(&admin, &50, &new_treasury); // 5% fee + let mut admins = Vec::new(&env); + admins.push_back(admin.clone()); + client.configure_fees(&admins, &50, &new_treasury); // 5% fee // Verify update // (In a real test we'd check storage or run a payout, but here we just ensure it doesn't panic) // Attempt update as someone else should panic let stranger = Address::generate(&env); - let res = client.try_configure_fees(&stranger, &100, &new_treasury); + let mut stranger_admins = Vec::new(&env); + stranger_admins.push_back(stranger.clone()); + let res = client.try_configure_fees(&stranger_admins, &100, &new_treasury); assert!(res.is_err()); }