The RockPaperScissors
contract allows players to stake WinningToken
tokens when joining a game via the joinGameWithToken()
function. These tokens are transferred into the contract using transferFrom()
and are held by the contract during gameplay.
However, at the conclusion of the game-whether it ends in a win (_finishGame())
, draw (_handleTie())
, or is cancelled (_cancelGame())
-, the originally staked tokens are neither returned to players nor burned. Instead, the contract simply mints new WinningToken
tokens to the winner (or to both players in a draw), while the original tokens remain indefinitely locked inside the contract.
Although WinningToken
inherits from ERC20Burnable
, the burn()
functionality is not utilized. Moreover, the contract lacks any admin function that could reclaim or manage the accumulated tokens. Over time, this behavior leads to silent inflation and unnecessary token accumulation, which could impact the token economy and contract sustainability.
Tokens transferred by players are never recovered or destroyed, effectively becoming inaccessible.
The system silently inflates the WinningToken supply by minting new tokens for winners, while keeping the original staked tokens in contract storage.
This may mislead users or third parties inspecting totalSupply, and could lead to long-term sustainability issues.
Accumulation of unused tokens within the contract could require emergency administrative intervention in the future.
Test for the cancelGame()
function
Capture the balances of playerA and the RockPaperScissors contract.
playerA approves the RockPaperScissors contract for 1 token.
playerA creates a new game.
Capture the balances during the game.
playerA cancels the game.
Capture the balances after the game.
This test checks balances before and after creating and canceling a game. When playerA transfers 1 token to the RockPaperScissors contract, it remains locked inside the contract even after the game is canceled.
Although playerA is minted a new token during the cancellation process, the originally transferred token is neither returned nor burned, causing tokens to accumulate in the contract over time. This same behavior is present in tie scenarios via _handleTie() and when the game ends via _finishGame(), where new tokens are minted, but previously transferred tokens are never handled.
This progressive accumulation of locked tokens may lead to sustainability issues and could require future administrative intervention or additional token management logic.
Manual Review and Foundry
To prevent token accumulation within the contract, we recommend the following:
Burn the originally staked tokens upon game resolution (_cancelGame
, _handleTie
, _finishGame
), instead of leaving them locked.
While the WinningToken contract inherits from ERC20Burnable, it does not expose any burnFrom()
-like functionality for external contracts to burn held tokens.
Implement a restricted burn()
function callable by the RockPaperScissors
contract (or its owner) to cleanly dispose of these locked tokens.
Add a burn function to the WinningToken contract:
Update game resolution functions:
To prevent WinningToken from being permanently locked due to direct transfers, implement an admin-only recovery function:
Mints new tokens upon game completion or cancellation for token-based games
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.