Skip to content

feat: harden game settlement and staking paths for issues 522-525#682

Merged
gabito1451 merged 1 commit into
OpenKnight-Foundation:mainfrom
devsimze:feature/issues-522-523-524-525-game-contract
Apr 25, 2026
Merged

feat: harden game settlement and staking paths for issues 522-525#682
gabito1451 merged 1 commit into
OpenKnight-Foundation:mainfrom
devsimze:feature/issues-522-523-524-525-game-contract

Conversation

@devsimze

@devsimze devsimze commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR delivers critical on-chain logic improvements for game settlement and staking in contracts/game_contract/src, with a focus on correctness, replay safety, authorization, and resource-conscious execution patterns.

#522: Context

This task involves critical on-chain logic for game settlement and staking on Stellar.

Implemented

  • Added settlement finalization semantics (Settled) to prevent replay payout paths.
  • Ensured payout flows transition game state to a terminal settled state.
  • Added explicit replay protection error handling for repeated payout attempts.

Acceptance

  • Follows existing contract design/style patterns.
  • Covered by regression tests and integrated test snapshots.
  • Validated with cargo tests.

Closes #522


#523: Context

This task involves critical on-chain logic for game settlement and staking on Stellar.

Implemented

  • Hardened admin controls for staking configuration (set_max_stake now admin-authenticated).
  • Added validation for invalid max stake inputs.
  • Updated call sites and tests to enforce permissioned stake-limit updates.

Acceptance

  • Authorization and edge cases covered by tests.
  • Fully integrated with existing admin configuration flow.
  • Validated with cargo tests.

Closes #523


#524: Context

This task involves critical on-chain logic for game settlement and staking on Stellar.

Implemented

  • Added strict reward amount validation in puzzle reward claim flow.
  • Introduced explicit InvalidAmount error handling for unsafe/invalid reward values.
  • Preserved signature and nonce protections while hardening input constraints.

Acceptance

  • Standard and edge case tests added (including invalid amount scenarios).
  • Implementation follows established on-chain safety patterns.
  • Validated with cargo tests.

Closes #524


#525: Context

This task involves critical on-chain logic for game settlement and staking on Stellar.

Implemented

  • Replaced timeout arithmetic with saturating subtraction to avoid underflow edge cases.
  • Applied timeout safety in both timeout-claim and timeout-remaining query paths.
  • Kept logic gas/CPU-efficient with low-overhead arithmetic safeguards.

Acceptance

  • Edge behavior is now deterministic and panic-safe.
  • Coverage included in timeout-related tests.
  • Validated with cargo tests.

Closes #525

Test Plan

  • cargo test -p game_contract
  • Result: 30 passed, 0 failed

Summary by CodeRabbit

  • New Features

    • Implemented a new game settlement state for improved transaction lifecycle management.
  • Bug Fixes

    • Prevented duplicate payout processing through settlement state validation.
    • Added validation to reject invalid reward amounts.
    • Strengthened timeout calculations to prevent underflow errors.
  • Security

    • Enhanced admin authorization verification for stake configuration.

Strengthen on-chain game settlement with replay-safe payout state transitions, admin-gated stake limits, safer timeout arithmetic, and strict puzzle reward amount validation. Add regression tests and updated snapshots to cover settlement replay, admin authorization, and invalid amount edge cases.

Made-with: Cursor
@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR introduces a terminal GameState::Settled after payouts, restricts set_max_stake to require explicit admin address authorization, validates puzzle reward amounts, and hardens timeout calculations. Workspace members are changed from wildcard to explicit contract list.

Changes

Cohort / File(s) Summary
Workspace Configuration
Cargo.toml
Changed workspace members from ["backend", "contracts/*"] to explicit list of three contracts: contracts/game_contract, contracts/emergency_circuit_breaker, contracts/ai_nft.
Core Settlement Logic
contracts/game_contract/src/lib.rs
Added GameState::Settled terminal state; added ContractError::AlreadySettled and ContractError::InvalidAmount error variants; updated set_max_stake(env, new_limit) to set_max_stake(env, admin: Address, new_limit) with admin auth validation and limit validation; updated all payout flows (payout, payout_tournament, payout_tournament_optimized, claim_win, claim_timeout_win, forfeit, and dispute winner settlement) to transition games to Settled after transfers; added input validation to claim_puzzle_reward rejecting non-positive or out-of-range amounts; hardened timeout elapsed computation with saturating_sub.
Test Suite Updates
contracts/game_contract/src/test.rs
Updated token imports from TokenClient to StellarAssetClient; modified test_set_max_stake to initialize puzzle rewards and pass admin address argument; added new test test_set_max_stake_rejects_non_admin to verify non-admin rejection.
Game State Snapshots
contracts/game_contract/test_snapshots/test/test_payout_tournament.1.json, test_payout_tournament_dust.1.json, test_payout_with_fee.1.json, test_fee_redirection_2_percent.1.json, test_usdc_staking_workflow.1.json
Updated persisted game state enum from "Completed" or "Forfeited" to "Settled".
Set Max Stake Snapshots
contracts/game_contract/test_snapshots/test/test_set_max_stake.1.json
Updated generator nonce from 5 to 6; added explicit initialize_puzzle_rewards authorization call; changed set_max_stake argument encoding from bare i128 to vector including address parameter; expanded contract instance storage with ADMIN_KEY, CT_ADMIN, FEE_BIPS, TREASURY, TR_ADDR entries; adjusted ledger temporary entries and diagnostic events.
Non-Admin Authorization Test Snapshots
contracts/game_contract/test_snapshots/test/test_set_max_stake_rejects_non_admin.1.json
New snapshot recording set_max_stake authorization failure with panic 'Unauthorized admin address' diagnostic.
Puzzle Reward Validation Snapshots
contracts/game_contract/test_snapshots/tests/test_claim_puzzle_reward_invalid_amount_rejected.1.json
New snapshot capturing claim_puzzle_reward failure for invalid reward amounts with contract error code 24.
Puzzle Reward Signature Snapshots
contracts/game_contract/test_snapshots/tests/test_claim_puzzle_reward_invalid_sig.1.json, test_claim_puzzle_reward_replay_rejected.1.json, test_claim_puzzle_reward_valid_sig.1.json
Updated cryptographic bytes fields for ADMIN_KEY and signature values across initialization and claim diagnostic events.
Timeout and Dispute Snapshots
contracts/game_contract/test_snapshots/tests/test_claim_timeout_win_not_reached.1.json, test_claim_timeout_win_rejects_current_turn_player.1.json, test_claim_timeout_win_success.1.json, test_file_dispute_rejects_settled_games.1.json, test_file_dispute_success.1.json, test_get_timeout_remaining.1.json, test_resolve_dispute_rejects_already_settled_games.1.json, test_resolve_dispute_winner_takes_all.1.json
Added explicit set_max_stake authorization calls with address parameter; updated set_max_stake event payloads from bare i128 to vector encoding address and stake; adjusted ledger temporary entries and nonces; updated game state from "Forfeited" to "Settled" where applicable.
Double-Payout Test Snapshot
contracts/game_contract/test_snapshots/tests/test_payout_cannot_be_called_twice.1.json
New snapshot verifying rejection of second payout call with contract error code 23 and AlreadySettled semantics.
Tournament and Fee Snapshots
contracts/game_contract/test_snapshots/tests/test_payout_tournament_optimized_splits_correctly.1.json, test_reject_dispute_refund_fee.1.json, test_fee_redirection_2_percent_large.1.json
Added set_max_stake authorization calls with address parameter; updated call argument encoding to vector format; adjusted ledger nonces; changed game state to "Settled".
Move Submission Snapshots
contracts/game_contract/test_snapshots/tests/test_submit_move_rejects_out_of_turn_and_empty_moves.1.json, test_submit_move_sequence_updates_turn_and_history.1.json
Updated generator address from 6 to 7 and all dependent contract identifiers; changed initialize_token target from AMDR4 to OLZM; added initialize_puzzle_rewards authorization steps; updated set_max_stake calls with address parameter; added new contract storage fields (ADMIN_KEY, CT_ADMIN, FEE_BIPS, TREASURY, TR_ADDR) to persistent instance.

Sequence Diagram

sequenceDiagram
    participant Player
    participant GameContract
    participant TokenContract
    
    Player->>GameContract: payout(game_id, player)
    GameContract->>GameContract: Check game not Settled
    GameContract->>GameContract: Validate authorization
    alt Game is Settled
        GameContract-->>Player: Error: AlreadySettled
    else Game not Settled
        GameContract->>TokenContract: Transfer winnings
        TokenContract-->>GameContract: Success
        GameContract->>GameContract: Set GameState::Settled
        GameContract-->>Player: Success
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Poem

🐰 A rabbit hops through settled states,
No double payouts at the gates!
Admin checks and puzzle validation too,
The contract's walls now strong and true! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive Changes are tightly scoped to game settlement, staking admin controls, reward validation, and timeout safety. The Cargo.toml modification limiting workspace members appears administrative but is not directly addressed in the linked issues. Clarify whether the Cargo.toml workspace member changes are intentional or should be reverted; confirm this aligns with project workspace management practices.
✅ Passed checks (4 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 changes: hardening game settlement and staking paths across four specific issues (#522-525).
Linked Issues check ✅ Passed All four linked issues (#522-525) have their core coding objectives met: game settlement finalization with Settled state [#522], admin authentication for set_max_stake [#523], reward amount validation [#524], and saturating subtraction for timeout arithmetic [#525].
Docstring Coverage ✅ Passed Docstring coverage is 100.00% 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

Warning

Tools execution failed with the following error:

Failed to run tools: 14 UNAVAILABLE: read ECONNRESET


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

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

@drips-wave

drips-wave Bot commented Apr 25, 2026

Copy link
Copy Markdown

@devsimze 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 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: 1

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)

196-204: ⚠️ Potential issue | 🔴 Critical

Critical: payout_tournament_optimized is exploitable on canceled games — drains contract tokens.

cancel_game (line 556, unchanged) sets state = GameState::Completed while leaving player2 = None and winner = None. This function only gates on state == Completed plus player1.require_auth() and then computes total_pool = wager_amount (no player2) and physically transfers it via token_client.transfer(&contract_address, &winner, &amount) at line 241 — with no winner/player2/escrow sanity check.

A malicious flow:

  1. create_game(attacker, 100) — deposits 100; contract holds funds from other games too.
  2. cancel_game(game_id, attacker) — refunded 100, state → Completed.
  3. payout_tournament_optimized(game_id, [attacker], [100]) — passes the state check, transfers another wager_amount from the contract to attacker.

payout is safe because of the game.winner.as_ref() == Some(&winner) check; payout_tournament is mostly safe because the escrow balance is zeroed by cancel_game. Only payout_tournament_optimized is unprotected — and the new Settled semantics in this PR don't close it because cancellation never reaches a terminal payout-rejecting state.

🛡️ Recommended fix — make cancellation terminal (preferred)

Treat cancel_game as a settlement: this leverages the new Settled state and closes the lateral path uniformly across all three payout functions.

-        game.state = GameState::Completed; // Mark as completed to prevent joining
+        game.state = GameState::Settled; // Terminal: prevents joining and any payout path
         games.set(game_id, game);

Alternatively (or in addition), defensively reject games that never reached a settleable shape inside payout_tournament_optimized:

         if game.state != GameState::Completed {
+            if game.state == GameState::Settled {
+                return Err(ContractError::AlreadySettled);
+            }
             return Err(ContractError::GameNotInProgress);
         }
+        if game.player2.is_none() {
+            return Err(ContractError::GameNotInProgress);
+        }
         game.player1.require_auth();

A regression test that asserts payout_tournament_optimized cannot be invoked after cancel_game would lock this down.

🤖 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 196 - 204, The bug is that
cancel_game sets game.state = GameState::Completed which lets
payout_tournament_optimized (which only checks for GameState::Completed) be
called on canceled games; change cancel_game to set game.state =
GameState::Settled (use the new Settled terminal state) instead of Completed so
canceled games are terminal and cannot be paid out, and add a regression test
invoking cancel_game then asserting payout_tournament_optimized returns an
error; touch the cancel_game function and reference
GameState::Settled/GameState::Completed and payout_tournament_optimized in your
change.
🧹 Nitpick comments (3)
Cargo.toml (1)

2-6: Confirm workspace narrowing is intentional and protected from drift.

Line 2-Line 6 hard-codes workspace members. This can silently exclude newly added contract crates from cargo test --workspace unless the list is manually updated. Consider adding a lightweight CI check to keep [workspace].members in sync with contracts/*/Cargo.toml.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Cargo.toml` around lines 2 - 6, The [workspace].members list in Cargo.toml is
hard-coded and can drift from actual crates under contracts/*, so add a CI check
that enumerates contracts/*/Cargo.toml entries and fails if they don't match the
members list; implement a small script (bash/python) that reads Cargo.toml's
workspace.members, globs contracts/*/Cargo.toml to derive expected members,
compares the two sets, and exits nonzero on mismatch (hook it into the existing
CI workflow so PRs fail until synced), referencing the Cargo.toml
[workspace].members key and the contracts/*/Cargo.toml pattern.
contracts/game_contract/src/lib.rs (2)

2313-2352: Consider extending the double-call regression test to other settlement paths.

test_payout_cannot_be_called_twice only exercises payout. The PR also transitions claim_win, forfeit, claim_timeout_win, payout_tournament, and payout_tournament_optimized to Settled, but their replay rejection currently returns the generic GameNotInProgress (since state != InProgress/Completed is what they actually check). A small set of additional cases — e.g., calling forfeit twice, or payout_tournament_optimized twice — would prove the terminal-state invariant holds across every settlement entrypoint and catch any future regression where someone forgets the state = Settled write.

Want me to draft the additional regression cases for the other settlement paths?

🤖 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 2313 - 2352, The test only
covers double-call rejection for payout; add analogous regression tests for each
settlement entrypoint (claim_win, forfeit, claim_timeout_win, payout_tournament,
payout_tournament_optimized) to ensure calling them twice returns AlreadySettled
and the contract persists state = Settled; for each new test reuse the existing
pattern (initialize contract, create/join game, set game state to Completed and
winner if needed inside env.as_contract, call the settlement method once via the
client, then call the corresponding try_* variant and assert
Err(Ok(ContractError::AlreadySettled))) so any missing state write in functions
like claim_win, forfeit, claim_timeout_win, payout_tournament, or
payout_tournament_optimized is caught.

1294-1309: Redundant Completed write before Settled.

Line 1299 sets game.state = GameState::Completed and line 1302 immediately overwrites it with GameState::Settled. process_payout does not read game.state, so the intermediate assignment is a dead store. Drop it for clarity and to keep the lifecycle linear (InProgress → Settled), matching claim_win/forfeit/payout.

♻️ Proposed simplification
             Some(ref winner_addr) => {
                 if *winner_addr != game.player1 && Some(winner_addr.clone()) != game.player2 {
                     return Err(ContractError::NotPlayer);
                 }
-                game.state = GameState::Completed;
                 game.winner = Some(winner_addr.clone());
                 Self::process_payout(&env, &game, winner_addr)?;
                 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 1294 - 1309, Remove the
redundant intermediate assignment to GameState::Completed in the winner branch:
in the match handling where Some(ref winner_addr) is processed (checking
winner_addr against game.player1 and game.player2 and calling
Self::process_payout), drop the line setting game.state = GameState::Completed
before calling Self::process_payout and directly set game.state =
GameState::Settled after process_payout; this keeps the state transition linear
(InProgress → Settled) and avoids the dead store since process_payout does not
read game.state.
🤖 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/test.rs`:
- Around line 252-254: The test currently only checks res.is_err() for
client.try_set_max_stake called by &attacker; replace this with asserting the
exact authorization failure by capturing the error (e.g., let err =
res.unwrap_err();) and then asserting it equals the unauthorized error variant
used by the contract (for example assert_eq!(err, ContractError::Unauthorized)
or match on Err(ContractError::Unauthorized) depending on the error type),
referencing client.try_set_max_stake, attacker and the contract's unauthorized
error variant so the test specifically verifies the permission check.

---

Outside diff comments:
In `@contracts/game_contract/src/lib.rs`:
- Around line 196-204: The bug is that cancel_game sets game.state =
GameState::Completed which lets payout_tournament_optimized (which only checks
for GameState::Completed) be called on canceled games; change cancel_game to set
game.state = GameState::Settled (use the new Settled terminal state) instead of
Completed so canceled games are terminal and cannot be paid out, and add a
regression test invoking cancel_game then asserting payout_tournament_optimized
returns an error; touch the cancel_game function and reference
GameState::Settled/GameState::Completed and payout_tournament_optimized in your
change.

---

Nitpick comments:
In `@Cargo.toml`:
- Around line 2-6: The [workspace].members list in Cargo.toml is hard-coded and
can drift from actual crates under contracts/*, so add a CI check that
enumerates contracts/*/Cargo.toml entries and fails if they don't match the
members list; implement a small script (bash/python) that reads Cargo.toml's
workspace.members, globs contracts/*/Cargo.toml to derive expected members,
compares the two sets, and exits nonzero on mismatch (hook it into the existing
CI workflow so PRs fail until synced), referencing the Cargo.toml
[workspace].members key and the contracts/*/Cargo.toml pattern.

In `@contracts/game_contract/src/lib.rs`:
- Around line 2313-2352: The test only covers double-call rejection for payout;
add analogous regression tests for each settlement entrypoint (claim_win,
forfeit, claim_timeout_win, payout_tournament, payout_tournament_optimized) to
ensure calling them twice returns AlreadySettled and the contract persists state
= Settled; for each new test reuse the existing pattern (initialize contract,
create/join game, set game state to Completed and winner if needed inside
env.as_contract, call the settlement method once via the client, then call the
corresponding try_* variant and assert Err(Ok(ContractError::AlreadySettled)))
so any missing state write in functions like claim_win, forfeit,
claim_timeout_win, payout_tournament, or payout_tournament_optimized is caught.
- Around line 1294-1309: Remove the redundant intermediate assignment to
GameState::Completed in the winner branch: in the match handling where Some(ref
winner_addr) is processed (checking winner_addr against game.player1 and
game.player2 and calling Self::process_payout), drop the line setting game.state
= GameState::Completed before calling Self::process_payout and directly set
game.state = GameState::Settled after process_payout; this keeps the state
transition linear (InProgress → Settled) and avoids the dead store since
process_payout does not read game.state.
🪄 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: 871394f6-697f-4e7c-bcf0-0803bed2a56e

📥 Commits

Reviewing files that changed from the base of the PR and between db43c34 and e3b158a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • Cargo.toml
  • contracts/game_contract/src/lib.rs
  • contracts/game_contract/src/test.rs
  • 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_with_fee.1.json
  • contracts/game_contract/test_snapshots/test/test_set_max_stake.1.json
  • contracts/game_contract/test_snapshots/test/test_set_max_stake_rejects_non_admin.1.json
  • contracts/game_contract/test_snapshots/tests/test_claim_puzzle_reward_invalid_amount_rejected.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
  • contracts/game_contract/test_snapshots/tests/test_claim_timeout_win_not_reached.1.json
  • contracts/game_contract/test_snapshots/tests/test_claim_timeout_win_rejects_current_turn_player.1.json
  • contracts/game_contract/test_snapshots/tests/test_claim_timeout_win_success.1.json
  • contracts/game_contract/test_snapshots/tests/test_fee_redirection_2_percent.1.json
  • contracts/game_contract/test_snapshots/tests/test_fee_redirection_2_percent_large.1.json
  • contracts/game_contract/test_snapshots/tests/test_file_dispute_rejects_settled_games.1.json
  • contracts/game_contract/test_snapshots/tests/test_file_dispute_success.1.json
  • contracts/game_contract/test_snapshots/tests/test_get_timeout_remaining.1.json
  • contracts/game_contract/test_snapshots/tests/test_payout_cannot_be_called_twice.1.json
  • contracts/game_contract/test_snapshots/tests/test_payout_tournament_optimized_splits_correctly.1.json
  • contracts/game_contract/test_snapshots/tests/test_reject_dispute_refund_fee.1.json
  • contracts/game_contract/test_snapshots/tests/test_resolve_dispute_rejects_already_settled_games.1.json
  • contracts/game_contract/test_snapshots/tests/test_resolve_dispute_winner_takes_all.1.json
  • contracts/game_contract/test_snapshots/tests/test_submit_move_rejects_out_of_turn_and_empty_moves.1.json
  • contracts/game_contract/test_snapshots/tests/test_submit_move_sequence_updates_turn_and_history.1.json
  • contracts/game_contract/test_snapshots/tests/test_usdc_staking_workflow.1.json

Comment on lines +252 to +254
client.initialize_puzzle_rewards(&admin, &admin_key, &0i128, &0u32, &treasury_addr);
let res = client.try_set_max_stake(&attacker, &1_000i128);
assert!(res.is_err());

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

Make the non-admin test assert the specific authorization failure.

Line 254 currently checks only is_err(), so the test can pass on unrelated failures. Please assert the concrete unauthorized error code/variant to ensure this test truly guards the permission check.

🤖 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 252 - 254, The test
currently only checks res.is_err() for client.try_set_max_stake called by
&attacker; replace this with asserting the exact authorization failure by
capturing the error (e.g., let err = res.unwrap_err();) and then asserting it
equals the unauthorized error variant used by the contract (for example
assert_eq!(err, ContractError::Unauthorized) or match on
Err(ContractError::Unauthorized) depending on the error type), referencing
client.try_set_max_stake, attacker and the contract's unauthorized error variant
so the test specifically verifies the permission check.

@gabito1451 gabito1451 merged commit 55d39f3 into OpenKnight-Foundation:main Apr 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants