Skip to content

feat: implement admin game settlement and staking logic#661

Open
ScriptedBro wants to merge 2 commits into
NOVUS-X:mainfrom
ScriptedBro:feature/game-settlement-staking
Open

feat: implement admin game settlement and staking logic#661
ScriptedBro wants to merge 2 commits into
NOVUS-X:mainfrom
ScriptedBro:feature/game-settlement-staking

Conversation

@ScriptedBro

@ScriptedBro ScriptedBro commented Apr 23, 2026

Copy link
Copy Markdown

Overview

This PR introduces critical on-chain logic for game settlement and staking, providing reliable infrastructure for back-end administrators (or validation oracles) to finalize games reliably and trigger payouts seamlessly.

Closes #511

Details

  • Admin/Oracle Settlement Logic: Added resolve_game to securely finalize game states. This enables trusted validation (like evaluating an off-chain Checkmate verification) to properly mark game status as GameState::Completed.
  • Integrated Staking & Payout Automation: Directly routes the settled game through the Self::process_payout hook. Wagers and escrow pools are distributed securely out of escrow to the winning address, correctly separating the platform/treasury fee based on initialized configurations.
  • Improved Testing Coverage: Adds a detailed, fully comprehensive test case (test_resolve_game_by_admin) verifying the payout pool mathematics, treasury redirection logic, and authorization scoping.
  • Resource Efficiency: Kept the payload tight by utilizing existing payout functions across the codebase instead of writing overlapping transfer functionalities.

Testing

All tests executed with cargo test passing smoothly. Covered basic payouts and token validation limits correctly inline with the Stellar implementation.

Closes #511

Summary by CodeRabbit

Release Notes

  • New Features

    • Added admin-driven game settlement with winner determination and automatic payout processing.
    • Fee collection and treasury distribution implemented during game resolution.
  • Tests

    • New test coverage for admin game resolution workflow, including balance verification and payout validation.

@drips-wave

drips-wave Bot commented Apr 23, 2026

Copy link
Copy Markdown

@ScriptedBro Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Introduces an admin-driven resolve_game entrypoint for game settlement. It authorizes against CONTRACT_ADMIN, validates the target game is InProgress, confirms the winner is a participant, marks the game as Completed, executes payout logic via process_payout, and persists the updated game state.

Changes

Cohort / File(s) Summary
Core Admin Resolution Logic
contracts/game_contract/src/lib.rs
Adds public resolve_game(env, game_id, winner) method that authorizes against stored CONTRACT_ADMIN, retrieves and validates game status, confirms winner is a participant, updates game state to Completed, invokes process_payout for fund distribution, and persists changes to GAMES map.
Admin Resolution Test Suite
contracts/game_contract/src/test.rs
Adds comprehensive unit test exercising the full admin-driven resolve_game workflow: contract initialization with token setup, puzzle-reward/fee configuration, minting wagers to two players, creating and joining a game, then resolving via admin. Verifies token balance outcomes: winner receives net payout (after fee deduction), treasury receives configured fee, loser receives nothing.
Execution Snapshot
contracts/game_contract/test_snapshots/test/test_resolve_game_by_admin.1.json
Captures complete simulated execution flow including contract function invocations (admin setup, token initialization, balance minting, game creation/joining, admin resolution), nested sub-invocations (token transfers, escrow movements), resulting ledger state (game contract storage with admin/token/fees/game map, token contract balances), and emitted contract/diagnostic events, all marked as successful.

Sequence Diagram

sequenceDiagram
    participant Admin
    participant GameContract
    participant GameState as GAMES Storage
    participant PayoutLogic as process_payout
    participant TokenContract

    Admin->>GameContract: resolve_game(game_id, winner)
    GameContract->>GameContract: Verify sender == CONTRACT_ADMIN
    GameContract->>GameState: Fetch game by game_id
    GameState-->>GameContract: Return game data
    GameContract->>GameContract: Validate game.state == InProgress
    GameContract->>GameContract: Verify winner in participants
    GameContract->>GameState: Update game.state = Completed
    GameContract->>PayoutLogic: process_payout(game, winner)
    PayoutLogic->>TokenContract: Transfer to winner (net amount)
    TokenContract-->>PayoutLogic: Confirm transfer
    PayoutLogic->>TokenContract: Transfer to treasury (fee)
    TokenContract-->>PayoutLogic: Confirm transfer
    PayoutLogic-->>GameContract: Payout complete
    GameContract->>GameState: Persist updated game
    GameState-->>GameContract: Acknowledged
    GameContract-->>Admin: Success
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PR #141: Adds admin resolution entrypoint marking games Completed and invoking payout logic; directly extends the same game contract foundation that this PR builds upon.
  • PR #205: Modifies process_payout function's fee/treasury and escrow logic; directly affects the payout behavior invoked by the new resolve_game entrypoint.
  • PR #191: Implements CONTRACT_ADMIN storage and configurable fee mechanisms; provides the authorization and fee infrastructure used by this PR's resolve_game method.

Poem

🐰 Admin hops in with a winner's decree,
Game state now marked as completed you see!
Payouts are flowing from escrow so deep,
Treasury counts beans while victors all leap! ✨🏆

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main feature addition: implementing admin game settlement and staking logic, which matches the core changes in the PR.
Linked Issues check ✅ Passed The PR implements all key coding requirements from issue #511: on-chain settlement and staking logic, analyzes existing code patterns, includes unit tests covering the new flow, and is tested with cargo.
Out of Scope Changes check ✅ Passed All changes are directly related to the settlement and staking objectives: the resolve_game function, comprehensive test coverage, and snapshot fixtures are all in scope.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
contracts/game_contract/src/lib.rs (1)

639-641: ⚠️ Potential issue | 🔴 Critical

Restrict set_max_stake to the contract admin.

Anyone can currently change the global stake limit, including setting it negative or raising it arbitrarily. That lets an attacker disable game creation or bypass the intended staking cap.

🔒 Proposed admin check and limit validation
-    pub fn set_max_stake(env: Env, new_limit: i128) {
+    pub fn set_max_stake(env: Env, new_limit: i128) {
+        let admin: Address = env
+            .storage()
+            .instance()
+            .get(&CONTRACT_ADMIN)
+            .expect("Not initialized");
+        admin.require_auth();
+        if new_limit <= 0 {
+            panic!("Stake limit must be positive");
+        }
         env.storage().instance().set(&MAX_STAKE, &new_limit);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/game_contract/src/lib.rs` around lines 639 - 641, The setter
set_max_stake currently allows anyone to change MAX_STAKE; restrict it to the
contract admin by checking the caller against the stored admin/owner value
(e.g., compare env.invoker()/env::signer() to the existing admin/owner storage
key) and return/abort if not admin, and validate the new_limit before storing
(reject negative values and enforce any sensible upper bound if applicable) so
only an admin can update MAX_STAKE and only with a non-negative (and
within-allowed-range) value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/game_contract/src/lib.rs`:
- Around line 387-392: The admin resolution flow calls Self::process_payout in
resolve_game and then persists the game as GameState::Completed, but the public
payout entrypoint can later call payout and replay process_payout; add a guard
to prevent double payouts by (a) extending the Game struct with a boolean like
paid or settled and set it to true after process_payout in resolve_game, and (b)
at the start of process_payout (and/or payout) check that game.paid is false and
return an error if true; alternatively, have process_payout validate that both
players’ escrow deposits still exist and return an error when they are already
cleared—update resolve_game to set the paid flag when it performs the payout and
persist that field (references: resolve_game, payout, process_payout,
GameState::Completed, Game struct).
- Line 1059: Add an explicit test-only cfg attribute to the external test module
declaration: prefix the existing `mod test;` declaration in `lib.rs` with
`#[cfg(test)]` so it matches the inline `mod tests` pattern and makes the test
module import conditional; update the `mod test;` declaration (symbol: mod test)
to be guarded by `#[cfg(test)]`.

In `@contracts/game_contract/src/test.rs`:
- Around line 345-384: The test uses env.mock_all_auths() so it never exercises
the admin-only guard on resolve_game and it only asserts token balances, not
contract state changes; update the test to (1) replace env.mock_all_auths() with
per-address auths (or add a new test test_resolve_game_rejects_non_admin) that
calls try_resolve_game as a non-admin and asserts Err to verify authorization,
and (2) after client.resolve_game(&game_id, &player1) read back the contract
storage (GAMES map) and assert the game’s state equals GameState::Completed and
game.winner == Some(player1) to verify the contract-side settlement effects.

---

Outside diff comments:
In `@contracts/game_contract/src/lib.rs`:
- Around line 639-641: The setter set_max_stake currently allows anyone to
change MAX_STAKE; restrict it to the contract admin by checking the caller
against the stored admin/owner value (e.g., compare env.invoker()/env::signer()
to the existing admin/owner storage key) and return/abort if not admin, and
validate the new_limit before storing (reject negative values and enforce any
sensible upper bound if applicable) so only an admin can update MAX_STAKE and
only with a non-negative (and within-allowed-range) value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c6138e09-c013-4427-bcc7-cbce19066352

📥 Commits

Reviewing files that changed from the base of the PR and between fad8e4b and 6771814.

📒 Files selected for processing (14)
  • contracts/game_contract/src/lib.rs
  • contracts/game_contract/src/test.rs
  • contracts/game_contract/test_snapshots/test/test_configure_fees_permissioned.1.json
  • contracts/game_contract/test_snapshots/test/test_create_game_exceeds_max_stake.1.json
  • contracts/game_contract/test_snapshots/test/test_payout_tournament.1.json
  • contracts/game_contract/test_snapshots/test/test_payout_tournament_dust.1.json
  • contracts/game_contract/test_snapshots/test/test_payout_tournament_invalid_percentage.1.json
  • contracts/game_contract/test_snapshots/test/test_payout_with_fee.1.json
  • contracts/game_contract/test_snapshots/test/test_resolve_game_by_admin.1.json
  • contracts/game_contract/test_snapshots/test/test_set_max_stake.1.json
  • contracts/game_contract/test_snapshots/test/test_upgrade_admin_logic.1.json
  • contracts/game_contract/test_snapshots/tests/test_claim_puzzle_reward_invalid_sig.1.json
  • contracts/game_contract/test_snapshots/tests/test_claim_puzzle_reward_replay_rejected.1.json
  • contracts/game_contract/test_snapshots/tests/test_claim_puzzle_reward_valid_sig.1.json

Comment on lines +387 to +392
game.state = GameState::Completed;
game.winner = Some(winner.clone());
Self::process_payout(&env, &game, &winner)?;

games.set(game_id, game);
env.storage().instance().set(&GAMES, &games);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Prevent replay payout after admin resolution.

resolve_game pays immediately, then persists a Completed game with a winner. The public payout path accepts that same state/winner and can call process_payout again, draining other funds if the contract has enough token balance. Add an explicit paid/settled guard, or make process_payout reject when both player escrow deposits are no longer present.

🛡️ One minimal guard in process_payout
         // Deduct both stakes first (clean state, prevents double-spend)
         let player1_escrow = escrow.get(game.player1.clone()).unwrap_or(0);
+        if player1_escrow < game.wager_amount {
+            return Err(ContractError::InsufficientFunds);
+        }
         escrow.set(game.player1.clone(), player1_escrow - game.wager_amount);
 
         let player2 = game.player2.as_ref().ok_or(ContractError::GameFull)?;
         let player2_escrow = escrow.get(player2.clone()).unwrap_or(0);
+        if player2_escrow < game.wager_amount {
+            return Err(ContractError::InsufficientFunds);
+        }
         escrow.set(player2.clone(), player2_escrow - game.wager_amount);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/game_contract/src/lib.rs` around lines 387 - 392, The admin
resolution flow calls Self::process_payout in resolve_game and then persists the
game as GameState::Completed, but the public payout entrypoint can later call
payout and replay process_payout; add a guard to prevent double payouts by (a)
extending the Game struct with a boolean like paid or settled and set it to true
after process_payout in resolve_game, and (b) at the start of process_payout
(and/or payout) check that game.paid is false and return an error if true;
alternatively, have process_payout validate that both players’ escrow deposits
still exist and return an error when they are already cleared—update
resolve_game to set the paid flag when it performs the payout and persist that
field (references: resolve_game, payout, process_payout, GameState::Completed,
Game struct).

assert_eq!(result, Err(Ok(ContractError::Unauthorized)));
}
}
mod test;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect the unguarded Rust test module and its imports.

fd '^lib\.rs$' | xargs rg -n -C2 '^\s*mod test;\s*$|^\s*#\[cfg\(test\)\]'
fd '^test\.rs$' | xargs sed -n '1,120p'

Repository: NOVUS-X/XLMate

Length of output: 5612


Add #[cfg(test)] guard to the external test module declaration for consistency.

The external test module mod test; (line 1059) is not guarded with #[cfg(test)], unlike the inline mod tests block at line 793. While test.rs itself starts with #![cfg(test)], explicitly guarding the module declaration in lib.rs is clearer and matches the pattern used elsewhere in this file.

Proposed fix
-mod test;
+#[cfg(test)]
+mod test;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mod test;
#[cfg(test)]
mod test;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/game_contract/src/lib.rs` at line 1059, Add an explicit test-only
cfg attribute to the external test module declaration: prefix the existing `mod
test;` declaration in `lib.rs` with `#[cfg(test)]` so it matches the inline `mod
tests` pattern and makes the test module import conditional; update the `mod
test;` declaration (symbol: mod test) to be guarded by `#[cfg(test)]`.

Comment on lines +345 to +384
#[test]
fn test_resolve_game_by_admin() {
let env = Env::default();
env.mock_all_auths();

let contract_id = env.register_contract(None, GameContract);
let client = GameContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);
let issuer = Address::generate(&env);
let player1 = Address::generate(&env);
let player2 = Address::generate(&env);
let treasury_addr = Address::generate(&env);

let stellar_token = env.register_stellar_asset_contract_v2(issuer);
let token_address = stellar_token.address();
let stellar_asset_client = StellarAssetClient::new(&env, &token_address);
let token_client = TokenClient::new(&env, &token_address);

client.initialize_token(&admin, &token_address);

let admin_key = Bytes::from_slice(&env, &[0u8; 32]);
client.initialize_puzzle_rewards(&admin, &admin_key, &0i128, &20u32, &treasury_addr);

let wager = 500;
stellar_asset_client.mint(&player1, &wager);
stellar_asset_client.mint(&player2, &wager);

let game_id = client.create_game(&player1, &wager);
client.join_game(&game_id, &player2);

// Let the admin resolve the game, meaning player1 won
client.resolve_game(&game_id, &player1);

// Check balances
// Pool = 1000. Fee = 1000 * 20 / 1000 = 20. Payout = 980.
assert_eq!(token_client.balance(&player1), 980);
assert_eq!(token_client.balance(&treasury_addr), 20);
assert_eq!(token_client.balance(&player2), 0);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test does not actually verify admin-only authorization or final game state.

Two gaps in a test that the PR summary claims verifies "authorization scoping" and settlement:

  1. env.mock_all_auths() on line 348 unconditionally approves every require_auth in the transaction. The test never exercises a non-admin caller invoking resolve_game, so the admin-only guard is not actually covered. A companion negative case (e.g. try_resolve_game from a stranger with per-address auth mocks, expecting Err) is needed to back up the PR's "authorization scoping" claim — analogous to how test_configure_fees_permissioned (lines 292–316) pairs a success path with a stranger-rejected path.
  2. Only token balances are asserted. The core contract-side effects of resolve_game — transitioning GameState::Completed and recording winner = Some(player1) — are never read back from storage. If a future refactor accidentally skipped the state/winner update but still produced a correct payout, this test would silently pass.
Suggested additions
// After resolve_game:
env.as_contract(&contract_id, || {
    let games: Map<u64, Game> = env.storage().instance().get(&GAMES).unwrap();
    let game = games.get(game_id).unwrap();
    assert_eq!(game.state, GameState::Completed);
    assert_eq!(game.winner, Some(player1.clone()));
});

// And a separate test:
#[test]
fn test_resolve_game_rejects_non_admin() {
    // ...setup identical up to create_game/join_game...
    let stranger = Address::generate(&env);
    // Use per-address auth mocks instead of mock_all_auths so the admin
    // requirement is actually checked.
    let res = client
        .mock_auths(&[/* stranger only */])
        .try_resolve_game(&game_id, &player1);
    assert!(res.is_err());
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/game_contract/src/test.rs` around lines 345 - 384, The test uses
env.mock_all_auths() so it never exercises the admin-only guard on resolve_game
and it only asserts token balances, not contract state changes; update the test
to (1) replace env.mock_all_auths() with per-address auths (or add a new test
test_resolve_game_rejects_non_admin) that calls try_resolve_game as a non-admin
and asserts Err to verify authorization, and (2) after
client.resolve_game(&game_id, &player1) read back the contract storage (GAMES
map) and assert the game’s state equals GameState::Completed and game.winner ==
Some(player1) to verify the contract-side settlement effects.

@gabito1451

Copy link
Copy Markdown
Collaborator

conflicts @ScriptedBro

- Resolved conflicts in contracts/game_contract/src/test.rs by keeping both upstream tests and our test_resolve_game_by_admin test
- Accepted upstream versions of test snapshot JSON files
- Added TokenClient import for test_resolve_game_by_admin test
- All conflicts resolved successfully
@ScriptedBro

Copy link
Copy Markdown
Author

conflicts @ScriptedBro

Resoloved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
contracts/game_contract/src/lib.rs (2)

3047-3047: ⚠️ Potential issue | 🔴 Critical

Remove the duplicate mod test; declaration.

Line 3047 redeclares mod test; even though it is already declared at Line 2098, which can break test compilation.

🧹 Minimal fix
-mod test;
#!/bin/bash
# Verify duplicate module declarations in the same file
rg -n '^\s*(#\[cfg\(test\)\]\s*)?mod test;\s*$' contracts/game_contract/src/lib.rs
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/game_contract/src/lib.rs` at line 3047, Remove the duplicate module
declaration "mod test;" that was reintroduced (the one shown in the diff) so
only the original "mod test;" remains; locate the redundant "mod test;"
declaration and delete it to avoid duplicate module definitions that break test
compilation, leaving the original test module declaration intact.

722-727: ⚠️ Potential issue | 🔴 Critical

Prevent replay payout after admin settlement.

After resolve_game pays out, the game is still persisted as Completed; at Line 675, payout accepts Completed, so the same game can be paid again.

🛡️ Minimal fix
-        game.state = GameState::Completed;
-        game.winner = Some(winner.clone());
-        Self::process_payout(&env, &game, &winner)?;
+        game.state = GameState::Completed;
+        game.winner = Some(winner.clone());
+        Self::process_payout(&env, &game, &winner)?;
+        game.state = GameState::Settled;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/game_contract/src/lib.rs` around lines 722 - 727, The code persists
the game as GameState::Completed after calling Self::process_payout in
resolve_game, but payout(...) still accepts Completed so the same game can be
paid again; after calling process_payout you must mark the game with a terminal
“paid/settled” state that payout will reject (e.g., add GameState::Settled or
GameState::Paid to the GameState enum), set game.state = GameState::Settled (or
.Paid) instead of Completed in resolve_game, persist with games.set(...) and
env.storage().instance().set(&GAMES,...), and update the payout(...) validation
to only allow states prior to this settled state (so replay payouts are
blocked).
contracts/game_contract/src/test.rs (1)

772-807: ⚠️ Potential issue | 🟠 Major

Test still does not prove admin-only access or persisted settlement state.

mock_all_auths() at Line 772 bypasses real auth gating, and the test only checks balances after Line 801 (no game.state / game.winner assertion).

✅ Suggested test additions
 fn test_resolve_game_by_admin() {
     let env = Env::default();
     env.mock_all_auths();
@@
     // Let the admin resolve the game, meaning player1 won
     client.resolve_game(&game_id, &player1);
+
+    // Verify contract-side state changes too
+    let game = client.get_game(&game_id);
+    assert_eq!(game.winner, Some(player1.clone()));
+    assert_eq!(game.state, GameState::Settled);
@@
     assert_eq!(token_client.balance(&player2), 0);
 }
+
+#[test]
+fn test_resolve_game_rejects_non_admin() {
+    let env = Env::default();
+    env.mock_all_auths();
+
+    let contract_id = env.register_contract(None, GameContract);
+    let client = GameContractClient::new(&env, &contract_id);
+
+    let admin = Address::generate(&env);
+    let issuer = Address::generate(&env);
+    let player1 = Address::generate(&env);
+    let player2 = Address::generate(&env);
+    let treasury_addr = Address::generate(&env);
+    let stranger = Address::generate(&env);
+
+    let stellar_token = env.register_stellar_asset_contract_v2(issuer);
+    let token_address = stellar_token.address();
+    let stellar_asset_client = StellarAssetClient::new(&env, &token_address);
+
+    client.initialize_token(&admin, &token_address);
+    client.initialize_puzzle_rewards(&admin, &Bytes::from_slice(&env, &[0u8; 32]), &0i128, &20u32, &treasury_addr);
+
+    let wager = 500;
+    stellar_asset_client.mint(&player1, &wager);
+    stellar_asset_client.mint(&player2, &wager);
+    let game_id = client.create_game(&player1, &wager);
+    client.join_game(&game_id, &player2);
+
+    let res = client.try_resolve_game(&game_id, &stranger);
+    assert!(res.is_err());
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/game_contract/src/test.rs` around lines 772 - 807, The test
currently uses env.mock_all_auths(), which bypasses auth checks, and it never
asserts persisted game settlement state; remove or avoid relying on
mock_all_auths() for the admin-resolution flow and instead add two assertions:
1) a negative auth test that calls GameContractClient::resolve_game with a
non-admin address (e.g., player2) and asserts it fails/reverts to prove
admin-only access, and 2) after calling
GameContractClient::resolve_game(&game_id, &player1) fetch the game record
(e.g., via GameContractClient::get_game or the contract's game accessor) and
assert game.state == Resolved (or equivalent enum/variant) and game.winner ==
player1 to prove settlement was persisted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@contracts/game_contract/src/lib.rs`:
- Line 3047: Remove the duplicate module declaration "mod test;" that was
reintroduced (the one shown in the diff) so only the original "mod test;"
remains; locate the redundant "mod test;" declaration and delete it to avoid
duplicate module definitions that break test compilation, leaving the original
test module declaration intact.
- Around line 722-727: The code persists the game as GameState::Completed after
calling Self::process_payout in resolve_game, but payout(...) still accepts
Completed so the same game can be paid again; after calling process_payout you
must mark the game with a terminal “paid/settled” state that payout will reject
(e.g., add GameState::Settled or GameState::Paid to the GameState enum), set
game.state = GameState::Settled (or .Paid) instead of Completed in resolve_game,
persist with games.set(...) and env.storage().instance().set(&GAMES,...), and
update the payout(...) validation to only allow states prior to this settled
state (so replay payouts are blocked).

In `@contracts/game_contract/src/test.rs`:
- Around line 772-807: The test currently uses env.mock_all_auths(), which
bypasses auth checks, and it never asserts persisted game settlement state;
remove or avoid relying on mock_all_auths() for the admin-resolution flow and
instead add two assertions: 1) a negative auth test that calls
GameContractClient::resolve_game with a non-admin address (e.g., player2) and
asserts it fails/reverts to prove admin-only access, and 2) after calling
GameContractClient::resolve_game(&game_id, &player1) fetch the game record
(e.g., via GameContractClient::get_game or the contract's game accessor) and
assert game.state == Resolved (or equivalent enum/variant) and game.winner ==
player1 to prove settlement was persisted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: df4bb335-a2a0-4074-8a3d-16d6897f2fb0

📥 Commits

Reviewing files that changed from the base of the PR and between 6771814 and 1505a04.

📒 Files selected for processing (2)
  • contracts/game_contract/src/lib.rs
  • contracts/game_contract/src/test.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Contract: Persistent Storage Migration for Player Profiles

2 participants