RareSkills Solidity Interview Question #48 Answered: How would you design a game of rock-paper-scissors in a smart contract such that players cannot cheat?

This series will provide answers to the list of Solidity interview questions that were published by RareSkills..

Question #48 (Medium): How would you design a game of rock-paper-scissors in a smart contract such that players cannot cheat?

Answer: I would design the smart contract for a game of rock-paper-scissors to require players to submit their choice using a commit-reveal scheme. This design would prevent players from seeing each other’s choice. Also, it would mean that players could not change their choice once it’s committed.

Demonstration:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

contract RockPaperScissors {
enum Move { None, Rock, Paper, Scissors }

struct Player {
bytes32 commitment;
Move revealedMove;
}

address public player1;
address public player2;

mapping(address => Player) public players;

uint256 public revealDeadline;

bool public gameStarted;

modifier onlyPlayers() {
require(msg.sender == player1 || msg.sender == player2, “Not a player”);
_;
}

function startGame(address _player2) external {
require(!gameStarted, “Game already started”);

player1 = msg.sender;
player2 = _player2;
gameStarted = true;
revealDeadline = block.timestamp + 1 days;
}

function commitMove(bytes32 _commitment) external onlyPlayers {
// Players can only commit a choice once
require(
players[msg.sender].commitment == bytes32(0),
“Already committed”
);

players[msg.sender].commitment = _commitment;
}

function revealMove(Move _move, string memory _nonce) external onlyPlayers {
require(
players[msg.sender].commitment != bytes32(0),
“No commitment found”
);
require(
players[msg.sender].revealedMove == Move.None,
“Already revealed”
);

/**
* NOTE: _move is a uint8. Make sure to pass the correct type to the
* commitment hash so that it matches this.
*/
bytes32 hash = keccak256(abi.encodePacked(_move, _nonce));

/**
* If the player tries to submit a different move than the one they
* previously committed, the hash would be different from the
* commitment hash and thus, this check would fail.
*/
require(
hash == players[msg.sender].commitment,
“Invalid move or nonce”
);

players[msg.sender].revealedMove = _move;
}

function determineWinner() external view onlyPlayers returns (string memory) {
require(
players[player1].revealedMove != Move.None &&
players[player2].revealedMove != Move.None,
“Both players must reveal their moves”
);

Move move1 = players[player1].revealedMove;
Move move2 = players[player2].revealedMove;

if (move1 == move2) {
return “It’s a draw!”;
} else if (
(move1 == Move.Rock && move2 == Move.Scissors) ||
(move1 == Move.Paper && move2 == Move.Rock) ||
(move1 == Move.Scissors && move2 == Move.Paper))
{
return “Player 1 wins!”;
} else {
return “Player 2 wins!”;
}
}

function forceForfeit() external returns (string memory) {
require(
block.timestamp > revealDeadline,
“Reveal deadline has not passed”
);

if (
players[player1].revealedMove == Move.None &&
players[player2].revealedMove == Move.None
) {
// Restart the game if neither player has revealed before the deadline
revealDeadline = block.timestamp + 1 days;
} else if (players[player1].revealedMove == Move.None) {
// Force player 2 to win
return “Player 2 wins!”;
} else if(players[player2].revealedMove == Move.None) {
// Force player 1 to win
return “Player 1 wins!”;
}
}
}

Further Discussion:

The commit-reveal scheme is commonly used in blockchain games to prevent cheating tactics, such as front-running. This is effective because each player’s choice is hashed, along with a secret value, and thus, sufficiently concealed.

Connect with me:

LinkedIn: https://www.linkedin.com/in/faybianbyrd/Twitter: https://twitter.com/FaybianByrdGithub: https://github.com/FaybianB

Latest articles:

Question #38 (Medium): What is frontrunning?Question #39 (Medium): What is a commit-reveal scheme and when would you use it?Question #40 (Medium): Under what circumstances could abi.encodePacked create a vulnerability?Question #41 (Medium): How does Ethereum determine the BASEFEE in EIP-1559?Question #42 (Medium): What is the difference between a cold read and a warm read?Question #43 (Medium): How does an AMM price assets?Question #44 (Medium): What is a function selector clash in a proxy and how does it happen?Question #45 (Medium): What is the effect on gas of making a function payable?Question #46 (Medium): What is a signature replay attack?Question #47 (Medium): What is gas griefing?

Disclosure: Some content may have been generated with the help of artificial intelligence.

RareSkills Solidity Interview Question #48 Answered: How would you design a game of… was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.

By

Leave a Reply

Your email address will not be published. Required fields are marked *