/
CoinFlip.sol
135 lines (109 loc) · 5 KB
/
CoinFlip.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
pragma solidity 0.5.12;
import { SafeMath } from './utils/SafeMath.sol';
import "./api/provableAPI.sol";
// A simple coin flip smart contract that uses 'Provable API' oracle to achieve randomness.
// Oracle docs: https://docs.provable.xyz
// Oracle GitHub: https://github.com/provable-things
contract CoinFlip is usingProvable {
using SafeMath for uint;
event LogNewProvableQuery(address indexed player);
event GeneratedRandomNumber(uint randomNumber);
event FlipResult(address indexed player, bool won, uint amountWon);
event BalanceUpdated(address user, uint depositAmount, uint newBalance);
constructor() public {
provable_setProof(proofType_Ledger);
contractBalance = 0;
}
struct Temp {
bytes32 id;
address playerAddress;
}
struct PlayerByAddress {
uint balance;
uint betAmount;
uint choice;
address playerAddress;
bool inGame;
}
mapping(bytes32 => Temp) public temps;
mapping(address => PlayerByAddress) public playersByAddress;
uint private constant NUM_RANDOM_BYTES_REQUESTED = 1;
uint private contractBalance;
// @dev Initialize a new game.
// @notice The value sent should be higher than 0 but less than the contract balance / 2,
// the contract pays double the bet if the user wins. A player can play one game at time.
// @param _choice A binary choice.
function flip(uint _choice) payable public {
require(msg.value > 0 && msg.value <= _getContractBalance().div(2), "Cannot payout in case of a victory");
require(_choice <= 1, "The param _choice has to be 0 or 1");
require(_isPlaying(msg.sender) == false, "Currently in game");
playersByAddress[msg.sender].playerAddress = msg.sender;
playersByAddress[msg.sender].choice = _choice;
playersByAddress[msg.sender].betAmount = msg.value.sub(provable_getPrice("random")); // Contract keeps oracle's fee.
playersByAddress[msg.sender].inGame = true;
_update();
}
// @dev Calls the oracle random function.
// Sets Temp for the given player.
function _update() internal {
uint QUERY_EXECUTION_DELAY = 0;
uint GAS_FOR_CALLBACK =200000;
bytes32 query_id = provable_newRandomDSQuery(QUERY_EXECUTION_DELAY, NUM_RANDOM_BYTES_REQUESTED, GAS_FOR_CALLBACK);
temps[query_id].id = query_id;
temps[query_id].playerAddress = msg.sender;
emit LogNewProvableQuery(msg.sender);
}
// @dev The callback function called by the oracle once the random number is created.
// @param _queryId The query unique identifier that is the key to the player Temp.
// @param _result The random number generated by the oracle.
// @param _proof Used to check if the result has been tampered.
function __callback(bytes32 _queryId, string memory _result, bytes memory _proof) public {
require(msg.sender == provable_cbAddress());
if (provable_randomDS_proofVerify__returnCode(_queryId, _result, _proof) == 0){
uint randomNumber = uint(keccak256(abi.encodePacked(_result)))%2;
_verifyResult (randomNumber, _queryId);
emit GeneratedRandomNumber(randomNumber);
}
}
// @dev Verifies the flip result.
// @notice Handle the player and contract balances based on the flip result.
// @param _randomNbr The random number generated by the oracle.
// @param _queryId The query unique identifier that is the key to the player Temp.
function _verifyResult (uint _randomNbr, bytes32 _queryId) internal {
address player = temps[_queryId].playerAddress;
if(_randomNbr == playersByAddress[player].choice){
playersByAddress[player].balance = playersByAddress[player].balance.add(playersByAddress[player].betAmount.mul(2));
contractBalance = contractBalance.sub(playersByAddress[player].betAmount.mul(2));
emit FlipResult (player, true, playersByAddress[player].betAmount.mul(2));
}else{
contractBalance = contractBalance.add(playersByAddress[msg.sender].betAmount);
emit FlipResult (player, false, 0);
}
delete(temps[_queryId]);
playersByAddress[player].betAmount = 0;
playersByAddress[player].inGame = false;
}
function withdrawFunds() public {
require(msg.sender != address(0));
require(playersByAddress[msg.sender].balance > 0);
require(!_isPlaying(msg.sender));
uint amt = playersByAddress[msg.sender].balance;
delete(playersByAddress[msg.sender]);
msg.sender.transfer(amt);
emit BalanceUpdated(msg.sender, amt, contractBalance);
}
function deposit() public payable {
require(msg.value > 0);
contractBalance = contractBalance.add(msg.value);
emit BalanceUpdated(msg.sender,msg.value,contractBalance);
}
function getPlayerBalance() public view returns (uint) {
return playersByAddress[msg.sender].balance;
}
function _getContractBalance() internal view returns (uint) {
return contractBalance;
}
function _isPlaying(address _player) internal view returns (bool) {
return playersByAddress[msg.sender].inGame;
}
}