Last active
September 30, 2022 23:45
-
-
Save pcaversaccio/4fff170db1a25876dc13e6b7e1942bf8 to your computer and use it in GitHub Desktop.
DoubleOrNothing: Send ETH to the contract and either get double back (if the randomness beacon + a pre-committed nonce is even) or lose the bet (if the randomness beacon + a pre-committed nonce is odd). The assumption is that the contract is sufficiently funded by enough lost bets!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: WTFPL | |
pragma solidity 0.8.17; | |
/** | |
* @dev Error that occurs when called by a contract account. | |
* @param emitter The contract that emits the error. | |
*/ | |
error ContractCallsAreNotAllowed(address emitter); | |
/** | |
* @dev Error that occurs when no bet has been prepared. | |
* @param emitter The contract that emits the error. | |
*/ | |
error NoBetPrepared(address emitter); | |
/** | |
* @dev Error that occurs when a game cannot be played yet. | |
* @param emitter The contract that emits the error. | |
*/ | |
error TooEarly(address emitter); | |
/** | |
* @dev Error that occurs when a message digest, | |
* given a nonce, cannot be verified. | |
* @param emitter The contract that emits the error. | |
*/ | |
error NonceMismatch(address emitter); | |
/** | |
* @title Double or Nothing on Ethereum Mainnet | |
* @author pcaversaccio | |
* @dev Send ETH to the contract and either get double back | |
* (if the randomness beacon + a pre-committed nonce is even) | |
* or lose the bet (if the randomness beacon + a pre-committed | |
* nonce is odd). The assumption is that the contract is sufficiently | |
* funded by enough lost bets! | |
*/ | |
contract DoubleOrNothing { | |
/** | |
* @dev To prevent manipulations, a player must wait 2 epochs | |
* (each epoch has 32 slots with 12 seconds intervals) before | |
* a game can be played. | |
*/ | |
uint16 private constant _WAITING_PERIOD = 12 * 32 * 2; | |
/** | |
* @dev The struct that represents a bet made by `msg.sender`. | |
* To prevent manipulation (e.g. front running), a commit-reveal | |
* scheme is used. | |
*/ | |
struct Bet { | |
bytes32 digest; | |
uint256 timestamp; | |
} | |
mapping(address => Bet) public bets; | |
/** | |
* @dev Event that is emitted when a bet is prepared. | |
* @param player The 20-byte address of the player. | |
*/ | |
event BetPrepared(address indexed player); | |
/** | |
* @dev Event that is emitted when a bet is won. | |
* @param player The 20-byte address of the player. | |
* @param amount The amount (i.e. 2 * msg.value) that is won. | |
*/ | |
event Won(address indexed player, uint256 amount); | |
/** | |
* @dev Event that is emitted when a bet is lost. | |
* @param player The 20-byte address of the player. | |
* @param amount The amount (i.e. msg.value) that is lost. | |
*/ | |
event Lost(address indexed player, uint256 amount); | |
constructor() payable {} | |
/** | |
* @notice Prepares a future bet. | |
* @param nonceDigest The 32-byte nonce digest. | |
*/ | |
function prepareBet(bytes32 nonceDigest) external { | |
/** | |
* @dev Since EIP-3074 (https://eips.ethereum.org/EIPS/eip-3074) | |
* allows for `authorized` to equal `tx.origin`, | |
* the following check will fail under this new regime. | |
*/ | |
// solhint-disable-next-line avoid-tx-origin | |
if (tx.origin != msg.sender) | |
revert ContractCallsAreNotAllowed(address(this)); | |
bets[msg.sender] = Bet({ | |
digest: nonceDigest, | |
// solhint-disable-next-line not-rely-on-time | |
timestamp: block.timestamp | |
}); | |
emit BetPrepared(msg.sender); | |
} | |
/** | |
* @notice Good luck! | |
* @param nonce The 8-byte nonce number. | |
*/ | |
function play(uint64 nonce) external payable { | |
Bet memory bet = bets[msg.sender]; | |
if (bet.digest != 0) revert NoBetPrepared(address(this)); | |
// solhint-disable-next-line not-rely-on-time | |
if (block.timestamp <= bet.timestamp + _WAITING_PERIOD) | |
revert TooEarly(address(this)); | |
if (bet.digest != keccak256(abi.encodePacked(nonce, msg.sender))) | |
revert NonceMismatch(address(this)); | |
/** | |
* @dev It is theoretically possible that this addition overflows. | |
* In such a case, the transaction is reverted and the user must | |
* retry in the next block. | |
*/ | |
if ((block.difficulty + nonce) % 2 == 0) { | |
payable(msg.sender).transfer(2 * msg.value); | |
emit Won(msg.sender, 2 * msg.value); | |
} else { | |
emit Lost(msg.sender, msg.value); | |
} | |
delete bets[msg.sender]; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment