Transaction Hash:
Block:
7736771 at May-11-2019 03:22:41 AM +UTC
Transaction Fee:
0.000178275 ETH
$0.37
Gas Used:
35,655 Gas / 5 Gwei
Emitted Events:
| 82 |
XetherGames.Payment( beneficiary=0xcedaba98e13c3fc9bb1461a8cfe8ac58b256b545, commit=76876487329150922669888381973074147492924045207639830444778599539767693208954, amount=88290000000000000, paymentType=payment )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x0C073C92...19e864d18 | (Xether.io: Games) | 566.589721745790152649 Eth | 566.501431745790152649 Eth | 0.08829 | |
|
0x829BD824...93333A830
Miner
| (F2Pool Old) | 5,686.730840813679979226 Eth | 5,686.731019088679979226 Eth | 0.000178275 | |
| 0xceDabA98...8B256b545 | 0.697706137717391315 Eth | 0.785996137717391315 Eth | 0.08829 | ||
| 0xfE1B5585...08E659538 |
2.931898531491416866 Eth
Nonce: 43411
|
2.931720256491416866 Eth
Nonce: 43412
| 0.000178275 |
Execution Trace
XetherGames.settleBet( reveal=63042540440339637812170657486462058143214920151546707814970290870696799165516 )
- ETH 0.08829
0xcedaba98e13c3fc9bb1461a8cfe8ac58b256b545.CALL( )
settleBet[XetherGames (ln:602)]
settleBetPlinko[XetherGames (ln:612)]_plinkoGetDiceWin[XetherGames (ln:686)]_getPlinkoCellRatio[XetherGames (ln:714)]getBonusProgrammLevel[XetherGames (ln:724)]
checkJackPotWin[XetherGames (ln:690)]JackpotPayment[XetherGames (ln:692)]sendFunds[XetherGames (ln:695)]send[XetherGames (ln:976)]Payment[XetherGames (ln:977)]FailedPayment[XetherGames (ln:979)]
settleBetSlots[XetherGames (ln:614)]_slotsWinAmount[XetherGames (ln:753)]_slotsCheckWin[XetherGames (ln:775)]charAt[XetherGames (ln:803)]getLastN[XetherGames (ln:804)]
getBonusProgrammLevel[XetherGames (ln:781)]
checkJackPotWin[XetherGames (ln:757)]JackpotPayment[XetherGames (ln:759)]sendFunds[XetherGames (ln:762)]send[XetherGames (ln:976)]Payment[XetherGames (ln:977)]FailedPayment[XetherGames (ln:979)]
settleBetCommon[XetherGames (ln:616)]getDiceWinAmount[XetherGames (ln:650)]getBonusProgrammLevel[XetherGames (ln:919)]
getDiceWinAmount[XetherGames (ln:655)]getBonusProgrammLevel[XetherGames (ln:919)]
checkJackPotWin[XetherGames (ln:662)]JackpotPayment[XetherGames (ln:664)]sendFunds[XetherGames (ln:667)]send[XetherGames (ln:976)]Payment[XetherGames (ln:977)]FailedPayment[XetherGames (ln:979)]
pragma solidity 0.5.6;
// * xether.io - is a gambling ecosystem, which makes a difference by caring about its users.
// It’s our passion for perfection, as well as finding and creating neat solutions,
// that keeps us driven towards our goals.
//
// * Uses hybrid commit-reveal + block hash random number generation that is immune
// to tampering by players, house and miners. Apart from being fully transparent,
// this also allows arbitrarily high bets.
interface xEtherTokensContractInterface {
function ecosystemDividends() external payable;
}
contract XetherGames {
uint256 constant HOUSE_EDGE_MINIMUM_AMOUNT = 0.0003 ether;
uint256 constant MIN_JACKPOT_BET = 0.1 ether;
uint16 constant JACKPOT_MODULO = 1000;
uint256 constant JACKPOT_FEE = 0.001 ether;
uint256 constant MIN_BET = 0.01 ether;
uint256 constant MAX_AMOUNT = 300000 ether;
uint8 constant MAX_MODULO = 100;
uint8 constant MAX_MASK_MODULO = 40;
uint256 constant MAX_BET_MASK = 2 ** uint256(MAX_MASK_MODULO);
uint8 constant BET_EXPIRATION_BLOCKS = 250;
uint256 public DIVIDENDS_LIMIT = 1 ether;
uint16 constant PERCENTAGES_BASE = 1000;
address constant DUMMY_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
uint16 public luckyNumber = 777;
uint8 public DIVIDENDS_PERCENT = 10; // 1% example: 15 will be 1.5%
uint8 public ADVERTISE_PERCENT = 0; // 0%
uint8 public HOUSE_EDGE_PERCENT = 10; // 1%
uint8 constant ROULETTE_ID = 37;
uint8 constant ROULETTE_STAKES_LIMIT = 36;
uint8 public rouletteSkipComission = 1;
uint256 public rouletteTableLimit = 1.8 ether;
uint8 public ROULETTE_PERCENT = 10;
uint8 constant PLINKO_BYTES = 16;
uint256 constant PLINKO_ID = 2 ** uint256(PLINKO_BYTES);
uint16[17] PLINKO1Ratios = [1000,800,600,300,200,130,100,80,50,80,100,130,200,300,600,800,1000];
uint16[17] PLINKO2Ratios = [2000,700,500,300,200,110,100,60,100,60,100,110,200,300,500,700,2000];
uint16[17] PLINKO3Ratios = [5000,800,300,200,140,120,110,100,40,100,110,120,140,200,300,800,5000];
uint8 public plinkoSkipComission = 2;
uint8 public PLINKO_PERCENT = HOUSE_EDGE_PERCENT;
uint16 constant SLOTS_ID = 999;
uint8 constant SLOTS_COUNT = 5;
uint16[] SLOTSWinsRatios = [0, 50, 120, 200, 1500];
uint32[] SLOTSWildRatios = [0, 110, 250, 400, 3000, 10000];
xEtherTokensContractInterface public xEtherTokensContract;
address payable public owner;
address payable private nextOwner;
uint256 public totalDividends = 0;
uint256 public totalAdvertise = 0;
uint256 public maxProfit = 5 ether;
uint256 public maxProfitPlinko = 10 ether;
uint256 public maxProfitRoulette = 3.6 ether;
address public secretSigner;
address public moderator;
address public croupier;
uint128 public jackpotSize;
uint128 public lockedInBets;
struct Bet {
uint256 amount;
uint128 locked;
uint32 modulo;
uint8 rollUnder;
uint40 placeBlockNumber;
uint256 clientSeed;
uint40 mask;
address payable gambler;
}
struct BetRoulette {
uint256 totalBetAmount;
uint128 locked;
mapping (uint8 => uint256) amount;
mapping (uint8 => uint8) rollUnder;
uint40 placeBlockNumber;
uint256 clientSeed;
mapping (uint8 => uint40) mask;
address payable gambler;
uint8 betsCount;
}
mapping (uint => Bet) bets;
mapping (uint => BetRoulette) betsRoulette;
mapping (address => uint256) public bonusProgrammAccumulated;
event FailedPayment(address beneficiary, uint commit, uint amount, string paymentType);
event Payment(address beneficiary, uint commit, uint amount, string paymentType);
event JackpotPayment(address indexed beneficiary, uint commit, uint amount);
event PayDividendsSuccess(uint time, uint amount);
event PayDividendsFailed(uint time, uint amount);
event Commit(uint commit, uint clietSeed, uint amount);
constructor () public {
owner = msg.sender;
secretSigner = DUMMY_ADDRESS;
croupier = DUMMY_ADDRESS;
}
modifier onlyOwner {
require (msg.sender == owner, "OnlyOwner methods called by non-owner.");
_;
}
modifier onlyModeration {
require (msg.sender == owner || msg.sender == moderator, "Moderation methods called by non-moderator.");
_;
}
modifier onlyCroupier {
require (msg.sender == croupier, "OnlyCroupier methods called by non-croupier.");
_;
}
function approveNextOwner(address payable _nextOwner) external onlyOwner {
require (_nextOwner != owner, "Cannot approve current owner.");
nextOwner = _nextOwner;
}
function acceptNextOwner() external {
require (msg.sender == nextOwner, "Can only accept preapproved new owner.");
owner = nextOwner;
}
function () external payable {
}
function setNewPercents(
uint8 newHouseEdgePercent,
uint8 newDividendsPercent,
uint8 newAdvertPercent
) external onlyOwner {
// We guarantee that dividends will be minimum 0.5%
require(newDividendsPercent >= 5);
// Total percentages not greater then 3%
require(newHouseEdgePercent + newDividendsPercent + newAdvertPercent <= 30);
HOUSE_EDGE_PERCENT = newHouseEdgePercent;
ADVERTISE_PERCENT = newAdvertPercent;
DIVIDENDS_PERCENT = newDividendsPercent;
}
function setNewRoulettePercents(uint8 newRoulettePercent) external onlyModeration {
require(0 <= newRoulettePercent && newRoulettePercent <= 10);
ROULETTE_PERCENT = newRoulettePercent;
}
function setNewPlinkoPercents(uint8 newPlinkoPercent) external onlyModeration {
require(0 <= newPlinkoPercent && newPlinkoPercent <= 10);
PLINKO_PERCENT = newPlinkoPercent;
}
function setXEtherContract(address payable xEtherContract) external onlyOwner{
xEtherTokensContract = xEtherTokensContractInterface(xEtherContract);
}
function setAddresses(address newCroupier, address newSecretSigner, address newModerator) external onlyOwner {
secretSigner = newSecretSigner;
croupier = newCroupier;
moderator = newModerator;
}
function changeDividendsLimit(uint _newDividendsLimit) public onlyModeration {
DIVIDENDS_LIMIT = _newDividendsLimit;
}
function setMaxProfit(uint _maxProfit) public onlyModeration {
require (_maxProfit < MAX_AMOUNT, "maxProfit cant be great then top limit.");
maxProfit = _maxProfit;
}
function setMaxProfitPlinko(uint _maxProfitPlinko) public onlyModeration {
require (_maxProfitPlinko < MAX_AMOUNT, "maxProfitPlinko cant be great then top limit.");
maxProfitPlinko = _maxProfitPlinko;
}
function setMaxProfitRoulette(uint _maxProfitRoulette) public onlyModeration {
require (_maxProfitRoulette < MAX_AMOUNT, "maxProfitRoulette cant be great then top limit.");
maxProfitRoulette = _maxProfitRoulette;
}
function setRouletteTableLimit(uint _newRouletteTableLimit) public onlyModeration {
require (_newRouletteTableLimit < MAX_AMOUNT, "roultteTableLimit cant be great then top limit.");
rouletteTableLimit = _newRouletteTableLimit;
}
function setComissionState(uint8 _newRouletteState, uint8 _newPlinkoState) public onlyModeration {
rouletteSkipComission = _newRouletteState;
plinkoSkipComission = _newPlinkoState;
}
function releaseLockedInBetAmount() external onlyModeration {
lockedInBets = 0;
}
function increaseJackpot(uint increaseAmount) external onlyModeration {
require (increaseAmount <= address(this).balance, "Increase amount larger than balance.");
require (jackpotSize + lockedInBets + increaseAmount <= address(this).balance, "Not enough funds.");
jackpotSize += uint128(increaseAmount);
}
function withdrawFunds(address payable beneficiary, uint withdrawAmount) external onlyOwner {
require (withdrawAmount <= address(this).balance, "Increase amount larger than balance.");
require (jackpotSize + lockedInBets + withdrawAmount <= address(this).balance, "Not enough funds.");
sendFunds(beneficiary, withdrawAmount, withdrawAmount, 0, 'withdraw');
}
function withdrawAdvertiseFunds(address payable beneficiary, uint withdrawAmount) external onlyOwner {
require (withdrawAmount <= totalAdvertise, "Increase amount larger than balance.");
totalAdvertise -= withdrawAmount;
sendFunds(beneficiary, withdrawAmount, withdrawAmount, 0, 'withdraw');
}
function getBonusProgrammLevel(address gambler) public view returns (uint8 discount) {
uint accumulated = bonusProgrammAccumulated[gambler];
discount = 0;
if (accumulated >= 20 ether && accumulated < 100 ether) {
discount = 1;
} else if (accumulated >= 100 ether && accumulated < 500 ether) {
discount = 2;
} else if (accumulated >= 500 ether && accumulated < 1000 ether) {
discount = 3;
} else if (accumulated >= 1000 ether && accumulated < 5000 ether) {
discount = 4;
} else if (accumulated >= 5000 ether) {
discount = 5;
}
}
function kill() external onlyOwner {
require (lockedInBets == 0, "All bets should be processed (settled or refunded) before self-destruct.");
selfdestruct(owner);
}
function sendDividends() public payable {
if (address(xEtherTokensContract) != address(0)) {
uint tmpDividends = totalDividends;
xEtherTokensContract.ecosystemDividends.value(tmpDividends)();
totalDividends = 0;
emit PayDividendsSuccess(now, tmpDividends);
}
}
function placeBet(
uint betMask,
uint32 modulo,
uint commitLastBlock,
uint commit, uint256 clientSeed,
bytes32 r, bytes32 s
) external payable {
Bet storage bet = bets[commit];
require (bet.gambler == address(0), "Bet should be in a 'clean' state.");
uint amount = msg.value;
if (modulo > MAX_MODULO) {
require (modulo == PLINKO_ID || modulo == SLOTS_ID, "Modulo should be within range.");
} else {
require (modulo > 1 && modulo <= MAX_MODULO, "Modulo should be within range.");
}
require (amount >= MIN_BET && amount <= MAX_AMOUNT, "Amount should be within range.");
require (betMask > 0 && betMask < MAX_BET_MASK, "Mask should be within range.");
require (block.number <= commitLastBlock, "Commit has expired.");
require (
secretSigner == ecrecover(keccak256(abi.encodePacked(uint40(commitLastBlock), commit)), 27, r, s) ||
secretSigner == ecrecover(keccak256(abi.encodePacked(uint40(commitLastBlock), commit)), 28, r, s),
"ECDSA signature is not valid."
);
if (totalDividends >= DIVIDENDS_LIMIT) {
sendDividends();
}
uint rollUnder;
uint mask;
(mask, rollUnder, bet.locked) = prepareBet(betMask, modulo, amount, commit, clientSeed, msg.sender);
bet.amount = amount;
bet.modulo = uint32(modulo);
bet.rollUnder = uint8(rollUnder);
bet.placeBlockNumber = uint40(block.number);
bet.mask = uint40(mask);
bet.clientSeed = clientSeed;
bet.gambler = msg.sender;
}
function placeBetRoulette(
uint[] calldata betMask,
uint[] calldata betAmount,
uint commitLastBlock,
uint commit, uint256 clientSeed,
bytes32 r, bytes32 s
) external payable {
BetRoulette storage betRoulette = betsRoulette[commit];
require(msg.value <= rouletteTableLimit, "Bets sum must be LTE table limit");
betRoulette.betsCount = uint8(betMask.length);
require (betRoulette.gambler == address(0), "Bet should be in a 'clean' state.");
require (block.number <= commitLastBlock, "Commit has expired.");
require (
secretSigner == ecrecover(keccak256(abi.encodePacked(uint40(commitLastBlock), commit)), 27, r, s) ||
secretSigner == ecrecover(keccak256(abi.encodePacked(uint40(commitLastBlock), commit)), 28, r, s),
"ECDSA signature is not valid."
);
if (totalDividends >= DIVIDENDS_LIMIT) {
sendDividends();
}
(betRoulette.betsCount, betRoulette.locked) = placeBetRouletteProcess(commit, betMask, betAmount);
lockedInBets += betRoulette.locked;
require (lockedInBets <= address(this).balance, "Cannot afford to lose this bet.");
if (rouletteSkipComission != 1) {
bonusProgrammAccumulated[msg.sender] += msg.value;
}
betRoulette.totalBetAmount = msg.value;
betRoulette.placeBlockNumber = uint40(block.number);
betRoulette.clientSeed = clientSeed;
betRoulette.gambler = msg.sender;
jackpotSize += msg.value >= MIN_JACKPOT_BET ? uint128(JACKPOT_FEE) : 0;
emit Commit(commit, clientSeed, msg.value);
}
function placeBetRouletteProcess (
uint commit, uint[] memory betMask, uint[] memory betAmount
) internal returns (uint8 betsCount, uint128 locked) {
BetRoulette storage betRoulette = betsRoulette[commit];
betsCount = 0;
uint totalBetAmount = 0;
uint8 addBets = betRoulette.betsCount;
uint8 tmpBetCount = betRoulette.betsCount - 1;
uint128 curLocked = 0;
uint128 tmpLocked = 0;
bool numIsAlredyLocked = false;
uint8 bonuses = getBonusProgrammLevel(betRoulette.gambler);
while (0 <= tmpBetCount) {
require (betMask[tmpBetCount] > 0 && betMask[tmpBetCount] < MAX_BET_MASK, "Mask should be within range.");
// Check track sectors bets
if (betMask[tmpBetCount] == 38721851401) { // Jeu 0
require (betAmount[tmpBetCount] >= MIN_BET * 4 && betAmount[tmpBetCount] <= MAX_AMOUNT, "Amount should be within range.");
totalBetAmount += betAmount[tmpBetCount];
require (totalBetAmount <= msg.value, "Total bets amount should be LTE amount");
// 12/15
(betRoulette.mask[tmpBetCount], betRoulette.rollUnder[tmpBetCount], curLocked) = prepareBetRoulette(36864, betAmount[tmpBetCount] / 4, bonuses);
betRoulette.amount[tmpBetCount] = betAmount[tmpBetCount] / 4;
// 35/32
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(38654705664, betAmount[tmpBetCount] / 4, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 4;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
// 3/0
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(9, betAmount[tmpBetCount] / 4, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 4;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
// 26
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(67108864, betAmount[tmpBetCount] / 4, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 4;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
locked += curLocked;
addBets += 1;
betsCount += 4;
} else if (betMask[tmpBetCount] == 39567790237) { // Voisins mask
require (betAmount[tmpBetCount] >= MIN_BET * 9 && betAmount[tmpBetCount] <= MAX_AMOUNT, "Amount should be within range.");
totalBetAmount += betAmount[tmpBetCount];
require (totalBetAmount <= msg.value, "Total bets amount should be LTE amount");
// 4/7
(betRoulette.mask[tmpBetCount], betRoulette.rollUnder[tmpBetCount], curLocked) = prepareBetRoulette(
144, betAmount[tmpBetCount] / 9, bonuses);
betRoulette.amount[tmpBetCount] = betAmount[tmpBetCount] / 9;
// 12/15
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
36864, betAmount[tmpBetCount] / 9, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 9;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
// 18/21
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
2359296, betAmount[tmpBetCount] / 9, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 9;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
// 19/22
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
4718592, betAmount[tmpBetCount] / 9, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 9;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
// 35/32
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
38654705664, betAmount[tmpBetCount] / 9, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 9;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
// 25/26/28/29 (x2)
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
905969664, betAmount[tmpBetCount] * 2 / 9, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] * 2 / 9;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
// 0/2/3 (x2)
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
13, betAmount[tmpBetCount] * 2 / 9, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] * 2 / 9;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
locked += curLocked;
addBets += 1;
betsCount += 7;
} else if (betMask[tmpBetCount] == 19328549442) { // Orphelins mask
require (betAmount[tmpBetCount] >= MIN_BET * 5 && betAmount[tmpBetCount] <= MAX_AMOUNT, "Amount should be within range.");
totalBetAmount += betAmount[tmpBetCount];
require (totalBetAmount <= msg.value, "Total bets amount should be LTE amount");
// 14/17
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], curLocked) = prepareBetRoulette(
147456, betAmount[tmpBetCount] / 5, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 5;
// 17/20
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
1179648, betAmount[tmpBetCount] / 5, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 5;
curLocked += tmpLocked;
// 6/9
(betRoulette.mask[tmpBetCount], betRoulette.rollUnder[tmpBetCount], tmpLocked) = prepareBetRoulette(
576, betAmount[tmpBetCount] / 5, bonuses);
betRoulette.amount[tmpBetCount] = betAmount[tmpBetCount] / 5;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
// 31/34
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
19327352832, betAmount[tmpBetCount] / 5, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 5;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
// 1
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
2, betAmount[tmpBetCount] / 5, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 5;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
locked += curLocked;
addBets += 1;
betsCount += 5;
} else if (betMask[tmpBetCount] == 78542613792) { // Tier mask
require (betAmount[tmpBetCount] >= MIN_BET * 6 && betAmount[tmpBetCount] <= MAX_AMOUNT, "Amount should be within range.");
totalBetAmount += betAmount[tmpBetCount];
require (totalBetAmount <= msg.value, "Total bets amount should be LTE amount");
// 5/8
(betRoulette.mask[tmpBetCount], betRoulette.rollUnder[tmpBetCount], curLocked) = prepareBetRoulette(
288, betAmount[tmpBetCount] / 6, bonuses);
betRoulette.amount[tmpBetCount] = betAmount[tmpBetCount] / 6;
// 10/11
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
3072, betAmount[tmpBetCount] / 6, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 6;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
// 13/16
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
73728, betAmount[tmpBetCount] / 6, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 6;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
// 23/24
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
25165824, betAmount[tmpBetCount] / 6, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 6;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
// 27/30
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
1207959552, betAmount[tmpBetCount] / 6, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 6;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
// 33/36
addBets += 1;
(betRoulette.mask[addBets], betRoulette.rollUnder[addBets], tmpLocked) = prepareBetRoulette(
77309411328, betAmount[tmpBetCount] / 6, bonuses);
betRoulette.amount[addBets] = betAmount[tmpBetCount] / 6;
curLocked = (tmpLocked > curLocked) ? tmpLocked : curLocked;
locked += curLocked;
addBets += 1;
betsCount += 6;
} else {
require (betAmount[tmpBetCount] >= MIN_BET && betAmount[tmpBetCount] <= MAX_AMOUNT, "Amount should be within range.");
totalBetAmount += betAmount[tmpBetCount];
require (totalBetAmount <= msg.value, "Total bets amount should be LTE amount");
(betRoulette.mask[tmpBetCount], betRoulette.rollUnder[tmpBetCount], tmpLocked) = prepareBetRoulette(
betMask[tmpBetCount], betAmount[tmpBetCount], bonuses);
if (uint8(((betRoulette.mask[tmpBetCount] * POPCNT_MULT) & POPCNT_MASK) % POPCNT_MODULO) != 1) {
locked += tmpLocked;
} else {
if (!numIsAlredyLocked) {
numIsAlredyLocked = true;
locked += tmpLocked;
}
}
betRoulette.amount[tmpBetCount] = betAmount[tmpBetCount];
betsCount += 1;
}
if (tmpBetCount == 0) break;
tmpBetCount -= 1;
}
}
function prepareBet(uint betMask, uint32 modulo, uint amount, uint commit, uint clientSeed, address gambler) private returns (uint mask, uint8 rollUnder, uint128 possibleWinAmount) {
if (modulo <= MAX_MASK_MODULO) {
rollUnder = uint8(((betMask * POPCNT_MULT) & POPCNT_MASK) % POPCNT_MODULO);
mask = betMask;
} else {
require (betMask > 0 && betMask <= modulo, "High modulo range, betMask larger than modulo.");
rollUnder = uint8(betMask);
}
uint jackpotFee;
(possibleWinAmount, jackpotFee) = getDiceWinAmount(amount, modulo, rollUnder, gambler, true);
require (possibleWinAmount <= amount + maxProfitPlinko, "maxProfitPlinko limit violation.");
bonusProgrammAccumulated[gambler] += amount;
lockedInBets += uint128(possibleWinAmount);
jackpotSize += uint128(jackpotFee);
require (jackpotSize + lockedInBets <= address(this).balance, "Cannot afford to lose this bet.");
emit Commit(commit, clientSeed, amount);
}
function prepareBetRoulette(uint betMask, uint amount, uint8 bonuses) private returns (uint40 retMask, uint8 retRollUnder, uint128 possibleWinAmount) {
uint8 rollUnder = uint8(((betMask * POPCNT_MULT) & POPCNT_MASK) % POPCNT_MODULO);
uint mask = betMask;
possibleWinAmount = getRouletteWinAmount(amount, 36, rollUnder, bonuses, true);
require (possibleWinAmount <= amount + maxProfitRoulette, "maxProfitRoulette limit violation.");
retMask = uint40(mask);
retRollUnder = rollUnder;
}
function settleBet(uint reveal) external onlyCroupier {
uint commit = uint(keccak256(abi.encodePacked(reveal)));
Bet storage bet = bets[commit];
uint placeBlockNumber = bet.placeBlockNumber;
require (block.number > placeBlockNumber, "settleBet in the same block as placeBet, or before.");
require (block.number <= placeBlockNumber + BET_EXPIRATION_BLOCKS, "Can't be queried by EVM.");
if (bet.modulo == PLINKO_ID) {
settleBetPlinko(bet, reveal);
} else if (bet.modulo == SLOTS_ID) {
settleBetSlots(bet, reveal);
} else {
settleBetCommon(bet, reveal);
}
}
function settleBetRoulette(uint reveal) external onlyCroupier {
uint commit = uint(keccak256(abi.encodePacked(reveal)));
BetRoulette storage betRoulette = betsRoulette[commit];
uint placeBlockNumber = betRoulette.placeBlockNumber;
require (betRoulette.totalBetAmount > 0, "Bet already processed");
require (block.number > placeBlockNumber, "settleBet in the same block as placeBet, or before.");
require (block.number <= placeBlockNumber + BET_EXPIRATION_BLOCKS, "Can't be queried by EVM.");
settleBetRoulette(betRoulette, reveal);
}
// Common bets
function settleBetCommon(Bet storage bet, uint reveal) private {
uint amount = bet.amount;
uint8 rollUnder = bet.rollUnder;
require (amount != 0, "Bet should be in an 'active' state");
bet.amount = 0;
bytes32 entropy = keccak256(abi.encodePacked(reveal, bet.clientSeed));
uint dice = uint(entropy) % bet.modulo;
uint diceWinAmount;
uint _jackpotFee;
uint diceWin;
if (bet.modulo <= MAX_MASK_MODULO) {
if ((2 ** dice) & bet.mask != 0) {
(diceWinAmount, _jackpotFee) = getDiceWinAmount(amount, bet.modulo, rollUnder, bet.gambler, false);
diceWin = diceWinAmount;
}
} else {
if (dice < rollUnder) {
(diceWinAmount, _jackpotFee) = getDiceWinAmount(amount, bet.modulo, rollUnder, bet.gambler, false);
diceWin = diceWinAmount;
}
}
lockedInBets -= uint128(bet.locked);
uint jackpotWin = checkJackPotWin(entropy, amount, bet.modulo);
if (jackpotWin > 0) {
emit JackpotPayment(bet.gambler, uint(keccak256(abi.encodePacked(reveal))), jackpotWin);
}
sendFunds(
bet.gambler,
diceWin + jackpotWin == 0 ? 1 wei : diceWin + jackpotWin,
diceWin,
uint(keccak256(abi.encodePacked(reveal))),
'payment'
);
}
// Plinko
function settleBetPlinko(Bet storage bet, uint reveal) private {
uint amount = bet.amount;
uint rollUnder = bet.rollUnder;
require (amount != 0, "Bet should be in an 'active' state");
bet.amount = 0;
bytes32 entropy = keccak256(abi.encodePacked(reveal, bet.clientSeed));
uint dice = uint(entropy) % bet.modulo;
uint diceWin = _plinkoGetDiceWin(dice, amount, rollUnder, bet.gambler);
lockedInBets -= uint128(bet.locked);
uint jackpotWin = checkJackPotWin(entropy, amount, bet.modulo);
if (jackpotWin > 0) {
emit JackpotPayment(bet.gambler, uint(keccak256(abi.encodePacked(reveal))), jackpotWin);
}
sendFunds(
bet.gambler,
diceWin + jackpotWin == 0 ? 1 wei : diceWin + jackpotWin,
diceWin,
uint(keccak256(abi.encodePacked(reveal))),
'payment'
);
}
function _plinkoGetDiceWin(uint dice, uint amount, uint rollUnder, address gambler) internal view returns (uint) {
uint bytesCount = 0;
uint diceWin = 1;
for (uint byteNum = 0; byteNum < PLINKO_BYTES; byteNum += 1) {
if ((2 ** byteNum) & dice != 0) {
bytesCount += 1;
}
}
uint inCellRatio = _getPlinkoCellRatio(rollUnder, bytesCount);
uint jackpotFee = amount >= MIN_JACKPOT_BET ? JACKPOT_FEE : 0;
uint totalPercentages;
if (plinkoSkipComission == 2) {
totalPercentages = PLINKO_PERCENT + ADVERTISE_PERCENT + DIVIDENDS_PERCENT;
} else {
totalPercentages = HOUSE_EDGE_PERCENT + ADVERTISE_PERCENT + DIVIDENDS_PERCENT;
}
uint houseEdge = amount * (totalPercentages - getBonusProgrammLevel(gambler)) / PERCENTAGES_BASE;
if (houseEdge < HOUSE_EDGE_MINIMUM_AMOUNT) {
houseEdge = HOUSE_EDGE_MINIMUM_AMOUNT;
}
diceWin = (amount - houseEdge - jackpotFee) * inCellRatio / 100;
return diceWin;
}
function _getPlinkoCellRatio(uint plinkoNum, uint cell) internal view returns (uint cellRatio) {
if (plinkoNum == 1) {
cellRatio = PLINKO1Ratios[cell];
} else if (plinkoNum == 2) {
cellRatio = PLINKO2Ratios[cell];
} else {
cellRatio = PLINKO3Ratios[cell];
}
}
// Slots
function settleBetSlots(Bet storage bet, uint reveal) private {
uint amount = bet.amount;
uint modulo = bet.modulo;
require (amount != 0, "Bet should be in an 'active' state");
bet.amount = 0;
bytes32 entropy = keccak256(abi.encodePacked(reveal, bet.clientSeed));
uint diceWin = _slotsWinAmount(entropy, amount, bet.gambler);
lockedInBets -= uint128(bet.locked);
uint jackpotWin = checkJackPotWin(entropy, amount, modulo);
if (jackpotWin > 0) {
emit JackpotPayment(bet.gambler, uint(keccak256(abi.encodePacked(reveal))), jackpotWin);
}
sendFunds(
bet.gambler,
diceWin + jackpotWin == 0 ? 1 wei : diceWin + jackpotWin,
diceWin,
uint(keccak256(abi.encodePacked(reveal))),
'payment'
);
}
function _slotsWinAmount(bytes32 entropy, uint amount, address gambler) internal view returns (uint winAmount) {
uint8 wins;
uint8 wild;
(wins, wild) = _slotsCheckWin(entropy);
winAmount = 0;
uint jackpotFee = amount >= MIN_JACKPOT_BET ? JACKPOT_FEE : 0;
uint totalPercentages = HOUSE_EDGE_PERCENT + ADVERTISE_PERCENT + DIVIDENDS_PERCENT;
uint houseEdge = amount * (totalPercentages - getBonusProgrammLevel(gambler)) / PERCENTAGES_BASE;
if (houseEdge < HOUSE_EDGE_MINIMUM_AMOUNT) {
houseEdge = HOUSE_EDGE_MINIMUM_AMOUNT;
}
winAmount = (amount - houseEdge - jackpotFee) / 100 * SLOTSWinsRatios[wins];
winAmount += (amount - houseEdge - jackpotFee) / 100 * SLOTSWildRatios[wild];
}
function _slotsCheckWin(bytes32 slots) internal pure returns(uint8 wins, uint8 wild) {
uint curNum;
uint prevNum;
bytes1 charAtPos;
uint8 firstNums;
uint8 prevWins = 0;
uint8 curWins = 0;
wins = 0;
wild = 0;
for(uint8 i = 0; i < SLOTS_COUNT; i++) {
charAtPos = charAt(slots, i + 2);
firstNums = getLastN(charAtPos, 4);
curNum = uint(firstNums);
if (curNum > 8) {
curNum = 16 - curNum;
}
if (curNum == 7) wild += 1;
if (i == 0) {
prevNum = curNum;
continue;
}
if (prevNum == curNum) {
curWins += 1;
} else {
prevWins = (curWins > prevWins) ? curWins : prevWins;
curWins = 0;
}
prevNum = curNum;
}
wins = (curWins > prevWins) ? curWins : prevWins;
}
function settleBetRoulette(BetRoulette storage betRoulette, uint reveal) private {
require (betRoulette.totalBetAmount != 0, "Bet should be in an 'active' state");
address payable gambler = betRoulette.gambler;
bytes32 entropy = keccak256(abi.encodePacked(reveal, betRoulette.clientSeed));
uint diceWin = 0;
uint diceWinAmount;
uint feeToJP = betRoulette.totalBetAmount >= MIN_JACKPOT_BET ? JACKPOT_FEE / betRoulette.betsCount : 0;
uint dice = uint(entropy) % ROULETTE_ID;
uint8 bonuses = getBonusProgrammLevel(betRoulette.gambler);
for(uint8 index = 0; index < betRoulette.betsCount; index += 1) {
if ((2 ** dice) & betRoulette.mask[index] != 0) {
diceWinAmount = getRouletteWinAmount(betRoulette.amount[index] - feeToJP, ROULETTE_ID - 1, betRoulette.rollUnder[index], bonuses, false);
diceWin += diceWinAmount;
}
}
lockedInBets -= betRoulette.locked;
uint jackpotWin = checkJackPotWin(entropy, betRoulette.totalBetAmount, ROULETTE_ID);
if (jackpotWin > 0) {
emit JackpotPayment(gambler, uint(keccak256(abi.encodePacked(reveal))), jackpotWin);
}
sendFunds(
gambler,
diceWin + jackpotWin == 0 ? 1 wei : diceWin + jackpotWin,
diceWin,
uint(keccak256(abi.encodePacked(reveal))),
'payment'
);
betRoulette.totalBetAmount = 0;
}
function refundBet(uint commit) external {
Bet storage bet = bets[commit];
uint amount = bet.amount;
require (amount != 0, "Bet should be in an 'active' state");
require (block.number > bet.placeBlockNumber + BET_EXPIRATION_BLOCKS, "Blockhash can't be queried by EVM.");
bet.amount = 0;
lockedInBets -= uint128(bet.locked);
if (amount >= MIN_JACKPOT_BET && jackpotSize > JACKPOT_FEE) {
jackpotSize -= uint128(JACKPOT_FEE);
}
sendFunds(bet.gambler, amount, amount, commit, 'refund');
}
function refundBetRoulette(uint commit) external {
BetRoulette storage betRoulette = betsRoulette[commit];
uint amount = betRoulette.totalBetAmount;
require (amount != 0, "Bet should be in an 'active' state");
require (block.number > betRoulette.placeBlockNumber + BET_EXPIRATION_BLOCKS, "Blockhash can't be queried by EVM.");
betRoulette.totalBetAmount = 0;
for(uint8 index = 0; index < betRoulette.betsCount; index += 1) {
betRoulette.amount[index] = 0;
}
lockedInBets -= betRoulette.locked;
if (amount >= MIN_JACKPOT_BET && jackpotSize > JACKPOT_FEE) {
jackpotSize -= uint128(JACKPOT_FEE);
}
sendFunds(betRoulette.gambler, amount, amount, commit, 'refund');
}
function getDiceWinAmount(uint amount, uint32 modulo, uint8 rollUnder, address gambler, bool init) private returns (uint128 winAmount, uint jackpotFee) {
require (0 < rollUnder && rollUnder <= modulo, "Win probability out of range.");
jackpotFee = amount >= MIN_JACKPOT_BET ? JACKPOT_FEE : 0;
uint8 totalPercentages;
if (plinkoSkipComission == 2) { // Plinko
totalPercentages = PLINKO_PERCENT + ADVERTISE_PERCENT + DIVIDENDS_PERCENT;
} else { // All other games
totalPercentages = HOUSE_EDGE_PERCENT + ADVERTISE_PERCENT + DIVIDENDS_PERCENT;
}
uint houseEdge = amount * (totalPercentages - getBonusProgrammLevel(gambler)) / PERCENTAGES_BASE;
if (houseEdge < HOUSE_EDGE_MINIMUM_AMOUNT) {
houseEdge = HOUSE_EDGE_MINIMUM_AMOUNT;
}
require (houseEdge + jackpotFee <= amount, "Bet doesn't even cover house edge.");
if (init) {
totalDividends += amount * DIVIDENDS_PERCENT / PERCENTAGES_BASE;
totalAdvertise += amount * ADVERTISE_PERCENT / PERCENTAGES_BASE;
}
if (modulo == PLINKO_ID) {
// We lock maximum for selected plinko row
if (rollUnder == 1) {
modulo = 10; // equal to PLINKO1Ratios[0] / 100
} else if (rollUnder == 2) {
modulo = 20; // equal to PLINKO2Ratios[0] / 100
} else {
modulo = 50; // equal to PLINKO3Ratios[0] / 100
}
rollUnder = 1;
}
if (modulo == SLOTS_ID) {
modulo = 5; // We lock x5 for slots
rollUnder = 1;
}
winAmount = uint128((amount - houseEdge - jackpotFee) * modulo / rollUnder);
}
function getRouletteWinAmount(uint amount, uint32 modulo, uint8 rollUnder, uint8 bonuses, bool init) private returns (uint128 winAmount) {
require (0 < rollUnder && rollUnder <= modulo, "Win probability out of range.");
uint houseEdge;
if (rouletteSkipComission == 1) { // Roulette
houseEdge = amount * ROULETTE_PERCENT / PERCENTAGES_BASE;
} else {
if (init) {
totalDividends += amount * DIVIDENDS_PERCENT / PERCENTAGES_BASE;
totalAdvertise += amount * ADVERTISE_PERCENT / PERCENTAGES_BASE;
}
houseEdge = amount * (HOUSE_EDGE_PERCENT + ADVERTISE_PERCENT + DIVIDENDS_PERCENT - bonuses) / PERCENTAGES_BASE;
}
if (houseEdge < HOUSE_EDGE_MINIMUM_AMOUNT) {
houseEdge = HOUSE_EDGE_MINIMUM_AMOUNT;
}
require (houseEdge <= amount, "Bet doesn't even cover house edge.");
winAmount = uint128((amount - houseEdge) * modulo / rollUnder);
}
function sendFunds(address payable beneficiary, uint amount, uint successLogAmount, uint commit, string memory paymentType) private {
if (beneficiary.send(amount)) {
emit Payment(beneficiary, commit, successLogAmount, paymentType);
} else {
emit FailedPayment(beneficiary, commit, amount, paymentType);
}
}
function checkJackPotWin(bytes32 entropy, uint amount, uint modulo) internal returns (uint jackpotWin) {
jackpotWin = 0;
if (amount >= MIN_JACKPOT_BET) {
uint jackpotRng = (uint(entropy) / modulo) % JACKPOT_MODULO;
if (jackpotRng == luckyNumber) {
jackpotWin = jackpotSize;
jackpotSize = 0;
}
}
}
uint constant POPCNT_MULT = 0x0000000000002000000000100000000008000000000400000000020000000001;
uint constant POPCNT_MASK = 0x0001041041041041041041041041041041041041041041041041041041041041;
uint constant POPCNT_MODULO = 0x3F;
function charAt(bytes32 b, uint char) private pure returns (bytes1) {
return bytes1(uint8(uint(b) / (2**((31 - char) * 8))));
}
function getLastN(bytes1 a, uint8 n) private pure returns (uint8) {
uint8 lastN = uint8(a) % uint8(2) ** n;
return lastN;
}
}