ETH Price: $2,050.18 (-1.73%)

Transaction Decoder

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 Code
0x0C073C92...19e864d18
(Xether.io: Games)
566.589721745790152649 Eth566.501431745790152649 Eth0.08829
(F2Pool Old)
5,686.730840813679979226 Eth5,686.731019088679979226 Eth0.000178275
0xceDabA98...8B256b545 0.697706137717391315 Eth0.785996137717391315 Eth0.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( )
    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;
        }
    }