pragma solidity ^0.4.24;
// Zethr Token Bankroll interface
contract ZethrTokenBankroll{
// Game request token transfer to player
function gameRequestTokens(address target, uint tokens) public;
}
// Zether Main Bankroll interface
contract ZethrMainBankroll{
function gameGetTokenBankrollList() public view returns (address[7]);
}
// Zethr main contract interface
contract ZethrInterface{
function withdraw() public;
}
// Library for figuring out the "tier" (1-7) of a dividend rate
library ZethrTierLibrary{
uint constant internal magnitude = 2**64;
function getTier(uint divRate) internal pure returns (uint){
// Tier logic
// Returns the index of the UsedBankrollAddresses which should be used to call into to withdraw tokens
// We can divide by magnitude
// Remainder is removed so we only get the actual number we want
uint actualDiv = divRate;
if (actualDiv >= 30){
return 6;
} else if (actualDiv >= 25){
return 5;
} else if (actualDiv >= 20){
return 4;
} else if (actualDiv >= 15){
return 3;
} else if (actualDiv >= 10){
return 2;
} else if (actualDiv >= 5){
return 1;
} else if (actualDiv >= 2){
return 0;
} else{
// Impossible
revert();
}
}
}
// Contract that contains the functions to interact with the bankroll system
contract ZethrBankrollBridge{
// Must have an interface with the main Zethr token contract
ZethrInterface Zethr;
// Store the bankroll addresses
// address[0] is main bankroll
// address[1] is tier1: 2-5%
// address[2] is tier2: 5-10, etc
address[7] UsedBankrollAddresses;
// Mapping for easy checking
mapping(address => bool) ValidBankrollAddress;
// Set up the tokenbankroll stuff
function setupBankrollInterface(address ZethrMainBankrollAddress) internal {
// Get the bankroll addresses from the main bankroll
UsedBankrollAddresses = ZethrMainBankroll(ZethrMainBankrollAddress).gameGetTokenBankrollList();
for(uint i=0; i<7; i++){
ValidBankrollAddress[UsedBankrollAddresses[i]] = true;
}
}
// Require a function to be called from a *token* bankroll
modifier fromBankroll(){
require(ValidBankrollAddress[msg.sender], "msg.sender should be a valid bankroll");
_;
}
// Request a payment in tokens to a user FROM the appropriate tokenBankroll
// Figure out the right bankroll via divRate
function RequestBankrollPayment(address to, uint tokens, uint userDivRate) internal {
uint tier = ZethrTierLibrary.getTier(userDivRate);
address tokenBankrollAddress = UsedBankrollAddresses[tier];
ZethrTokenBankroll(tokenBankrollAddress).gameRequestTokens(to, tokens);
}
}
// Contract that contains functions to move divs to the main bankroll
contract ZethrShell is ZethrBankrollBridge{
// Dump ETH balance to main bankroll
function WithdrawToBankroll() public {
address(UsedBankrollAddresses[0]).transfer(address(this).balance);
}
// Dump divs and dump ETH into bankroll
function WithdrawAndTransferToBankroll() public {
Zethr.withdraw();
WithdrawToBankroll();
}
}
// Zethr game data setup
// Includes all necessary to run with Zethr
contract Zethroll is ZethrShell {
using SafeMath for uint;
// Makes sure that player profit can't exceed a maximum amount,
// that the bet size is valid, and the playerNumber is in range.
modifier betIsValid(uint _betSize, uint _playerNumber, uint divRate) {
require( calculateProfit(_betSize, _playerNumber) < getMaxProfit(divRate)
&& _betSize >= minBet
&& _playerNumber >= minNumber
&& _playerNumber <= maxNumber);
_;
}
// Requires game to be currently active
modifier gameIsActive {
require(gamePaused == false);
_;
}
// Requires msg.sender to be owner
modifier onlyOwner {
require(msg.sender == owner);
_;
}
// Constants
uint constant private MAX_INT = 2 ** 256 - 1;
uint constant public maxProfitDivisor = 1000000;
uint constant public maxNumber = 100;
uint constant public minNumber = 2;
uint constant public houseEdgeDivisor = 1000;
// Configurables
bool public gamePaused;
address public owner;
mapping (uint => uint) public contractBalance;
mapping (uint => uint) public maxProfit;
uint public houseEdge;
uint public maxProfitAsPercentOfHouse;
uint public minBet = 0;
// Trackers
uint public totalBets;
uint public totalZTHWagered;
// Events
// Logs bets + output to web3 for precise 'payout on win' field in UI
event LogBet(address sender, uint value, uint rollUnder);
// Outputs to web3 UI on bet result
// Status: 0=lose, 1=win, 2=win + failed send, 3=refund, 4=refund + failed send
event LogResult(address player, uint result, uint rollUnder, uint profit, uint tokensBetted, bool won);
// Logs owner transfers
event LogOwnerTransfer(address indexed SentToAddress, uint indexed AmountTransferred);
// Logs changes in maximum profit
event MaxProfitChanged(uint _oldMaxProfit, uint _newMaxProfit);
// Logs current contract balance
event CurrentContractBalance(uint _tokens);
constructor (address ZethrMainBankrollAddress) public {
setupBankrollInterface(ZethrMainBankrollAddress);
// Owner is deployer
owner = msg.sender;
// Init 990 = 99% (1% houseEdge)
houseEdge = 990;
// The maximum profit from each bet is 10% of the contract balance.
ownerSetMaxProfitAsPercentOfHouse(10000);
// Init min bet (1 ZTH)
ownerSetMinBet(1e18);
}
// Returns a random number using a specified block number
// Always use a FUTURE block number.
function maxRandom(uint blockn, address entropy) public view returns (uint256 randomNumber) {
return uint256(keccak256(
abi.encodePacked(
blockhash(blockn),
entropy)
));
}
// Random helper
function random(uint256 upper, uint256 blockn, address entropy) public view returns (uint256 randomNumber) {
return maxRandom(blockn, entropy) % upper;
}
// Calculate the maximum potential profit
function calculateProfit(uint _initBet, uint _roll)
private
view
returns (uint)
{
return ((((_initBet * (100 - (_roll.sub(1)))) / (_roll.sub(1)) + _initBet)) * houseEdge / houseEdgeDivisor) - _initBet;
}
// I present a struct which takes only 20k gas
struct playerRoll{
uint192 tokenValue; // Token value in uint
uint48 blockn; // Block number 48 bits
uint8 rollUnder; // Roll under 8 bits
uint8 divRate; // Divrate, 8 bits
}
// Mapping because a player can do one roll at a time
mapping(address => playerRoll) public playerRolls;
// The actual roll function
function _playerRollDice(uint _rollUnder, TKN _tkn, uint userDivRate) private
gameIsActive
betIsValid(_tkn.value, _rollUnder, userDivRate)
{
require(_tkn.value < ((2 ** 192) - 1)); // Smaller than the storage of 1 uint192;
require(block.number < ((2 ** 48) - 1)); // Current block number smaller than storage of 1 uint48
require(userDivRate < (2 ** 8 - 1)); // This should never throw
// Note that msg.sender is the Token Contract Address
// and "_from" is the sender of the tokens
playerRoll memory roll = playerRolls[_tkn.sender];
// Cannot bet twice in one block
require(block.number != roll.blockn);
// If there exists a roll, finish it
if (roll.blockn != 0) {
_finishBet(_tkn.sender);
}
// Set struct block number, token value, and rollUnder values
roll.blockn = uint48(block.number);
roll.tokenValue = uint192(_tkn.value);
roll.rollUnder = uint8(_rollUnder);
roll.divRate = uint8(userDivRate);
// Store the roll struct - 20k gas.
playerRolls[_tkn.sender] = roll;
// Provides accurate numbers for web3 and allows for manual refunds
emit LogBet(_tkn.sender, _tkn.value, _rollUnder);
// Increment total number of bets
totalBets += 1;
// Total wagered
totalZTHWagered += _tkn.value;
}
// Finished the current bet of a player, if they have one
function finishBet() public
gameIsActive
returns (uint)
{
return _finishBet(msg.sender);
}
/*
* Pay winner, update contract balance
* to calculate new max bet, and send reward.
*/
function _finishBet(address target) private returns (uint){
playerRoll memory roll = playerRolls[target];
require(roll.tokenValue > 0); // No re-entracy
require(roll.blockn != block.number);
// If the block is more than 255 blocks old, we can't get the result
// Also, if the result has already happened, fail as well
uint result;
if (block.number - roll.blockn > 255) {
result = 1000; // Cant win
} else {
// Grab the result - random based ONLY on a past block (future when submitted)
result = random(100, roll.blockn, target) + 1;
}
uint rollUnder = roll.rollUnder;
if (result < rollUnder) {
// Player has won!
// Safely map player profit
uint profit = calculateProfit(roll.tokenValue, rollUnder);
uint mProfit = getMaxProfit(roll.divRate);
if (profit > mProfit){
profit = mProfit;
}
// Safely reduce contract balance by player profit
subContractBalance(roll.divRate, profit);
emit LogResult(target, result, rollUnder, profit, roll.tokenValue, true);
// Update maximum profit
setMaxProfit(roll.divRate);
// Prevent re-entracy memes
playerRolls[target] = playerRoll(uint192(0), uint48(0), uint8(0), uint8(0));
// Transfer profit plus original bet
RequestBankrollPayment(target, profit + roll.tokenValue, roll.divRate);
return result;
} else {
/*
* Player has lost
* Update contract balance to calculate new max bet
*/
emit LogResult(target, result, rollUnder, profit, roll.tokenValue, false);
/*
* Safely adjust contractBalance
* SetMaxProfit
*/
addContractBalance(roll.divRate, roll.tokenValue);
playerRolls[target] = playerRoll(uint192(0), uint48(0), uint8(0), uint8(0));
// No need to actually delete player roll here since player ALWAYS loses
// Saves gas on next buy
// Update maximum profit
setMaxProfit(roll.divRate);
return result;
}
}
// TKN struct
struct TKN {address sender; uint value;}
// Token fallback to bet or deposit from bankroll
function execute(address _from, uint _value, uint userDivRate, bytes _data) public fromBankroll gameIsActive returns (bool) {
TKN memory _tkn;
_tkn.sender = _from;
_tkn.value = _value;
uint8 chosenNumber = uint8(_data[0]);
_playerRollDice(chosenNumber, _tkn, userDivRate);
return true;
}
// Sets max profit
function setMaxProfit(uint divRate) internal {
//emit CurrentContractBalance(contractBalance);
maxProfit[divRate] = (contractBalance[divRate] * maxProfitAsPercentOfHouse) / maxProfitDivisor;
}
// Gets max profit
function getMaxProfit(uint divRate) public view returns (uint){
return (contractBalance[divRate] * maxProfitAsPercentOfHouse) / maxProfitDivisor;
}
// Subtracts from the contract balance tracking var
function subContractBalance(uint divRate, uint sub) internal {
contractBalance[divRate] = contractBalance[divRate].sub(sub);
}
// Adds to the contract balance tracking var
function addContractBalance(uint divRate, uint add) internal {
contractBalance[divRate] = contractBalance[divRate].add(add);
}
// Only owner adjust contract balance variable (only used for max profit calc)
function ownerUpdateContractBalance(uint newContractBalance, uint divRate) public
onlyOwner
{
contractBalance[divRate] = newContractBalance;
}
// An EXTERNAL update of tokens should be handled here
// This is due to token allocation
// The game should handle internal updates itself (e.g. tokens are betted)
function bankrollExternalUpdateTokens(uint divRate, uint newBalance) public fromBankroll {
contractBalance[divRate] = newBalance;
setMaxProfit(divRate);
}
// Only owner address can set maxProfitAsPercentOfHouse
function ownerSetMaxProfitAsPercentOfHouse(uint newMaxProfitAsPercent) public
onlyOwner
{
// Restricts each bet to a maximum profit of 20% contractBalance
require(newMaxProfitAsPercent <= 200000);
maxProfitAsPercentOfHouse = newMaxProfitAsPercent;
setMaxProfit(2);
setMaxProfit(5);
setMaxProfit(10);
setMaxProfit(15);
setMaxProfit(20);
setMaxProfit(25);
setMaxProfit(33);
}
// Only owner address can set minBet
function ownerSetMinBet(uint newMinimumBet) public
onlyOwner
{
minBet = newMinimumBet;
}
// Only owner address can set emergency pause #1
function ownerPauseGame(bool newStatus) public
onlyOwner
{
gamePaused = newStatus;
}
// Only owner address can set owner address
function ownerChangeOwner(address newOwner) public
onlyOwner
{
owner = newOwner;
}
// Only owner address can selfdestruct - emergency
function ownerkill() public
onlyOwner
{
selfdestruct(owner);
}
}
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint a, uint b) internal pure returns (uint) {
if (a == 0) {
return 0;
}
uint c = a * b;
assert(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint a, uint b) internal pure returns (uint) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint a, uint b) internal pure returns (uint) {
assert(b <= a);
return a - b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint a, uint b) internal pure returns (uint) {
uint c = a + b;
assert(c >= a);
return c;
}
}
{
"compilationTarget": {
"Zethroll.sol": "Zethroll"
},
"evmVersion": "byzantium",
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":true,"inputs":[],"name":"totalZTHWagered","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"WithdrawAndTransferToBankroll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"maxProfitAsPercentOfHouse","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maxNumber","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"maxProfit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maxProfitDivisor","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"ownerChangeOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"minNumber","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newMaxProfitAsPercent","type":"uint256"}],"name":"ownerSetMaxProfitAsPercentOfHouse","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"blockn","type":"uint256"},{"name":"entropy","type":"address"}],"name":"maxRandom","outputs":[{"name":"randomNumber","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newContractBalance","type":"uint256"},{"name":"divRate","type":"uint256"}],"name":"ownerUpdateContractBalance","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newMinimumBet","type":"uint256"}],"name":"ownerSetMinBet","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newStatus","type":"bool"}],"name":"ownerPauseGame","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"WithdrawToBankroll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_value","type":"uint256"},{"name":"userDivRate","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"finishBet","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"minBet","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"divRate","type":"uint256"}],"name":"getMaxProfit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalBets","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"gamePaused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"playerRolls","outputs":[{"name":"tokenValue","type":"uint192"},{"name":"blockn","type":"uint48"},{"name":"rollUnder","type":"uint8"},{"name":"divRate","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"ownerkill","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"houseEdge","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"houseEdgeDivisor","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"divRate","type":"uint256"},{"name":"newBalance","type":"uint256"}],"name":"bankrollExternalUpdateTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"contractBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"upper","type":"uint256"},{"name":"blockn","type":"uint256"},{"name":"entropy","type":"address"}],"name":"random","outputs":[{"name":"randomNumber","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"ZethrMainBankrollAddress","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"rollUnder","type":"uint256"}],"name":"LogBet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"player","type":"address"},{"indexed":false,"name":"result","type":"uint256"},{"indexed":false,"name":"rollUnder","type":"uint256"},{"indexed":false,"name":"profit","type":"uint256"},{"indexed":false,"name":"tokensBetted","type":"uint256"},{"indexed":false,"name":"won","type":"bool"}],"name":"LogResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"SentToAddress","type":"address"},{"indexed":true,"name":"AmountTransferred","type":"uint256"}],"name":"LogOwnerTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_oldMaxProfit","type":"uint256"},{"indexed":false,"name":"_newMaxProfit","type":"uint256"}],"name":"MaxProfitChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_tokens","type":"uint256"}],"name":"CurrentContractBalance","type":"event"}]