True Spartan
Jr. Member
Offline
Activity: 360
Merit: 1
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 09, 2018, 01:41:41 AM |
|
#Winning!
|
|
|
|
True Spartan
Jr. Member
Offline
Activity: 360
Merit: 1
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 09, 2018, 01:47:28 AM |
|
Anyone here play at Bovada?
|
|
|
|
Nickie001
Jr. Member
Offline
Activity: 76
Merit: 1
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 09, 2018, 03:45:25 PM |
|
Anyone here play at Bovada?
No actually, I like Win-Win roulette more. Have you played it?
|
|
|
|
yeyicheng888
Member
![*](https://bitcointalk.org/Themes/custom1/images/star.gif)
Offline
Activity: 350
Merit: 10
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 10, 2018, 05:06:35 AM |
|
It's hard to create a game that everyone loves to play. If you want to meet most of the requirements, you'd better ask the player's opinion to make it an excellent project content.
|
|
|
|
retinger
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 10, 2018, 07:06:39 AM |
|
It's hard to create a game that everyone loves to play. If you want to meet most of the requirements, you'd better ask the player's opinion to make it an excellent project content.
You can always send an email to the devteam with your wishes, they always read emails and reply. By the way, what is your opinion about how to make the games and the project better? It's really interesting to read the different points of view ![Smiley](https://bitcointalk.org/Smileys/default/smiley.gif)
|
|
|
|
SmartPlay.tech (OP)
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 11, 2018, 06:15:18 PM |
|
It's hard to create a game that everyone loves to play. If you want to meet most of the requirements, you'd better ask the player's opinion to make it an excellent project content.
Thank you for your opinion ![Smiley](https://bitcointalk.org/Smileys/default/smiley.gif) Please, do not hesitate to contact us with your complaints and proposals, we are always open to your offers and comments on the project. You can leave it in this forum thread, in Telegram group or send it via email to info@smartplay.tech.
|
|
|
|
SmartPlay.tech (OP)
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 11, 2018, 06:17:58 PM |
|
RLT was my very first investment. And it was a success. I am glad that the team is moving forward. Good luck, Smart Play !
Thank you for the words of support! Join our Telegram group for the early investors and receive information about all the latest project news amont the first users!
|
|
|
|
SmartPlay.tech (OP)
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 12, 2018, 10:58:00 AM |
|
How fair is the roulette on the Ethereum smart contract? An independent audit of the game contract We present to your attention a roulette audit from an independent developer Paul Rubin. Read, study and share your opinion about roulette!
Today it is very unlikely to meet a person who hasn't heard about cryptocurrencies and, in particular, about Bitcoin. In 2014, on a wave of interest in bitcoin, a new cryptocurrency - Ethereum, has been created. Today, in 2018, it has the largest capitalization after bitcoin. One of its most important differences from bitcoin is the use of a Turing virtual machine - EVM. More information about the Ethererum can be found in its Yellow Paper.
Today we will explore a game that works with Metamask wallet, a plugin for Google Chrome.
The game is the realization of the European roulette: there is the field with 37 cells numbered from 0 to 36. A player can bet on a specific number, or on a set of numbers: even/odd, red/black, 1-12, 1-18, etc. In each round, the player can make multiple bets (the minimum bet size is 0.01 ETH ≈ $ 7.26) on the corresponding field of the game table. Each field corresponds to a winning ratio. For example, the rate "to red" corresponds to a coefficient of 2 - placing a bet in amount of 0.01 ETH, in case of winning, the player will receive 0.02 ETH. And if the player bets on zero, the coefficient will be 36: paying the same 0.01 ETH for the bet the player will get 0.36.
Note In the game contract, the coefficient for this bet is also listed as 35, and the bet amount is added to the amount of the win before the payment.
When all bets are made, the player presses the "Play" button and, via MetaMask, sends a bet to the Ethereum blockchain to the roulette contract address. The contract determines the random number, calculates the results of the bets and, if necessary, sends the winnings to the player.
In order to understand honestly whether the game is working (that is, if the casino does not manipulate when determining the random number for mercenary purposes), the work of the smart contract should be analyzed.
Its address is listed on the game website. In addition, before confirming the payment, you can check where the bet will be sent. The contract at the address 0xDfC328c19C8De45ac0117f836646378c10e0CdA3 will be analyzed as an example. Etherscan shows its code, and for convenient viewing, you can use Solidity Browser. The work of the contract begins with the placeBet() function:function placeBet(uint256 bets, bytes32 values1,bytes32 values2) public payable { if (ContractState == false) { ErrorLog(msg.sender, "ContractDisabled"); if (msg.sender.send(msg.value) == false) throw; return; } var gamblesLength = gambles.length;
if (gamblesLength > 0) { uint8 gamblesCountInCurrentBlock = 0; for(var i = gamblesLength - 1;i > 0; i--) { if (gambles[i].blockNumber == block.number) { if (gambles[i].player == msg.sender) { ErrorLog(msg.sender, "Play twice the same block"); if (msg.sender.send(msg.value) == false) throw; return; }
gamblesCountInCurrentBlock++; if (gamblesCountInCurrentBlock >= maxGamblesPerBlock) { ErrorLog(msg.sender, "maxGamblesPerBlock"); if (msg.sender.send(msg.value) == false) throw; return; } } else { break; } } } var _currentMaxBet = currentMaxBet;
if (msg.value < _currentMaxBet/256 || bets == 0) { ErrorLog(msg.sender, "Wrong bet value"); if (msg.sender.send(msg.value) == false) throw; return; }
if (msg.value > _currentMaxBet) { ErrorLog(msg.sender, "Limit for table"); if (msg.sender.send(msg.value) == false) throw; return; }
GameInfo memory g = GameInfo(msg.sender, block.number, 37, bets, values1,values2);
if (totalBetValue(g) != msg.value) { ErrorLog(msg.sender, "Wrong bet value"); if (msg.sender.send(msg.value) == false) throw; return; }
address affiliate = 0; uint16 coef_affiliate = 0; uint16 coef_player; if (address(smartAffiliateContract) > 0) { (affiliate, coef_affiliate, coef_player) = smartAffiliateContract.getAffiliateInfo(msg.sender); } else { coef_player = CoefPlayerEmission; }
uint256 playerTokens; uint8 errorCodeEmission; (playerTokens, errorCodeEmission) = smartToken.emission(msg.sender, affiliate, msg.value, coef_player, coef_affiliate); if (errorCodeEmission != 0) { if (errorCodeEmission == 1) ErrorLog(msg.sender, "token operations stopped"); else if (errorCodeEmission == 2) ErrorLog(msg.sender, "contract is not in a games list"); else if (errorCodeEmission == 3) ErrorLog(msg.sender, "incorect player address"); else if (errorCodeEmission == 4) ErrorLog(msg.sender, "incorect value bet"); else if (errorCodeEmission == 5) ErrorLog(msg.sender, "incorect Coefficient emissions"); if (msg.sender.send(msg.value) == false) throw; return; }
gambles.push(g);
PlayerBet(gamblesLength, playerTokens); }
For Solidity newbies: the public and payable modifiers mean that the function is part of the API contract and when you call it, you can send ETH. In this case, information about the sender and the ETH amount will be available via the variable msg. The call parameters are the bit mask of bet types and two 32-byte arrays with the number of bets per type. You can guess this by looking at the definition of the GameInfo type and the functions getBetValueByGamble(), getBetValue (). struct GameInfo { address player; uint256 blockNumber; uint8 wheelResult; uint256 bets; bytes32 values; bytes32 values2; }
// n - number player bet // nBit - betIndex function getBetValueByGamble(GameInfo memory gamble, uint8 n, uint8 nBit) private constant returns (uint256) { if (n <= 32) return getBetValue(gamble.values , n, nBit); if (n <= 64) return getBetValue(gamble.values2, n - 32, nBit); // there are 64 maximum unique bets (positions) in one game throw; }
// n form 1 <= to <= 32 function getBetValue(bytes32 values, uint8 n, uint8 nBit) private constant returns (uint256) { // bet in credits (1..256) uint256 bet = uint256(values[32 - n]) + 1;
if (bet < uint256(minCreditsOnBet[nBit]+1)) throw; //default: bet < 0+1 if (bet > uint256(256-maxCreditsOnBet[nBit])) throw; //default: bet > 256-0
return currentMaxBet * bet / 256; }
It is worth noting that getBetValue() returns the amount of the bet in wei. The first thing placeBet() checks is that the contract is not turned off and then bet checks begin. The gambles array is the repository of all the bets placed in this contract. placeBet() finds all the bets in its block and checks whether the current player has sent another bet in this block and whether the allowed amount of betting in the block is exceeded. Then, restrictions on the minimum and maximum amount of the bet are checked. In the event of any error, the execution of the contract is terminated by the throw command, which rolls back the transaction, returning ETH to the player. Further, the parameters passed to the function are stored in the GameInfo structure. It is important for us that the wheelResult field is initialized with the number 37. After another check that the amount of bets coincides with the amount of ETH sent, the RLT tokens are distributed, the referral program is processed, the bet information is stored in gambles and a PlayerBet event is generated with the number and amount of the bet, which is seen in the game's web part. RLT tokens_At each bet, the player is given a certain number of RLT, ERC-20 tokens, which determine the right of the owner of tokens to receive the loyalty rewards from the profits received by the game creators. More information about this is given in White Paper. The further life of the bet begins with calling the function ProcessGames(), which, after the appearance of a new bet, is being executed, at the present time, from the address 0xa92d36dc1ca4f505f1886503a0626c4aa8106497. Such calls are well visible when viewing the list of transactions of the game contract: they have Value=0. ProcessGames code function ProcessGames(uint256[] gameIndexes, bool simulate) { if (!simulate) { if (lastBlockGamesProcessed == block.number) return; lastBlockGamesProcessed = block.number; }
uint8 delay = BlockDelay; uint256 length = gameIndexes.length; bool success = false; for(uint256 i = 0;i < length;i++) { if (ProcessGame(gameIndexes[i], delay) == GameStatus.Success) success = true; } if (simulate && !success) throw; }
In the call parameters, an array with the bet numbers requiring calculation is transmitted, and for each ProcessGame is called. function ProcessGame(uint256 index, uint256 delay) private returns (GameStatus) { GameInfo memory g = gambles[index]; if (block.number - g.blockNumber >= 256) return GameStatus.Stop;
if (g.wheelResult == 37 && block.number > g.blockNumber + delay) { gambles[index].wheelResult = getRandomNumber(g.player, g.blockNumber); uint256 playerWinnings = getGameResult(gambles[index]); if (playerWinnings > 0) { if (g.player.send(playerWinnings) == false) throw; }
EndGame(g.player, gambles[index].wheelResult, index); return GameStatus.Success; }
return GameStatus.Skipped; }
The call parameters are the bet number and the number of blocks that must pass between the bet and its processing. When called from ProcessGames() or ProcessGameExt(), this parameter is currently 1, this value can be learned from the result of calling getSettings(). In the event that the number of the block in which processing takes place is more than 255 blocks apart from the betting block, it can not be processed: the block hash is available only for the last 256 blocks, and it is needed to determine the number dropped. Next, the code checks whether the result of the game has already been calculated (remember, wheelResult was initialized with the number 37, which can't be the game result?) and whether the required number of blocks has already passed. If the conditions are met, the call to getRandomNumber() is done to determine the random number, the win is calculated by calling getGameResult(). If it is not zero, ETH is sent to the player: g.player.send(playerWinnings). Then an EndGame event is created, which can be read from the web part of the game. Let's see the most interesting part: how to determine the random number: the function getRandomNumber(). function getRandomNumber(address player, uint256 playerblock) private returns(uint8 wheelResult) { // block.blockhash - hash of the given block - only works for 256 most recent blocks excluding current bytes32 blockHash = block.blockhash(playerblock+BlockDelay); if (blockHash==0) { ErrorLog(msg.sender, "Cannot generate random number"); wheelResult = 200; } else { bytes32 shaPlayer = sha3(player, blockHash);
wheelResult = uint8(uint256(shaPlayer)%37); } }
Its arguments are the player's address and the block number in which the bet was placed. The first thing the function gets is a block hash, which is separated from the bet block for BlockDelay blocks into the future. This is an important point because if a player can somehow learn the hash of this block in advance, he can form a bet that is guaranteed to win. If you remember that there are Uncle blocks in Ethereum, there may be a problem and further analysis is required. Next, SHA-3 is calculated from the gluing of the player's address and the received block hash. The dropped number is calculated by taking the remainder of dividing the result of SHA-3 by 37. From my point of view, the algorithm is quite honest and the casino has no precedence over the player.Why does the casino bring profit to its owners?There are 37 cells on the field. For example, a player wants to make 100 000 bets on one particular field. Probably, about 2703 times the player will win, and all the other times he will lose. In this case, from the casino winnings, the player will receive 2703 * 36 = 97,308 RLT tokens. And 2692 RLT tokens, spent on bets, will go to the casino. Similar calculations can be made for all other types of bets. It is also interesting to see, how the win is calculated. The getGameResult() function does this: function getGameResult(GameInfo memory game) private constant returns (uint256 totalWin) { totalWin = 0; uint8 nPlayerBetNo = 0; // we sent count bets at last byte uint8 betsCount = uint8(bytes32(game.bets)[0]); for(uint8 i=0; i<maxTypeBets; i++) { if (isBitSet(game.bets, i)) { var winMul = winMatrix.getCoeff(getIndex(i, game.wheelResult)); // get win coef if (winMul > 0) winMul++; // + return player bet totalWin += winMul * getBetValueByGamble(game, nPlayerBetNo+1,i); nPlayerBetNo++;
if (betsCount == 1) break; betsCount--; } } }
Parameter here is the structure of GameInfo with data about the calculated bet. And its wheelResult field is already filled with a random number. Here is the cycle for all types of bets, in which the bit mask game.bets is checked and if the bit of the type being checked is installed, winMatrix.getCoeff() is requested. winMatrix is the contract with the address 0x073D6621E9150bFf9d1D450caAd3c790b6F071F2, loaded in the SmartRoulettee() constructor. A parameter of this function is a combination of the bet type and the random number: // unique combination of bet and wheelResult, used for access to WinMatrix function getIndex(uint16 bet, uint16 wheelResult) private constant returns (uint16) { return (bet+1)*256 + (wheelResult+1); } Have you played the Blockchain roulette by SmartPlay.tech? Don’t forget to share your opinion with other players, join the Telegram chat with the early project investors and ask you questions to the official representatives.
|
|
|
|
Nickie001
Jr. Member
Offline
Activity: 76
Merit: 1
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 12, 2018, 11:23:54 AM |
|
How fair is the roulette on the Ethereum smart contract? An independent audit of the game contract We present to your attention a roulette audit from an independent developer Paul Rubin. Read, study and share your opinion about roulette!
Today it is very unlikely to meet a person who hasn't heard about cryptocurrencies and, in particular, about Bitcoin. In 2014, on a wave of interest in bitcoin, a new cryptocurrency - Ethereum, has been created. Today, in 2018, it has the largest capitalization after bitcoin. One of its most important differences from bitcoin is the use of a Turing virtual machine - EVM. More information about the Ethererum can be found in its Yellow Paper.
Today we will explore a game that works with Metamask wallet, a plugin for Google Chrome.
The game is the realization of the European roulette: there is the field with 37 cells numbered from 0 to 36. A player can bet on a specific number, or on a set of numbers: even/odd, red/black, 1-12, 1-18, etc. In each round, the player can make multiple bets (the minimum bet size is 0.01 ETH ≈ $ 7.26) on the corresponding field of the game table. Each field corresponds to a winning ratio. For example, the rate "to red" corresponds to a coefficient of 2 - placing a bet in amount of 0.01 ETH, in case of winning, the player will receive 0.02 ETH. And if the player bets on zero, the coefficient will be 36: paying the same 0.01 ETH for the bet the player will get 0.36.
Note In the game contract, the coefficient for this bet is also listed as 35, and the bet amount is added to the amount of the win before the payment.
When all bets are made, the player presses the "Play" button and, via MetaMask, sends a bet to the Ethereum blockchain to the roulette contract address. The contract determines the random number, calculates the results of the bets and, if necessary, sends the winnings to the player.
In order to understand honestly whether the game is working (that is, if the casino does not manipulate when determining the random number for mercenary purposes), the work of the smart contract should be analyzed.
Its address is listed on the game website. In addition, before confirming the payment, you can check where the bet will be sent. The contract at the address 0xDfC328c19C8De45ac0117f836646378c10e0CdA3 will be analyzed as an example. Etherscan shows its code, and for convenient viewing, you can use Solidity Browser. The work of the contract begins with the placeBet() function:function placeBet(uint256 bets, bytes32 values1,bytes32 values2) public payable { if (ContractState == false) { ErrorLog(msg.sender, "ContractDisabled"); if (msg.sender.send(msg.value) == false) throw; return; } var gamblesLength = gambles.length;
if (gamblesLength > 0) { uint8 gamblesCountInCurrentBlock = 0; for(var i = gamblesLength - 1;i > 0; i--) { if (gambles[i].blockNumber == block.number) { if (gambles[i].player == msg.sender) { ErrorLog(msg.sender, "Play twice the same block"); if (msg.sender.send(msg.value) == false) throw; return; }
gamblesCountInCurrentBlock++; if (gamblesCountInCurrentBlock >= maxGamblesPerBlock) { ErrorLog(msg.sender, "maxGamblesPerBlock"); if (msg.sender.send(msg.value) == false) throw; return; } } else { break; } } } var _currentMaxBet = currentMaxBet;
if (msg.value < _currentMaxBet/256 || bets == 0) { ErrorLog(msg.sender, "Wrong bet value"); if (msg.sender.send(msg.value) == false) throw; return; }
if (msg.value > _currentMaxBet) { ErrorLog(msg.sender, "Limit for table"); if (msg.sender.send(msg.value) == false) throw; return; }
GameInfo memory g = GameInfo(msg.sender, block.number, 37, bets, values1,values2);
if (totalBetValue(g) != msg.value) { ErrorLog(msg.sender, "Wrong bet value"); if (msg.sender.send(msg.value) == false) throw; return; }
address affiliate = 0; uint16 coef_affiliate = 0; uint16 coef_player; if (address(smartAffiliateContract) > 0) { (affiliate, coef_affiliate, coef_player) = smartAffiliateContract.getAffiliateInfo(msg.sender); } else { coef_player = CoefPlayerEmission; }
uint256 playerTokens; uint8 errorCodeEmission; (playerTokens, errorCodeEmission) = smartToken.emission(msg.sender, affiliate, msg.value, coef_player, coef_affiliate); if (errorCodeEmission != 0) { if (errorCodeEmission == 1) ErrorLog(msg.sender, "token operations stopped"); else if (errorCodeEmission == 2) ErrorLog(msg.sender, "contract is not in a games list"); else if (errorCodeEmission == 3) ErrorLog(msg.sender, "incorect player address"); else if (errorCodeEmission == 4) ErrorLog(msg.sender, "incorect value bet"); else if (errorCodeEmission == 5) ErrorLog(msg.sender, "incorect Coefficient emissions"); if (msg.sender.send(msg.value) == false) throw; return; }
gambles.push(g);
PlayerBet(gamblesLength, playerTokens); }
For Solidity newbies: the public and payable modifiers mean that the function is part of the API contract and when you call it, you can send ETH. In this case, information about the sender and the ETH amount will be available via the variable msg. The call parameters are the bit mask of bet types and two 32-byte arrays with the number of bets per type. You can guess this by looking at the definition of the GameInfo type and the functions getBetValueByGamble(), getBetValue (). struct GameInfo { address player; uint256 blockNumber; uint8 wheelResult; uint256 bets; bytes32 values; bytes32 values2; }
// n - number player bet // nBit - betIndex function getBetValueByGamble(GameInfo memory gamble, uint8 n, uint8 nBit) private constant returns (uint256) { if (n <= 32) return getBetValue(gamble.values , n, nBit); if (n <= 64) return getBetValue(gamble.values2, n - 32, nBit); // there are 64 maximum unique bets (positions) in one game throw; }
// n form 1 <= to <= 32 function getBetValue(bytes32 values, uint8 n, uint8 nBit) private constant returns (uint256) { // bet in credits (1..256) uint256 bet = uint256(values[32 - n]) + 1;
if (bet < uint256(minCreditsOnBet[nBit]+1)) throw; //default: bet < 0+1 if (bet > uint256(256-maxCreditsOnBet[nBit])) throw; //default: bet > 256-0
return currentMaxBet * bet / 256; }
It is worth noting that getBetValue() returns the amount of the bet in wei. The first thing placeBet() checks is that the contract is not turned off and then bet checks begin. The gambles array is the repository of all the bets placed in this contract. placeBet() finds all the bets in its block and checks whether the current player has sent another bet in this block and whether the allowed amount of betting in the block is exceeded. Then, restrictions on the minimum and maximum amount of the bet are checked. In the event of any error, the execution of the contract is terminated by the throw command, which rolls back the transaction, returning ETH to the player. Further, the parameters passed to the function are stored in the GameInfo structure. It is important for us that the wheelResult field is initialized with the number 37. After another check that the amount of bets coincides with the amount of ETH sent, the RLT tokens are distributed, the referral program is processed, the bet information is stored in gambles and a PlayerBet event is generated with the number and amount of the bet, which is seen in the game's web part. RLT tokens_At each bet, the player is given a certain number of RLT, ERC-20 tokens, which determine the right of the owner of tokens to receive the loyalty rewards from the profits received by the game creators. More information about this is given in White Paper. The further life of the bet begins with calling the function ProcessGames(), which, after the appearance of a new bet, is being executed, at the present time, from the address 0xa92d36dc1ca4f505f1886503a0626c4aa8106497. Such calls are well visible when viewing the list of transactions of the game contract: they have Value=0. ProcessGames code function ProcessGames(uint256[] gameIndexes, bool simulate) { if (!simulate) { if (lastBlockGamesProcessed == block.number) return; lastBlockGamesProcessed = block.number; }
uint8 delay = BlockDelay; uint256 length = gameIndexes.length; bool success = false; for(uint256 i = 0;i < length;i++) { if (ProcessGame(gameIndexes[i], delay) == GameStatus.Success) success = true; } if (simulate && !success) throw; }
In the call parameters, an array with the bet numbers requiring calculation is transmitted, and for each ProcessGame is called. function ProcessGame(uint256 index, uint256 delay) private returns (GameStatus) { GameInfo memory g = gambles[index]; if (block.number - g.blockNumber >= 256) return GameStatus.Stop;
if (g.wheelResult == 37 && block.number > g.blockNumber + delay) { gambles[index].wheelResult = getRandomNumber(g.player, g.blockNumber); uint256 playerWinnings = getGameResult(gambles[index]); if (playerWinnings > 0) { if (g.player.send(playerWinnings) == false) throw; }
EndGame(g.player, gambles[index].wheelResult, index); return GameStatus.Success; }
return GameStatus.Skipped; }
The call parameters are the bet number and the number of blocks that must pass between the bet and its processing. When called from ProcessGames() or ProcessGameExt(), this parameter is currently 1, this value can be learned from the result of calling getSettings(). In the event that the number of the block in which processing takes place is more than 255 blocks apart from the betting block, it can not be processed: the block hash is available only for the last 256 blocks, and it is needed to determine the number dropped. Next, the code checks whether the result of the game has already been calculated (remember, wheelResult was initialized with the number 37, which can't be the game result?) and whether the required number of blocks has already passed. If the conditions are met, the call to getRandomNumber() is done to determine the random number, the win is calculated by calling getGameResult(). If it is not zero, ETH is sent to the player: g.player.send(playerWinnings). Then an EndGame event is created, which can be read from the web part of the game. Let's see the most interesting part: how to determine the random number: the function getRandomNumber(). function getRandomNumber(address player, uint256 playerblock) private returns(uint8 wheelResult) { // block.blockhash - hash of the given block - only works for 256 most recent blocks excluding current bytes32 blockHash = block.blockhash(playerblock+BlockDelay); if (blockHash==0) { ErrorLog(msg.sender, "Cannot generate random number"); wheelResult = 200; } else { bytes32 shaPlayer = sha3(player, blockHash);
wheelResult = uint8(uint256(shaPlayer)%37); } }
Its arguments are the player's address and the block number in which the bet was placed. The first thing the function gets is a block hash, which is separated from the bet block for BlockDelay blocks into the future. This is an important point because if a player can somehow learn the hash of this block in advance, he can form a bet that is guaranteed to win. If you remember that there are Uncle blocks in Ethereum, there may be a problem and further analysis is required. Next, SHA-3 is calculated from the gluing of the player's address and the received block hash. The dropped number is calculated by taking the remainder of dividing the result of SHA-3 by 37. From my point of view, the algorithm is quite honest and the casino has no precedence over the player.Why does the casino bring profit to its owners?There are 37 cells on the field. For example, a player wants to make 100 000 bets on one particular field. Probably, about 2703 times the player will win, and all the other times he will lose. In this case, from the casino winnings, the player will receive 2703 * 36 = 97,308 RLT tokens. And 2692 RLT tokens, spent on bets, will go to the casino. Similar calculations can be made for all other types of bets. It is also interesting to see, how the win is calculated. The getGameResult() function does this: function getGameResult(GameInfo memory game) private constant returns (uint256 totalWin) { totalWin = 0; uint8 nPlayerBetNo = 0; // we sent count bets at last byte uint8 betsCount = uint8(bytes32(game.bets)[0]); for(uint8 i=0; i<maxTypeBets; i++) { if (isBitSet(game.bets, i)) { var winMul = winMatrix.getCoeff(getIndex(i, game.wheelResult)); // get win coef if (winMul > 0) winMul++; // + return player bet totalWin += winMul * getBetValueByGamble(game, nPlayerBetNo+1,i); nPlayerBetNo++;
if (betsCount == 1) break; betsCount--; } } }
Parameter here is the structure of GameInfo with data about the calculated bet. And its wheelResult field is already filled with a random number. Here is the cycle for all types of bets, in which the bit mask game.bets is checked and if the bit of the type being checked is installed, winMatrix.getCoeff() is requested. winMatrix is the contract with the address 0x073D6621E9150bFf9d1D450caAd3c790b6F071F2, loaded in the SmartRoulettee() constructor. A parameter of this function is a combination of the bet type and the random number: // unique combination of bet and wheelResult, used for access to WinMatrix function getIndex(uint16 bet, uint16 wheelResult) private constant returns (uint16) { return (bet+1)*256 + (wheelResult+1); } Have you played the Blockchain roulette by SmartPlay.tech? Don’t forget to share your opinion with other players, join the Telegram chat with the early project investors and ask you questions to the official representatives.Aaaand I did not doubt that this roulette is fair, nice job guys. Hope the project will succeed, since I look forward to a new game to play ![Grin](https://bitcointalk.org/Smileys/default/grin.gif) How long will it take to implement all the changes you've talked about?
|
|
|
|
SmartPlay.tech (OP)
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 12, 2018, 11:53:10 AM |
|
How fair is the roulette on the Ethereum smart contract? An independent audit of the game contract We present to your attention a roulette audit from an independent developer Paul Rubin. Read, study and share your opinion about roulette!
Today it is very unlikely to meet a person who hasn't heard about cryptocurrencies and, in particular, about Bitcoin. In 2014, on a wave of interest in bitcoin, a new cryptocurrency - Ethereum, has been created. Today, in 2018, it has the largest capitalization after bitcoin. One of its most important differences from bitcoin is the use of a Turing virtual machine - EVM. More information about the Ethererum can be found in its Yellow Paper.
Today we will explore a game that works with Metamask wallet, a plugin for Google Chrome.
The game is the realization of the European roulette: there is the field with 37 cells numbered from 0 to 36. A player can bet on a specific number, or on a set of numbers: even/odd, red/black, 1-12, 1-18, etc. In each round, the player can make multiple bets (the minimum bet size is 0.01 ETH ≈ $ 7.26) on the corresponding field of the game table. Each field corresponds to a winning ratio. For example, the rate "to red" corresponds to a coefficient of 2 - placing a bet in amount of 0.01 ETH, in case of winning, the player will receive 0.02 ETH. And if the player bets on zero, the coefficient will be 36: paying the same 0.01 ETH for the bet the player will get 0.36.
Note In the game contract, the coefficient for this bet is also listed as 35, and the bet amount is added to the amount of the win before the payment.
When all bets are made, the player presses the "Play" button and, via MetaMask, sends a bet to the Ethereum blockchain to the roulette contract address. The contract determines the random number, calculates the results of the bets and, if necessary, sends the winnings to the player.
In order to understand honestly whether the game is working (that is, if the casino does not manipulate when determining the random number for mercenary purposes), the work of the smart contract should be analyzed.
Its address is listed on the game website. In addition, before confirming the payment, you can check where the bet will be sent. The contract at the address 0xDfC328c19C8De45ac0117f836646378c10e0CdA3 will be analyzed as an example. Etherscan shows its code, and for convenient viewing, you can use Solidity Browser. The work of the contract begins with the placeBet() function:function placeBet(uint256 bets, bytes32 values1,bytes32 values2) public payable { if (ContractState == false) { ErrorLog(msg.sender, "ContractDisabled"); if (msg.sender.send(msg.value) == false) throw; return; } var gamblesLength = gambles.length;
if (gamblesLength > 0) { uint8 gamblesCountInCurrentBlock = 0; for(var i = gamblesLength - 1;i > 0; i--) { if (gambles[i].blockNumber == block.number) { if (gambles[i].player == msg.sender) { ErrorLog(msg.sender, "Play twice the same block"); if (msg.sender.send(msg.value) == false) throw; return; }
gamblesCountInCurrentBlock++; if (gamblesCountInCurrentBlock >= maxGamblesPerBlock) { ErrorLog(msg.sender, "maxGamblesPerBlock"); if (msg.sender.send(msg.value) == false) throw; return; } } else { break; } } } var _currentMaxBet = currentMaxBet;
if (msg.value < _currentMaxBet/256 || bets == 0) { ErrorLog(msg.sender, "Wrong bet value"); if (msg.sender.send(msg.value) == false) throw; return; }
if (msg.value > _currentMaxBet) { ErrorLog(msg.sender, "Limit for table"); if (msg.sender.send(msg.value) == false) throw; return; }
GameInfo memory g = GameInfo(msg.sender, block.number, 37, bets, values1,values2);
if (totalBetValue(g) != msg.value) { ErrorLog(msg.sender, "Wrong bet value"); if (msg.sender.send(msg.value) == false) throw; return; }
address affiliate = 0; uint16 coef_affiliate = 0; uint16 coef_player; if (address(smartAffiliateContract) > 0) { (affiliate, coef_affiliate, coef_player) = smartAffiliateContract.getAffiliateInfo(msg.sender); } else { coef_player = CoefPlayerEmission; }
uint256 playerTokens; uint8 errorCodeEmission; (playerTokens, errorCodeEmission) = smartToken.emission(msg.sender, affiliate, msg.value, coef_player, coef_affiliate); if (errorCodeEmission != 0) { if (errorCodeEmission == 1) ErrorLog(msg.sender, "token operations stopped"); else if (errorCodeEmission == 2) ErrorLog(msg.sender, "contract is not in a games list"); else if (errorCodeEmission == 3) ErrorLog(msg.sender, "incorect player address"); else if (errorCodeEmission == 4) ErrorLog(msg.sender, "incorect value bet"); else if (errorCodeEmission == 5) ErrorLog(msg.sender, "incorect Coefficient emissions"); if (msg.sender.send(msg.value) == false) throw; return; }
gambles.push(g);
PlayerBet(gamblesLength, playerTokens); }
For Solidity newbies: the public and payable modifiers mean that the function is part of the API contract and when you call it, you can send ETH. In this case, information about the sender and the ETH amount will be available via the variable msg. The call parameters are the bit mask of bet types and two 32-byte arrays with the number of bets per type. You can guess this by looking at the definition of the GameInfo type and the functions getBetValueByGamble(), getBetValue (). struct GameInfo { address player; uint256 blockNumber; uint8 wheelResult; uint256 bets; bytes32 values; bytes32 values2; }
// n - number player bet // nBit - betIndex function getBetValueByGamble(GameInfo memory gamble, uint8 n, uint8 nBit) private constant returns (uint256) { if (n <= 32) return getBetValue(gamble.values , n, nBit); if (n <= 64) return getBetValue(gamble.values2, n - 32, nBit); // there are 64 maximum unique bets (positions) in one game throw; }
// n form 1 <= to <= 32 function getBetValue(bytes32 values, uint8 n, uint8 nBit) private constant returns (uint256) { // bet in credits (1..256) uint256 bet = uint256(values[32 - n]) + 1;
if (bet < uint256(minCreditsOnBet[nBit]+1)) throw; //default: bet < 0+1 if (bet > uint256(256-maxCreditsOnBet[nBit])) throw; //default: bet > 256-0
return currentMaxBet * bet / 256; }
It is worth noting that getBetValue() returns the amount of the bet in wei. The first thing placeBet() checks is that the contract is not turned off and then bet checks begin. The gambles array is the repository of all the bets placed in this contract. placeBet() finds all the bets in its block and checks whether the current player has sent another bet in this block and whether the allowed amount of betting in the block is exceeded. Then, restrictions on the minimum and maximum amount of the bet are checked. In the event of any error, the execution of the contract is terminated by the throw command, which rolls back the transaction, returning ETH to the player. Further, the parameters passed to the function are stored in the GameInfo structure. It is important for us that the wheelResult field is initialized with the number 37. After another check that the amount of bets coincides with the amount of ETH sent, the RLT tokens are distributed, the referral program is processed, the bet information is stored in gambles and a PlayerBet event is generated with the number and amount of the bet, which is seen in the game's web part. RLT tokens_At each bet, the player is given a certain number of RLT, ERC-20 tokens, which determine the right of the owner of tokens to receive the loyalty rewards from the profits received by the game creators. More information about this is given in White Paper. The further life of the bet begins with calling the function ProcessGames(), which, after the appearance of a new bet, is being executed, at the present time, from the address 0xa92d36dc1ca4f505f1886503a0626c4aa8106497. Such calls are well visible when viewing the list of transactions of the game contract: they have Value=0. ProcessGames code function ProcessGames(uint256[] gameIndexes, bool simulate) { if (!simulate) { if (lastBlockGamesProcessed == block.number) return; lastBlockGamesProcessed = block.number; }
uint8 delay = BlockDelay; uint256 length = gameIndexes.length; bool success = false; for(uint256 i = 0;i < length;i++) { if (ProcessGame(gameIndexes[i], delay) == GameStatus.Success) success = true; } if (simulate && !success) throw; }
In the call parameters, an array with the bet numbers requiring calculation is transmitted, and for each ProcessGame is called. function ProcessGame(uint256 index, uint256 delay) private returns (GameStatus) { GameInfo memory g = gambles[index]; if (block.number - g.blockNumber >= 256) return GameStatus.Stop;
if (g.wheelResult == 37 && block.number > g.blockNumber + delay) { gambles[index].wheelResult = getRandomNumber(g.player, g.blockNumber); uint256 playerWinnings = getGameResult(gambles[index]); if (playerWinnings > 0) { if (g.player.send(playerWinnings) == false) throw; }
EndGame(g.player, gambles[index].wheelResult, index); return GameStatus.Success; }
return GameStatus.Skipped; }
The call parameters are the bet number and the number of blocks that must pass between the bet and its processing. When called from ProcessGames() or ProcessGameExt(), this parameter is currently 1, this value can be learned from the result of calling getSettings(). In the event that the number of the block in which processing takes place is more than 255 blocks apart from the betting block, it can not be processed: the block hash is available only for the last 256 blocks, and it is needed to determine the number dropped. Next, the code checks whether the result of the game has already been calculated (remember, wheelResult was initialized with the number 37, which can't be the game result?) and whether the required number of blocks has already passed. If the conditions are met, the call to getRandomNumber() is done to determine the random number, the win is calculated by calling getGameResult(). If it is not zero, ETH is sent to the player: g.player.send(playerWinnings). Then an EndGame event is created, which can be read from the web part of the game. Let's see the most interesting part: how to determine the random number: the function getRandomNumber(). function getRandomNumber(address player, uint256 playerblock) private returns(uint8 wheelResult) { // block.blockhash - hash of the given block - only works for 256 most recent blocks excluding current bytes32 blockHash = block.blockhash(playerblock+BlockDelay); if (blockHash==0) { ErrorLog(msg.sender, "Cannot generate random number"); wheelResult = 200; } else { bytes32 shaPlayer = sha3(player, blockHash);
wheelResult = uint8(uint256(shaPlayer)%37); } }
Its arguments are the player's address and the block number in which the bet was placed. The first thing the function gets is a block hash, which is separated from the bet block for BlockDelay blocks into the future. This is an important point because if a player can somehow learn the hash of this block in advance, he can form a bet that is guaranteed to win. If you remember that there are Uncle blocks in Ethereum, there may be a problem and further analysis is required. Next, SHA-3 is calculated from the gluing of the player's address and the received block hash. The dropped number is calculated by taking the remainder of dividing the result of SHA-3 by 37. From my point of view, the algorithm is quite honest and the casino has no precedence over the player.Why does the casino bring profit to its owners?There are 37 cells on the field. For example, a player wants to make 100 000 bets on one particular field. Probably, about 2703 times the player will win, and all the other times he will lose. In this case, from the casino winnings, the player will receive 2703 * 36 = 97,308 RLT tokens. And 2692 RLT tokens, spent on bets, will go to the casino. Similar calculations can be made for all other types of bets. It is also interesting to see, how the win is calculated. The getGameResult() function does this: function getGameResult(GameInfo memory game) private constant returns (uint256 totalWin) { totalWin = 0; uint8 nPlayerBetNo = 0; // we sent count bets at last byte uint8 betsCount = uint8(bytes32(game.bets)[0]); for(uint8 i=0; i<maxTypeBets; i++) { if (isBitSet(game.bets, i)) { var winMul = winMatrix.getCoeff(getIndex(i, game.wheelResult)); // get win coef if (winMul > 0) winMul++; // + return player bet totalWin += winMul * getBetValueByGamble(game, nPlayerBetNo+1,i); nPlayerBetNo++;
if (betsCount == 1) break; betsCount--; } } }
Parameter here is the structure of GameInfo with data about the calculated bet. And its wheelResult field is already filled with a random number. Here is the cycle for all types of bets, in which the bit mask game.bets is checked and if the bit of the type being checked is installed, winMatrix.getCoeff() is requested. winMatrix is the contract with the address 0x073D6621E9150bFf9d1D450caAd3c790b6F071F2, loaded in the SmartRoulettee() constructor. A parameter of this function is a combination of the bet type and the random number: // unique combination of bet and wheelResult, used for access to WinMatrix function getIndex(uint16 bet, uint16 wheelResult) private constant returns (uint16) { return (bet+1)*256 + (wheelResult+1); } Have you played the Blockchain roulette by SmartPlay.tech? Don’t forget to share your opinion with other players, join the Telegram chat with the early project investors and ask you questions to the official representatives.Aaaand I did not doubt that this roulette is fair, nice job guys. Hope the project will succeed, since I look forward to a new game to play ![Grin](https://bitcointalk.org/Smileys/default/grin.gif) How long will it take to implement all the changes you've talked about? Exact launch dates will be known in the nearest time. At the moment, the mechanics of the new game are completely ready, the game design is being drawn. The unique design is now integrated into the updated version of the site.
|
|
|
|
oleksii777
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 12, 2018, 03:24:23 PM |
|
Hello everybody. I want to say thank you to the developers for the fact that you are promoting the coin. This is a huge plus. I hope that the price will soon be over $ 1. Good luck to all.
|
|
|
|
SmartPlay.tech (OP)
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 13, 2018, 01:21:41 PM |
|
Hello everybody. I want to say thank you to the developers for the fact that you are promoting the coin. This is a huge plus. I hope that the price will soon be over $ 1. Good luck to all.
Thank you for your support! Now are working hard to achieve more results and develop the project, but we'll back with great news soon ![Smiley](https://bitcointalk.org/Smileys/default/smiley.gif)
|
|
|
|
SmartPlay.tech (OP)
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 14, 2018, 01:01:46 PM |
|
🚀New stylish design of the main website🚀
The SmartPlay.tech team is glad to present the new stylish design of the main website.
![](https://ip.bitcointalk.org/?u=https%3A%2F%2Fi.imgur.com%2Fd7BMavl.png&t=665&c=mDMkffaIBhS6lQ) Do not forget to share your opinion with other project participants and leave your feedback on this thread or in the Telegram group.
|
|
|
|
retinger
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 14, 2018, 01:11:19 PM |
|
🚀New stylish design of the main website🚀
The SmartPlay.tech team is glad to present the new stylish design of the main website.
![](https://ip.bitcointalk.org/?u=https%3A%2F%2Fi.imgur.com%2Fd7BMavl.png&t=665&c=mDMkffaIBhS6lQ) Do not forget to share your opinion with other project participants and leave your feedback on this thread or in the Telegram group.
I like that design so much! Hope we'll be able to test it soon ![Grin](https://bitcointalk.org/Smileys/default/grin.gif)
|
|
|
|
SmartPlay.tech (OP)
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 14, 2018, 02:18:10 PM |
|
🚀New stylish design of the main website🚀
The SmartPlay.tech team is glad to present the new stylish design of the main website.
![](https://ip.bitcointalk.org/?u=https%3A%2F%2Fi.imgur.com%2Fd7BMavl.png&t=665&c=mDMkffaIBhS6lQ) Do not forget to share your opinion with other project participants and leave your feedback on this thread or in the Telegram group.
I like that design so much! Hope we'll be able to test it soon ![Grin](https://bitcointalk.org/Smileys/default/grin.gif) Thank you! Our designers are working hard on both the game design and the site design. The launch date will be announced in the nearest time.
|
|
|
|
Nickie001
Jr. Member
Offline
Activity: 76
Merit: 1
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 15, 2018, 12:02:34 PM |
|
🚀New stylish design of the main website🚀
The SmartPlay.tech team is glad to present the new stylish design of the main website.
![](https://ip.bitcointalk.org/?u=https%3A%2F%2Fi.imgur.com%2Fd7BMavl.png&t=665&c=mDMkffaIBhS6lQ) Do not forget to share your opinion with other project participants and leave your feedback on this thread or in the Telegram group.
I think this design is absolutely amazing! When will the main site with the new design be launched?
|
|
|
|
SmartPlay.tech (OP)
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 15, 2018, 01:28:45 PM |
|
🚀New stylish design of the main website🚀
The SmartPlay.tech team is glad to present the new stylish design of the main website.
![](https://ip.bitcointalk.org/?u=https%3A%2F%2Fi.imgur.com%2Fd7BMavl.png&t=665&c=mDMkffaIBhS6lQ) Do not forget to share your opinion with other project participants and leave your feedback on this thread or in the Telegram group.
I think this design is absolutely amazing! When will the main site with the new design be launched? The devteam is doing their best to launch the new version of the site before the end of the month.
|
|
|
|
begau
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 17, 2018, 04:00:46 PM |
|
🚀New stylish design of the main website🚀
The SmartPlay.tech team is glad to present the new stylish design of the main website.
![](https://ip.bitcointalk.org/?u=https%3A%2F%2Fi.imgur.com%2Fd7BMavl.png&t=665&c=mDMkffaIBhS6lQ) Do not forget to share your opinion with other project participants and leave your feedback on this thread or in the Telegram group.
I think this design is absolutely amazing! When will the main site with the new design be launched? The devteam is doing their best to launch the new version of the site before the end of the month. We will expect a stronger return of RLT next month. I see the new stylish design of the main website is very attractive. It will be an important highlight of this coin.
|
|
|
|
Nickie001
Jr. Member
Offline
Activity: 76
Merit: 1
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 20, 2018, 12:37:15 PM |
|
🚀New stylish design of the main website🚀
The SmartPlay.tech team is glad to present the new stylish design of the main website.
![](https://ip.bitcointalk.org/?u=https%3A%2F%2Fi.imgur.com%2Fd7BMavl.png&t=665&c=mDMkffaIBhS6lQ) Do not forget to share your opinion with other project participants and leave your feedback on this thread or in the Telegram group.
I think this design is absolutely amazing! When will the main site with the new design be launched? The devteam is doing their best to launch the new version of the site before the end of the month. We will expect a stronger return of RLT next month. I see the new stylish design of the main website is very attractive. It will be an important highlight of this coin. Absolutely. The design is amazing. Hope the new game will also have the great design ![Cheesy](https://bitcointalk.org/Smileys/default/cheesy.gif)
|
|
|
|
mantoni
Newbie
Offline
Activity: 79
Merit: 0
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
March 21, 2018, 01:48:10 PM |
|
Does anybody know what is the new game? One more roulette? Blackjack? Or something else?
|
|
|
|
|