pragma solidity 0.4.25;
/*
* 0xBtcnnRoll.
*/
contract BTCNNInterface {
function getFrontEndTokenBalanceOf(address who) public view returns(uint);
function transfer(address _to, uint _value) public returns(bool);
function approve(address spender, uint tokens) public returns(bool);
}
contract BtcnnRoll {
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) {
require(calculateProfit(_betSize, _playerNumber) < maxProfit &&
_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 = 99;
uint constant public minNumber = 2;
uint constant public houseEdgeDivisor = 1000;
// Configurables
bool public gamePaused;
address public owner;
address public BTCNNBankroll;
address public BTCNNTKNADDR;
BTCNNInterface public BTCNNTKN;
uint public contractBalance;
uint public houseEdge;
uint public maxProfit;
uint public maxProfitAsPercentOfHouse;
uint public minBet = 0;
// Trackers
uint public totalBets;
uint public totalBTCNNWagered;
// 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 indexed 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 btcnntknaddr, address btcnnbankrolladdr) public {
// Owner is deployer
owner = msg.sender;
// Initialize the BTCNN contract and bankroll interfaces
BTCNNTKN = BTCNNInterface(btcnntknaddr);
BTCNNTKNADDR = btcnntknaddr;
// Set the bankroll
BTCNNBankroll = btcnnbankrolladdr;
// 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 BTCNN)
ownerSetMinBet(1e18);
// Allow 'unlimited' token transfer by the bankroll
BTCNNTKN.approve(btcnnbankrolladdr, MAX_INT);
}
function () public {
revert();
}
// 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) internal 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 {
uint200 tokenValue; // Token value in uint
uint48 blockn; // Block number 48 bits
uint8 rollUnder; // Roll under 8 bits
}
// Mapping because a player can do one roll at a time
mapping(address => playerRoll) public playerRolls;
function _playerRollDice(uint _rollUnder, TKN _tkn) private
gameIsActive
betIsValid(_tkn.value, _rollUnder) {
require(_tkn.value < ((2 ** 200) - 1)); // Smaller than the storage of 1 uint200;
require(block.number < ((2 ** 48) - 1)); // Current block number smaller than storage of 1 uint48
// Note that msg.sender is the Token Contract Address
// and "_from" is the sender of the tokens
// Check that this is a BTCNN token transfer
require(_btcnnToken(msg.sender));
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(false, _tkn.sender);
}
// Set struct block number, token value, and rollUnder values
roll.blockn = uint48(block.number);
roll.tokenValue = uint200(_tkn.value);
roll.rollUnder = uint8(_rollUnder);
// 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
totalBTCNNWagered += _tkn.value;
}
// Finished the current bet of a player, if they have one
function finishBet() public
gameIsActive
returns(uint) {
return _finishBet(true, msg.sender);
}
/*
* Pay winner, update contract balance
* to calculate new max bet, and send reward.
*/
function _finishBet(bool delete_it, 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(99, roll.blockn, target) + 1;
}
uint rollUnder = roll.rollUnder;
if (result < rollUnder) {
// Player has won!
// Safely map player profit
uint profit = calculateProfit(roll.tokenValue, rollUnder);
if (profit > maxProfit) {
profit = maxProfit;
}
// Safely reduce contract balance by player profit
contractBalance = contractBalance.sub(profit);
emit LogResult(target, result, rollUnder, profit, roll.tokenValue, true);
// Update maximum profit
setMaxProfit();
// Prevent re-entracy memes
playerRolls[target] = playerRoll(uint200(0), uint48(0), uint8(0));
// Transfer profit plus original bet
BTCNNTKN.transfer(target, profit + roll.tokenValue);
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
*/
contractBalance = contractBalance.add(roll.tokenValue);
playerRolls[target] = playerRoll(uint200(0), uint48(0), uint8(0));
// No need to actually delete player roll here since player ALWAYS loses
// Saves gas on next buy
// Update maximum profit
setMaxProfit();
return result;
}
}
// TKN struct
struct TKN {
address sender;
uint value;
}
// Token fallback to bet or deposit from bankroll
function tokenFallback(address _from, uint _value, bytes _data) public returns(bool) {
require(msg.sender == BTCNNTKNADDR);
if (_from == BTCNNBankroll) {
// Update the contract balance
contractBalance = contractBalance.add(_value);
// Update the maximum profit
uint oldMaxProfit = maxProfit;
setMaxProfit();
emit MaxProfitChanged(oldMaxProfit, maxProfit);
return true;
} else {
TKN memory _tkn;
_tkn.sender = _from;
_tkn.value = _value;
uint8 chosenNumber = uint8(_data[0]);
_playerRollDice(chosenNumber, _tkn);
}
return true;
}
/*
* Sets max profit
*/
function setMaxProfit() internal {
emit CurrentContractBalance(contractBalance);
maxProfit = (contractBalance * maxProfitAsPercentOfHouse) / maxProfitDivisor;
}
// Only owner adjust contract balance variable (only used for max profit calc)
function ownerUpdateContractBalance(uint newContractBalance) public
onlyOwner {
contractBalance = newContractBalance;
}
// 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();
}
// Only owner address can set minBet
function ownerSetMinBet(uint newMinimumBet) public
onlyOwner {
minBet = newMinimumBet;
}
// Only owner address can transfer BTCNN
function ownerTransferBTCNN(address sendTo, uint amount) public
onlyOwner {
// Safely update contract balance when sending out funds
contractBalance = contractBalance.sub(amount);
// update max profit
setMaxProfit();
require(BTCNNTKN.transfer(sendTo, amount));
emit LogOwnerTransfer(sendTo, amount);
}
// Only owner address can set emergency pause #1
function ownerPauseGame(bool newStatus) public
onlyOwner {
gamePaused = newStatus;
}
// Only owner address can set bankroll address
function ownerSetBankroll(address newBankroll) public
onlyOwner {
BTCNNTKN.approve(BTCNNBankroll, 0);
BTCNNBankroll = newBankroll;
BTCNNTKN.approve(newBankroll, MAX_INT);
}
// 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 {
BTCNNTKN.transfer(owner, contractBalance);
selfdestruct(owner);
}
function dumpdivs() public {
BTCNNTKN.transfer(BTCNNBankroll, BTCNNTKN.getFrontEndTokenBalanceOf(this));
}
function _btcnnToken(address _tokenContract) private view returns(bool) {
return _tokenContract == BTCNNTKNADDR;
// Is this the BTCNN token contract?
}
}
/**
* @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": {
"BtcnnRoll.sol": "BtcnnRoll"
},
"evmVersion": "byzantium",
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":false,"inputs":[],"name":"dumpdivs","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"sendTo","type":"address"},{"name":"amount","type":"uint256"}],"name":"ownerTransferBTCNN","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":"totalBTCNNWagered","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BTCNNBankroll","outputs":[{"name":"","type":"address"}],"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":"BTCNNTKNADDR","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newContractBalance","type":"uint256"}],"name":"ownerUpdateContractBalance","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"BTCNNTKN","outputs":[{"name":"","type":"address"}],"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":"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":"newBankroll","type":"address"}],"name":"ownerSetBankroll","outputs":[],"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":"contractBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","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":"maxProfit","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":false,"inputs":[{"name":"_from","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"tokenFallback","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","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":"uint200"},{"name":"blockn","type":"uint48"},{"name":"rollUnder","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"},{"inputs":[{"name":"btcnntknaddr","type":"address"},{"name":"btcnnbankrolladdr","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":false,"stateMutability":"nonpayable","type":"fallback"},{"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":true,"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"}]