Skip to content

Instantly share code, notes, and snippets.

@kmbarry1
Last active April 11, 2022 23:41
Show Gist options
  • Save kmbarry1/7393f16f4e07053a56c2ba6b5a6041f9 to your computer and use it in GitHub Desktop.
Save kmbarry1/7393f16f4e07053a56c2ba6b5a6041f9 to your computer and use it in GitHub Desktop.
Minority Wins...
// BUGGED, DO NOT DEPLOY
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.11;
/// @title AvsBGame
/// @notice A Liar's Game: Vote by putting ETH behind A or B ... the side with the least votes gets all the ETH
/// @author AvsB Team
/// @dev Built using a Commit-Reveal scheme
contract AvsBGame {
/// ============ Types ============
// Possible votes (and Hidden before votes are revealed)
enum Choice {
Hidden,
A,
B
}
// A cryptographic committment to a certain vote
struct VoteCommit {
bytes32 commitment;
uint256 amount;
Choice choice;
}
/// ============ Immutable storage ============
uint256 public immutable voteDeadline = 1643702400; // Vote phase ends Feb 1, 2022
uint256 public immutable revealDeadline = 1644912000; // Reveal phase ends Feb 15, 2022
uint256 public immutable minVoteIncrement = 5e15; // 0.005 ETH
/// ============ Mutable storage ============
// Tracks vote commitments
mapping(address => VoteCommit) public votes;
// Tracks revealed votes, updated every reveal
// We need to track these because some votes may remain unrevealed
uint256 public revealedA = 0;
uint256 public revealedB = 0;
// Stores total prize pool (only updated during payout phase)
uint256 public prizePool = 0;
/// ============ Events ============
event Vote(address player, uint256 amount);
event Reveal(address player, Choice choice);
event Payout(address player, uint256 amount);
constructor() {}
/// ============ Functions ============
/// @notice Cast a vote without revealing the vote by posting a commitment
/// @param commitment Commitment to A or B, by commit-reveal scheme
function castHiddenVote(bytes32 commitment) external payable {
// Ensure vote is placed before vote deadline
require(
block.timestamp <= voteDeadline,
"Cannot vote past the vote deadline."
);
// Ensure vote is greater than and a multiple of min vote increment
require(
msg.value >= minVoteIncrement && msg.value % minVoteIncrement == 0,
"Vote value must be greater than and multiple of minimum vote amount."
);
// Ensure player has not voted before
require(votes[msg.sender].amount == 0, "Cannot vote twice.");
// Store the commitment for the commit-reveal scheme
votes[msg.sender] = VoteCommit(commitment, msg.value, Choice.Hidden);
// Emit Vote event
emit Vote(msg.sender, msg.value);
}
/// @notice Reveal a vote that was previously commited to
/// @param choice Choice that is being revealed by sender
/// @param blindingFactor Salt used by the voter in their previous vote commitment
function reveal(Choice choice, bytes32 blindingFactor) external {
// Ensure reveal is before reveal deadline ("early" reveals during voting period are technically permitted)
require(
block.timestamp <= revealDeadline,
"Cannot reveal past the reveal deadline."
);
// Ensure reveal is either for choice A or B
require(
choice == Choice.A || choice == Choice.B,
"Invalid choice, must reveal A or B."
);
// Ensure sender has not already revealed
require(votes[msg.sender].choice == Choice.Hidden, "Already revealed.");
// Check hash and reveal if correct
VoteCommit storage vote = votes[msg.sender];
require(
keccak256(abi.encodePacked(msg.sender, choice, blindingFactor)) ==
vote.commitment,
"Invalid reveal, hash does not match committment."
);
vote.choice = choice;
// Update revealed vote counts
if (choice == Choice.A) {
revealedA += vote.amount;
} else {
revealedB += vote.amount;
}
// Emit reveal event
emit Reveal(msg.sender, choice);
}
/// @notice Claim payout at game end
function claimPayout() external {
// Ensure reveal deadline has passed before claiming payout
require(
block.timestamp > revealDeadline,
"Cannot claim payout before reveal deadline has passed."
);
// Require that sender has revealed a vote on the winning side
VoteCommit memory senderVote = votes[msg.sender];
require(
senderVote.choice != Choice.Hidden,
"Cannot claim payout since vote was not revealed."
);
// If first time being called, choose winner
Choice winner = getWinner();
// Require that sender is winner to claim funds
// If a tie, winner is returned as Choice.Hidden
require(
senderVote.choice == winner || winner == Choice.Hidden,
"Cannot claim payout since did not win game."
);
// Claim share of winnings
uint256 denominator;
if (winner == Choice.A) {
denominator = revealedA;
} else if (winner == Choice.B) {
denominator = revealedB;
} else {
// Everybody wins
denominator = revealedA + revealedB;
}
uint256 winnings = (prizePool * senderVote.amount) / denominator;
payable(msg.sender).transfer(winnings);
// Emit payout event
emit Payout(msg.sender, winnings);
}
/// @notice Returns winner, returns Hidden if tie
function getWinner() private returns (Choice) {
if (prizePool == 0) {
// Set prize pool to be remaining funds in the contract
prizePool = address(this).balance;
}
// Choose winner
// In case one side did not reveal any votes, the other side winw
// One side must have revealed votes as required in claimPayout
if (revealedA == 0) {
return Choice.B;
} else if (revealedB == 0) {
return Choice.A;
} else if (revealedA < revealedB) {
return Choice.A;
} else if (revealedA > revealedB) {
return Choice.B;
} else {
return Choice.Hidden;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment