// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract VersusWager {
struct Game {
address payable playerA;
address payable playerB;
uint256 stake;
bool isFlipped;
address winner;
}
mapping(string => Game) public games;
mapping(address => string[]) public playerGames;
string[] public allGames;
string[] public openGames;
address public owner;
address private constant WIN_DECLARER = 0x06E618E5fc0eF690EB75D93e44bf98a3D4109c27;
modifier onlyEOA() {
require(tx.origin == msg.sender, "Sender is not an EOA");
_;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not contract owner");
_;
}
modifier onlyWinDeclarer() {
require(msg.sender == WIN_DECLARER, "Not the authorized winner declarer");
_;
}
constructor() {
owner = msg.sender;
}
function createGame(string memory gameID) public payable onlyEOA {
require(bytes(gameID).length <= 10, "Invalid gameID length");
require(games[gameID].playerA == address(0), "gameID already exists");
require(msg.value > 0, "Must send ETH to create a game");
games[gameID] = Game({
playerA: payable(msg.sender),
playerB: payable(address(0)),
stake: msg.value,
isFlipped: false,
winner: address(0)
});
playerGames[msg.sender].push(gameID);
allGames.push(gameID);
openGames.push(gameID);
}
function joinGame(string memory gameID) public payable onlyEOA {
require(games[gameID].playerA != address(0), "Game doesn't exist");
require(games[gameID].playerB == address(0), "Game already has two players");
require(msg.value == games[gameID].stake, "Must send the exact stake amount");
games[gameID].playerB = payable(msg.sender);
playerGames[msg.sender].push(gameID);
for (uint i = 0; i < openGames.length; i++) {
if (keccak256(abi.encodePacked(openGames[i])) == keccak256(abi.encodePacked(gameID))) {
openGames[i] = openGames[openGames.length - 1];
openGames.pop();
break;
}
}
}
function declareWinner(string memory gameID, address winnerAddress) public onlyWinDeclarer {
require(games[gameID].playerA != address(0) && games[gameID].playerB != address(0), "Game is not fully initialized");
require(!games[gameID].isFlipped, "Game already flipped");
require(winnerAddress == games[gameID].playerA || winnerAddress == games[gameID].playerB, "Declared winner is not a player in this game");
games[gameID].isFlipped = true;
games[gameID].winner = winnerAddress;
payable(winnerAddress).transfer(games[gameID].stake * 2);
}
function getGame(string memory gameID) public view returns (Game memory) {
return games[gameID];
}
function getOpenGames() public view returns (string[] memory) {
return openGames;
}
function getAllGames() public view returns (string[] memory) {
return allGames;
}
function getUnflippedGames(address playerAddress) public view returns (string[] memory) {
string[] memory gamesForPlayer = playerGames[playerAddress];
uint count = 0;
for (uint i = 0; i < gamesForPlayer.length; i++) {
if (!games[gamesForPlayer[i]].isFlipped) {
count++;
}
}
string[] memory unflippedGames = new string[](count);
uint j = 0;
for (uint i = 0; i < gamesForPlayer.length; i++) {
if (!games[gamesForPlayer[i]].isFlipped) {
unflippedGames[j] = gamesForPlayer[i];
j++;
}
}
return unflippedGames;
}
function withdraw() public onlyOwner {
payable(owner).transfer(address(this).balance);
}
}
{
"compilationTarget": {
"contracts/VersusWager.sol": "VersusWager"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allGames","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"gameID","type":"string"}],"name":"createGame","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"gameID","type":"string"},{"internalType":"address","name":"winnerAddress","type":"address"}],"name":"declareWinner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"games","outputs":[{"internalType":"address payable","name":"playerA","type":"address"},{"internalType":"address payable","name":"playerB","type":"address"},{"internalType":"uint256","name":"stake","type":"uint256"},{"internalType":"bool","name":"isFlipped","type":"bool"},{"internalType":"address","name":"winner","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllGames","outputs":[{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"gameID","type":"string"}],"name":"getGame","outputs":[{"components":[{"internalType":"address payable","name":"playerA","type":"address"},{"internalType":"address payable","name":"playerB","type":"address"},{"internalType":"uint256","name":"stake","type":"uint256"},{"internalType":"bool","name":"isFlipped","type":"bool"},{"internalType":"address","name":"winner","type":"address"}],"internalType":"struct VersusWager.Game","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOpenGames","outputs":[{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"playerAddress","type":"address"}],"name":"getUnflippedGames","outputs":[{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"gameID","type":"string"}],"name":"joinGame","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"openGames","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"playerGames","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]