Skip to content

Instantly share code, notes, and snippets.

@yuriy77k
Created September 24, 2018 11:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save yuriy77k/909d9541f30628d3a0e6c39af36a4cdf to your computer and use it in GitHub Desktop.
Save yuriy77k/909d9541f30628d3a0e6c39af36a4cdf to your computer and use it in GitHub Desktop.
FiftyFlip
pragma solidity ^0.4.24;
/* This is fiftyflip
a simple yet elegant game contract
that is connected to Proof of Community
contract(0x08f7039d36f99eedc3d8b02cbd19f854f7dddc4d).
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 = 7 ether;
uint constant BET_EXPIRATION_BLOCKS = 250;
// owner and PoC contract address
address public owner;
address public autoPlayBot;
address public secretSigner;
address public 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));
secretSigner = ecrecover(signatureHash, v, r, s);
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;
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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment