Rock Paper Scissors

First Flight #38
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: low
Invalid

Missing ReentrancyGuard in `revealMove` Allows Potential Reentrancy in `_finishGame` ETH Transfer

Summary

The revealMove function calls _finishGame, which transfers ETH via .call before finalizing the game state, allowing reentrancy. Without ReentrancyGuard, a malicious winner contract could disrupt execution or manipulate state, violating best practices.

Vulnerability Details

The revealMove function, callable externally, processes a player’s move and, on the final turn (currentTurn == totalTurns), calls the internal _finishGame function. _finishGame transfers ETH to the winner using a low-level. `.call`:

(bool success, ) = _winner.call{value: prize}("");
require(success, "Transfer failed");

Impact

  • Potential disruption of game finalization if a malicious winner contract reenters and triggers unintended logic before game.state is set to Finished.

  • Possible state manipulation if other external functions lack proper GameState checks, allowing reentrant calls to alter contract behavior.

  • Increased audit complexity due to non-standard reentrancy protection, reducing developer trust.

  • No confirmed fund theft, but the risk of logic errors or gas consumption justifies Medium severity (could be High with a proven exploit).

Tools Used

Slither, Aderyn

Recommendations

Add OpenZeppelin’s ReentrancyGuard to revealMove and other external functions calling _finishGame (e.g., settleGame, if present) to prevent reentrancy. Optionally, move the state change (game.state = GameState.Finished) before the .call in _finishGame for additional safety.

+ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
- contract RockPaperScissors {
+ contract RockPaperScissors is ReentrancyGuard {
- function revealMove(uint256 _gameId, uint8 _move, bytes32 _salt) external {
+ function revealMove(uint256 _gameId, uint8 _move, bytes32 _salt) external nonReentrant {
}
- function settleGame(uint256 _gameId) external {
+ function settleGame(uint256 _gameId) external nonReentrant {
}
Updates

Appeal created

m3dython Lead Judge 2 months ago
Submission Judgement Published
Invalidated
Reason: Too generic
m3dython Lead Judge 2 months ago
Submission Judgement Published
Invalidated
Reason: Too generic

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.