P2P Game Wagering Contract โ Independent Security Audit
| Severity | Count |
|---|---|
| CRITICAL | 0 |
| HIGH | 1 |
| MEDIUM | 2 |
| LOW | 2 |
| INFO | 2 |
This is a well-structured P2P wagering contract using Clarity 4's as-contract? with explicit with-ft allowances. Signature authentication follows SIP-018 structured data with proper domain separation via auth-v7. The main risks are centralization around the oracle role and fee extraction via cancellation.
The contract implements a custodial P2P wagering system:
as-contract? with explicit with-ft allowances โ eliminates blanket asset accessused-signatures map prevents replay attacksLocation: resolve-game, cancel-game
Description: The oracle principal has unchecked power to resolve any game in favor of either player. There is no dispute mechanism, no timelock on resolution, no multi-oracle quorum, and no on-chain game state verification. A compromised or malicious oracle can systematically resolve games in favor of a colluding player.
(define-public (resolve-game (game-id uint) (winner (buff 33)))
(let (...)
(asserts! (is-eq tx-sender (var-get oracle)) err-not-oracle)
;; Oracle picks any player as winner โ no verification of actual game outcome
(asserts!
(or (is-eq winner (get player-a game))
(is-eq winner (get player-b game)))
err-invalid-winner)
(credit-balance winner token payout)
...))
Impact: Total loss of wagered funds for all users if oracle is compromised. The oracle can also cancel-game at any time (no timeout required for oracle), extracting fees from both players.
Recommendation:
Location: cancel-game
Description: The oracle can cancel any active game without waiting for the timeout. Cancellation charges withdraw-fee-rate to both players. A malicious oracle could let users create games, then cancel them instead of resolving, farming fees.
(define-public (cancel-game (game-id uint))
(let (...)
(asserts!
(or (is-eq tx-sender (var-get oracle)) ;; Oracle can cancel immediately
(> burn-block-height (+ (get created-at game) GAME_TIMEOUT)))
err-game-not-expired)
;; Both players lose fee
(credit-balance (get player-a game) tokn refund) ;; refund = wager - fee
...))
Impact: At current withdraw-fee-rate of 1% (u100), the oracle could extract 1% of every wager by cancelling instead of resolving. With maximum fee rate (10%), this becomes significant.
Recommendation: Oracle-initiated cancellations should refund the full wager (no fee), or require a minimum number of blocks before oracle can cancel.
Location: set-fee-rate, set-withdraw-fee-rate, set-treasury
Description: The deployer can change fee rates up to 20% (wager) and 10% (withdraw) at any time, and redirect the treasury to any address. These changes apply immediately to all future game resolutions and withdrawals, with no timelock or user notification.
(define-public (set-fee-rate (new-fee-rate uint))
(begin
(asserts! (is-eq tx-sender DEPLOYER) err-not-deployer)
(asserts! (<= new-fee-rate u2000) err-invalid-amount) ;; Up to 20%
(var-set fee-rate new-fee-rate)
(ok true)))
Impact: Users who deposited under one fee regime may have their funds subject to significantly higher fees. Combined with treasury redirect, this is a rug vector.
Recommendation:
Location: resolve-game
Description: Games can only be resolved with a winner or cancelled. There is no draw mechanism that returns full wagers without charging fees. If a game ends in a draw, the only option is cancel-game which charges withdraw-fee-rate to both players.
Impact: Players lose funds to fees on legitimate draws. For games with frequent draws, this creates an unfair cost.
Recommendation: Add a draw-game function that returns full wagers with no fee, callable by oracle only.
Location: set-oracle, set-fee-rate, set-withdraw-fee-rate, set-treasury
Description: All admin configuration changes execute silently with no print events. This makes it difficult for users and monitoring systems to detect changes to critical parameters like oracle address, fee rates, and treasury.
Impact: Reduced transparency. Fee changes or oracle swaps could go unnoticed by users.
Recommendation: Add (print { event: "oracle-updated", ... }) to all admin functions, consistent with existing event patterns in deposit, withdraw, etc.
Location: resolve-game, withdraw
Description: When a game is resolved, the winner's payout already has the wager fee deducted (up to 5% of pot). When the winner later withdraws, they pay an additional withdrawal fee (up to 1%). This results in a cumulative fee that may not be obvious to users.
Impact: At default rates: 5% wager fee + 1% withdrawal fee = ~5.95% effective fee. At maximum rates: 20% + 10% = ~28% effective fee.
Recommendation: Clearly document the fee structure for users. Consider waiving withdrawal fees on game payouts.
Location: deposit
Description: Anyone can deposit tokens to any pubkey without proving ownership of that pubkey. While likely by design (allowing deposits on behalf of others), this means an attacker could create many small deposits to arbitrary pubkeys, polluting the state.
Impact: Minimal โ state pollution only. The funds are still accessible by the pubkey owner.
Recommendation: Consider minimum deposit amounts or requiring signature auth for deposits (similar to the build-wager-deposit-hash that exists in auth-v7 but is unused in game-wager-v1).
The companion contract SP28MP1HQDJWQAFSQJN2HBAXBVP7H7THD1W2NYZVK.auth-v7 was reviewed for signature security:
0x534950303138)chain-id and the specific contract principal โ prevents cross-chain and cross-contract replaytopic field โ prevents cross-function replayauth-id in every message provides a nonce mechanism (combined with used-signatures map in game-wager-v1)build-wager-deposit-hash function, suggesting a planned signed-deposit feature not yet implemented