377 lines
12 KiB
Solidity
377 lines
12 KiB
Solidity
|
/*
|
||
|
* @source: etherscan.io
|
||
|
* @author: -
|
||
|
* @vulnerable_at_lines: 150
|
||
|
*/
|
||
|
|
||
|
pragma solidity ^0.4.24;
|
||
|
|
||
|
/* This is fiftyflip
|
||
|
a simple yet elegant game contract
|
||
|
that is connected to Proof of Community
|
||
|
contract(0x1739e311ddBf1efdFbc39b74526Fd8b600755ADa).
|
||
|
|
||
|
Greed serves no-one but the one,
|
||
|
But charity is kind, suffereth not and envieth not.
|
||
|
Charity is to give of oneself in the service of his fellow beings.
|
||
|
|
||
|
Play on Players. and Remember fifty feeds the multiudes and gives to the PoC community
|
||
|
Forever and ever.
|
||
|
|
||
|
|
||
|
*/
|
||
|
|
||
|
|
||
|
contract FiftyFlip {
|
||
|
uint constant DONATING_X = 20; // 2% kujira
|
||
|
|
||
|
// Need to be discussed
|
||
|
uint constant JACKPOT_FEE = 10; // 1% jackpot
|
||
|
uint constant JACKPOT_MODULO = 1000; // 0.1% jackpotwin
|
||
|
uint constant DEV_FEE = 20; // 2% devfee
|
||
|
uint constant WIN_X = 1900; // 1.9x
|
||
|
|
||
|
// There is minimum and maximum bets.
|
||
|
uint constant MIN_BET = 0.01 ether;
|
||
|
uint constant MAX_BET = 1 ether;
|
||
|
|
||
|
uint constant BET_EXPIRATION_BLOCKS = 250;
|
||
|
|
||
|
// owner and PoC contract address
|
||
|
address public owner;
|
||
|
address public autoPlayBot;
|
||
|
address public secretSigner;
|
||
|
address private whale;
|
||
|
|
||
|
// Accumulated jackpot fund.
|
||
|
uint256 public jackpotSize;
|
||
|
uint256 public devFeeSize;
|
||
|
|
||
|
// Funds that are locked in potentially winning bets.
|
||
|
uint256 public lockedInBets;
|
||
|
uint256 public totalAmountToWhale;
|
||
|
|
||
|
|
||
|
struct Bet {
|
||
|
// Wager amount in wei.
|
||
|
uint amount;
|
||
|
// Block number of placeBet tx.
|
||
|
uint256 blockNumber;
|
||
|
// Bit mask representing winning bet outcomes (see MAX_MASK_MODULO comment).
|
||
|
bool betMask;
|
||
|
// Address of a player, used to pay out winning bets.
|
||
|
address player;
|
||
|
}
|
||
|
|
||
|
mapping (uint => Bet) bets;
|
||
|
mapping (address => uint) donateAmount;
|
||
|
|
||
|
// events
|
||
|
event Wager(uint ticketID, uint betAmount, uint256 betBlockNumber, bool betMask, address betPlayer);
|
||
|
event Win(address winner, uint amount, uint ticketID, bool maskRes, uint jackpotRes);
|
||
|
event Lose(address loser, uint amount, uint ticketID, bool maskRes, uint jackpotRes);
|
||
|
event Refund(uint ticketID, uint256 amount, address requester);
|
||
|
event Donate(uint256 amount, address donator);
|
||
|
event FailedPayment(address paidUser, uint amount);
|
||
|
event Payment(address noPaidUser, uint amount);
|
||
|
event JackpotPayment(address player, uint ticketID, uint jackpotWin);
|
||
|
|
||
|
// constructor
|
||
|
constructor (address whaleAddress, address autoPlayBotAddress, address secretSignerAddress) public {
|
||
|
owner = msg.sender;
|
||
|
autoPlayBot = autoPlayBotAddress;
|
||
|
whale = whaleAddress;
|
||
|
secretSigner = secretSignerAddress;
|
||
|
jackpotSize = 0;
|
||
|
devFeeSize = 0;
|
||
|
lockedInBets = 0;
|
||
|
totalAmountToWhale = 0;
|
||
|
}
|
||
|
|
||
|
// modifiers
|
||
|
modifier onlyOwner() {
|
||
|
require (msg.sender == owner, "You are not the owner of this contract!");
|
||
|
_;
|
||
|
}
|
||
|
|
||
|
modifier onlyBot() {
|
||
|
require (msg.sender == autoPlayBot, "You are not the bot of this contract!");
|
||
|
_;
|
||
|
}
|
||
|
|
||
|
modifier checkContractHealth() {
|
||
|
require (address(this).balance >= lockedInBets + jackpotSize + devFeeSize, "This contract doesn't have enough balance, it is stopped till someone donate to this game!");
|
||
|
_;
|
||
|
}
|
||
|
|
||
|
// betMast:
|
||
|
// false is front, true is back
|
||
|
|
||
|
function() public payable { }
|
||
|
|
||
|
|
||
|
function setBotAddress(address autoPlayBotAddress)
|
||
|
onlyOwner()
|
||
|
external
|
||
|
{
|
||
|
autoPlayBot = autoPlayBotAddress;
|
||
|
}
|
||
|
|
||
|
function setSecretSigner(address _secretSigner)
|
||
|
onlyOwner()
|
||
|
external
|
||
|
{
|
||
|
secretSigner = _secretSigner;
|
||
|
}
|
||
|
|
||
|
// wager function
|
||
|
function wager(bool bMask, uint ticketID, uint ticketLastBlock, uint8 v, bytes32 r, bytes32 s)
|
||
|
checkContractHealth()
|
||
|
external
|
||
|
payable {
|
||
|
Bet storage bet = bets[ticketID];
|
||
|
uint amount = msg.value;
|
||
|
address player = msg.sender;
|
||
|
require (bet.player == address(0), "Ticket is not new one!");
|
||
|
require (amount >= MIN_BET, "Your bet is lower than minimum bet amount");
|
||
|
require (amount <= MAX_BET, "Your bet is higher than maximum bet amount");
|
||
|
require (getCollateralBalance() >= 2 * amount, "If we accept this, this contract will be in danger!");
|
||
|
|
||
|
require (block.number <= ticketLastBlock, "Ticket has expired.");
|
||
|
bytes32 signatureHash = keccak256(abi.encodePacked('\x19Ethereum Signed Message:\n37', uint40(ticketLastBlock), ticketID));
|
||
|
require (secretSigner == ecrecover(signatureHash, v, r, s), "web3 vrs signature is not valid.");
|
||
|
|
||
|
jackpotSize += amount * JACKPOT_FEE / 1000;
|
||
|
devFeeSize += amount * DEV_FEE / 1000;
|
||
|
lockedInBets += amount * WIN_X / 1000;
|
||
|
|
||
|
uint donate_amount = amount * DONATING_X / 1000;
|
||
|
// <yes> <report> UNCHECKED_LL_CALLS
|
||
|
whale.call.value(donate_amount)(bytes4(keccak256("donate()")));
|
||
|
totalAmountToWhale += donate_amount;
|
||
|
|
||
|
bet.amount = amount;
|
||
|
bet.blockNumber = block.number;
|
||
|
bet.betMask = bMask;
|
||
|
bet.player = player;
|
||
|
|
||
|
emit Wager(ticketID, bet.amount, bet.blockNumber, bet.betMask, bet.player);
|
||
|
}
|
||
|
|
||
|
// method to determine winners and losers
|
||
|
function play(uint ticketReveal)
|
||
|
checkContractHealth()
|
||
|
external
|
||
|
{
|
||
|
uint ticketID = uint(keccak256(abi.encodePacked(ticketReveal)));
|
||
|
Bet storage bet = bets[ticketID];
|
||
|
require (bet.player != address(0), "TicketID is not correct!");
|
||
|
require (bet.amount != 0, "Ticket is already used one!");
|
||
|
uint256 blockNumber = bet.blockNumber;
|
||
|
if(blockNumber < block.number && blockNumber >= block.number - BET_EXPIRATION_BLOCKS)
|
||
|
{
|
||
|
uint256 random = uint256(keccak256(abi.encodePacked(blockhash(blockNumber), ticketReveal)));
|
||
|
bool maskRes = (random % 2) !=0;
|
||
|
uint jackpotRes = random % JACKPOT_MODULO;
|
||
|
|
||
|
uint tossWinAmount = bet.amount * WIN_X / 1000;
|
||
|
|
||
|
uint tossWin = 0;
|
||
|
uint jackpotWin = 0;
|
||
|
|
||
|
if(bet.betMask == maskRes) {
|
||
|
tossWin = tossWinAmount;
|
||
|
}
|
||
|
if(jackpotRes == 0) {
|
||
|
jackpotWin = jackpotSize;
|
||
|
jackpotSize = 0;
|
||
|
}
|
||
|
if (jackpotWin > 0) {
|
||
|
emit JackpotPayment(bet.player, ticketID, jackpotWin);
|
||
|
}
|
||
|
if(tossWin + jackpotWin > 0)
|
||
|
{
|
||
|
payout(bet.player, tossWin + jackpotWin, ticketID, maskRes, jackpotRes);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
loseWager(bet.player, bet.amount, ticketID, maskRes, jackpotRes);
|
||
|
}
|
||
|
lockedInBets -= tossWinAmount;
|
||
|
bet.amount = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
revert();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function donateForContractHealth()
|
||
|
external
|
||
|
payable
|
||
|
{
|
||
|
donateAmount[msg.sender] += msg.value;
|
||
|
emit Donate(msg.value, msg.sender);
|
||
|
}
|
||
|
|
||
|
function withdrawDonation(uint amount)
|
||
|
external
|
||
|
{
|
||
|
require(donateAmount[msg.sender] >= amount, "You are going to withdraw more than you donated!");
|
||
|
|
||
|
if (sendFunds(msg.sender, amount)){
|
||
|
donateAmount[msg.sender] -= amount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// method to refund
|
||
|
function refund(uint ticketID)
|
||
|
checkContractHealth()
|
||
|
external {
|
||
|
Bet storage bet = bets[ticketID];
|
||
|
|
||
|
require (bet.amount != 0, "this ticket has no balance");
|
||
|
require (block.number > bet.blockNumber + BET_EXPIRATION_BLOCKS, "this ticket is expired.");
|
||
|
sendRefund(ticketID);
|
||
|
}
|
||
|
|
||
|
// Funds withdrawl
|
||
|
function withdrawDevFee(address withdrawAddress, uint withdrawAmount)
|
||
|
onlyOwner()
|
||
|
checkContractHealth()
|
||
|
external {
|
||
|
require (devFeeSize >= withdrawAmount, "You are trying to withdraw more amount than developer fee.");
|
||
|
require (withdrawAmount <= address(this).balance, "Contract balance is lower than withdrawAmount");
|
||
|
require (devFeeSize <= address(this).balance, "Not enough funds to withdraw.");
|
||
|
if (sendFunds(withdrawAddress, withdrawAmount)){
|
||
|
devFeeSize -= withdrawAmount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Funds withdrawl
|
||
|
function withdrawBotFee(uint withdrawAmount)
|
||
|
onlyBot()
|
||
|
checkContractHealth()
|
||
|
external {
|
||
|
require (devFeeSize >= withdrawAmount, "You are trying to withdraw more amount than developer fee.");
|
||
|
require (withdrawAmount <= address(this).balance, "Contract balance is lower than withdrawAmount");
|
||
|
require (devFeeSize <= address(this).balance, "Not enough funds to withdraw.");
|
||
|
if (sendFunds(autoPlayBot, withdrawAmount)){
|
||
|
devFeeSize -= withdrawAmount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get Bet Info from id
|
||
|
function getBetInfo(uint ticketID)
|
||
|
constant
|
||
|
external
|
||
|
returns (uint, uint256, bool, address){
|
||
|
Bet storage bet = bets[ticketID];
|
||
|
return (bet.amount, bet.blockNumber, bet.betMask, bet.player);
|
||
|
}
|
||
|
|
||
|
// Get Bet Info from id
|
||
|
function getContractBalance()
|
||
|
constant
|
||
|
external
|
||
|
returns (uint){
|
||
|
return address(this).balance;
|
||
|
}
|
||
|
|
||
|
// Get Collateral for Bet
|
||
|
function getCollateralBalance()
|
||
|
constant
|
||
|
public
|
||
|
returns (uint){
|
||
|
if (address(this).balance > lockedInBets + jackpotSize + devFeeSize)
|
||
|
return address(this).balance - lockedInBets - jackpotSize - devFeeSize;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// Contract may be destroyed only when there are no ongoing bets,
|
||
|
// either settled or refunded. All funds are transferred to contract owner.
|
||
|
function kill() external onlyOwner() {
|
||
|
require (lockedInBets == 0, "All bets should be processed (settled or refunded) before self-destruct.");
|
||
|
selfdestruct(owner);
|
||
|
}
|
||
|
|
||
|
// Payout ETH to winner
|
||
|
function payout(address winner, uint ethToTransfer, uint ticketID, bool maskRes, uint jackpotRes)
|
||
|
internal
|
||
|
{
|
||
|
winner.transfer(ethToTransfer);
|
||
|
emit Win(winner, ethToTransfer, ticketID, maskRes, jackpotRes);
|
||
|
}
|
||
|
|
||
|
// sendRefund to requester
|
||
|
function sendRefund(uint ticketID)
|
||
|
internal
|
||
|
{
|
||
|
Bet storage bet = bets[ticketID];
|
||
|
address requester = bet.player;
|
||
|
uint256 ethToTransfer = bet.amount;
|
||
|
requester.transfer(ethToTransfer);
|
||
|
|
||
|
uint tossWinAmount = bet.amount * WIN_X / 1000;
|
||
|
lockedInBets -= tossWinAmount;
|
||
|
|
||
|
bet.amount = 0;
|
||
|
emit Refund(ticketID, ethToTransfer, requester);
|
||
|
}
|
||
|
|
||
|
// Helper routine to process the payment.
|
||
|
function sendFunds(address paidUser, uint amount) private returns (bool){
|
||
|
bool success = paidUser.send(amount);
|
||
|
if (success) {
|
||
|
emit Payment(paidUser, amount);
|
||
|
} else {
|
||
|
emit FailedPayment(paidUser, amount);
|
||
|
}
|
||
|
return success;
|
||
|
}
|
||
|
// Payout ETH to whale when player loses
|
||
|
function loseWager(address player, uint amount, uint ticketID, bool maskRes, uint jackpotRes)
|
||
|
internal
|
||
|
{
|
||
|
emit Lose(player, amount, ticketID, maskRes, jackpotRes);
|
||
|
}
|
||
|
|
||
|
// bulk clean the storage.
|
||
|
function clearStorage(uint[] toCleanTicketIDs) external {
|
||
|
uint length = toCleanTicketIDs.length;
|
||
|
|
||
|
for (uint i = 0; i < length; i++) {
|
||
|
clearProcessedBet(toCleanTicketIDs[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Helper routine to move 'processed' bets into 'clean' state.
|
||
|
function clearProcessedBet(uint ticketID) private {
|
||
|
Bet storage bet = bets[ticketID];
|
||
|
|
||
|
// Do not overwrite active bets with zeros; additionally prevent cleanup of bets
|
||
|
// for which ticketID signatures may have not expired yet (see whitepaper for details).
|
||
|
if (bet.amount != 0 || block.number <= bet.blockNumber + BET_EXPIRATION_BLOCKS) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
bet.blockNumber = 0;
|
||
|
bet.betMask = false;
|
||
|
bet.player = address(0);
|
||
|
}
|
||
|
|
||
|
// A trap door for when someone sends tokens other than the intended ones so the overseers can decide where to send them.
|
||
|
function transferAnyERC20Token(address tokenAddress, address tokenOwner, uint tokens)
|
||
|
public
|
||
|
onlyOwner()
|
||
|
returns (bool success)
|
||
|
{
|
||
|
return ERC20Interface(tokenAddress).transfer(tokenOwner, tokens);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Define ERC20Interface.transfer, so PoCWHALE can transfer tokens accidently sent to it.
|
||
|
contract ERC20Interface
|
||
|
{
|
||
|
function transfer(address to, uint256 tokens) public returns (bool success);
|
||
|
}
|