Check the integrity of the game of roulette on smart Ethereum contract



Few people today have not heard of cryptocurrencies, in particular Bitcoin. In 2014-m to year, on the wave of interest in bitcoin, a new cryptocurrency — Ethereum. Today, in 2017, she's is the second capitalization after bitcoin. One of the major differences between bitcoin and is using a Turing-complete virtual machine EVM. Read more about broadcast you can read it Yellow Paper.

Smart contracts Ethereum is usually written in the language of the Solidity. On Habre already there were articles about writing and testing of smart contracts, for example 1, 2, 3. And about the relationship of the smart contract with the site, you can read, for example, article about creating a simple poll on smart contract. This article uses the built-in wallet Mist the browser, but the same can be done using a plugin for Chrome, such as MetaMask. So, through MetaMask, and a running game which we will investigate.

The game is an implementation implementation, European roulette, there are 37 cells, numbered from 0 to 36. You can bet on a specific number or on set of numbers: odd/even, red/black, 1-12, 1-18, etc. In each round, you can make multiple bets by adding a token (a value of 0.01 ETH ≈ $0.5) on the corresponding field of the table. Each field corresponds to the odds. For example, the rate of "red" corresponds to a factor of 2 — that is, paying 0.01 ETH you, if you win, you get 0.02 ETH. And if you put on zero, the ratio will be 36: paying the same ETH 0.01 per bet you will receive 0.36 in case of winning.

Developers, however, use other designations: 35:1
code contract ratio for this bet is also listed as 35, and the win amount, before payment, plus the amount of the bet. Maybe in the gaming world it is accepted that designation, but to me it is more logical just to use 36.

When all bets are made the player presses the "Play" button and after MetaMask, sends pari* in Ethereum-a blockchain on address smart contract game. The contract defines the given number, calculates the settlement and, if necessary, sends the winnings to the player.
* — I will use the term pari to denote the set of bets (i.e., pairs of the type of bet — number of coins for the bet) that the player makes in one round. If you know a more correct term, please contact me, I will correct.

In order to understand honestly if the game works (i.e., does not manipulate or casino the definition of the drawn numbers in their favor) will analyze the work of the smart contract.

His address is listed on the website. In addition, before you confirm payment, you can verify what address will be sent to the bet. I will review the contract at 0xDfC328c19C8De45ac0117f836646378c10e0cda3. Etherscan shows code, and for easy viewing, you can use Solidity Browser.

The work of the contract begins with the function call placeBet():

bed Sheet code
function placeBet(bets uint256, bytes32 values1,values2 bytes32) 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;
}

Memory g GameInfo = 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, "the contract is not in a games list");
else if (errorCodeEmission == 3) 
ErrorLog(msg.sender, "player incorect address");
else if (errorCodeEmission == 4) 
ErrorLog(msg.sender, "incorect value bet");
else if (errorCodeEmission == 5) 
ErrorLog(msg.sender "incorect emissions Coefficient");

if (msg.sender.send(msg.value) == false) throw;
return;
}

gambles.push(g);

PlayerBet(gamblesLength, playerTokens); 
}

For newcomers to the Solidity will explain the modifiers public, and payable means that the function is part of the API contract, and that when it is called to send the broadcast. The information about the sender and the number of sent broadcast will be available through the variable msg.

A call options is a bitmask of types of bets and two 32-byte array with the number of tokens for each type. To guess about this by looking at the definition of the type GameInfo and the functions getBetValueByGamble(), getBetValue().

Another sheet the code less
struct GameInfo
{
address player;
uint256 blockNumber;
uint8 wheelResult;
uint256 bets;
bytes32 values;
bytes32 values2;
}

the
// n - number player bet
// nBit - betIndex
getBetValueByGamble function(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 unique maximum 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; 
}

Note that getBetValue() returns the bet amount is not in tokens, and in wei.

The first thing placeBet() verifies that the contract is not muted, and then the testing will begin bet.
An array of gambles is a repository of all the played in this contract bet. placeBet() finds all the bet in your unit and checks are not sent if the player other bet in this unit and not exceeded the permitted number on the betting block. Then, it evaluates the minimum and maximum bet amount.

In case of any error, the execution of the contract is interrupted by a command throw, which rolls back the transaction, returning the air to the player.

Then passed to the function parameters are stored in the structure GameInfo.Here it is important that the field wheelResult is initialized to the number of 37.

After another test that the sum of the rates coincides with the sent number of broadcast is the distribution of tokens RLT, processed, referral program, information on bet is stored in gambles and creates the event PlayerBet number and the amount of the wager, which is then visible in the web part of the game.

About tokens
With each bet, the player is given a certain amount of RLT, Ethereum tokens, which determine the right of the owner of the token on the receipt of dividends from profits earned by the authors of the game. Read more about this read White Paper.

The future life of betting begins with the function call ProcessGames(), which, after the appearance of the new rates, is performed currently, with addresses 0xa92d36dc1ca4f505f1886503a0626c4aa8106497. These calls are visible in the view list of transaction contract games: they have a Value = 0.
{ 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; }
the parameters to the call is passed an array with numbers bet, which require a calculation, and each is called ProcessGame.

the
function ProcessGame(index uint256, uint256 delay) private returns (GameStatus)
{ 
Memory GameInfo 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 room rates and the number of blocks that must elapse between rate and its processing. When called from ProcessGames() or ProcessGameExt() this parameter currently set to 1, this value can be obtained from the result of calling getSettings().

In the case that the number of the block in which processing occurs, more than 255 blocks separated from the block bet, it can't be processed: the block hash is only available for the last 256 blocks, and it is needed to determine the number.

Next, check is performed whether the already calculated result of the game (remember, wheelResult was inicializalasa number 37, which fall not?) and if it passed already the required number of blocks.

If the conditions are met, is called getRandomNumber() to determine the number, call getGameResult() to calculate the winning amount. If it is not zero, the broadcast is sent to the player: g.player.send(playerWinnings). Then an event is generated EndGame, which can be read from the web part of the game.

View the most interesting thing is how is the winning number: the function getRandomNumber().

the
function getRandomNumber(address player, uint256 playerblock) private returns(uint8 wheelResult)
{
// block.blockhash - hash of the given block - only works for the 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);
} 
}

Her arguments address the player and block number in which the bet was made. First, the function gets the hash of the block, separated from the block bet on BlockDelay blocks in the future.

This is important, because if the player can somehow find out the hash of this block in advance, it could generate a bet that is guaranteed to win. If you remember that Uncle Ethereum, there are blocks, there may be a problem and further investigation is needed.

Next, the calculated SHA-3 from gluing the addresses of the player and the received hash. The winning number is calculated by taking the remainder modulo SHA-3 37.

From my point of view, the algorithm is quite honest and the casino any advantage the player has.
Why do casinos to bring the owners profit?
field 37 cells. Suppose I want to make 100 000 bets one chip on one specific field.
Probably about 2703 times I win and other times lose. With the winnings from the casino I get 2703*36 = 97 308 tokens. 2692 and tokens I spent on the bets will remain in the casino.
Similar calculations can be made for all remaining types of bets.


Interesting to see also how to calculate the winnings. As we have seen, it makes the getGameResult().

the
function getGameResult(GameInfo game) private constant returns (uint256 totalWin) 
{
totalWin = 0;
uint8 nPlayerBetNo = 0;
// we sent bets at last count byte 
betsCount uint8 = 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 coef win
if (winMul > 0) winMul++; // + return player bet
totalWin += winMul * getBetValueByGamble(game, nPlayerBetNo+1,i);
nPlayerBetNo++; 

if (betsCount == 1) break;
betsCount--;

} 
}

The setting here is passed a structure of GameInfo with the data on the calculated bet. And her field is already filled wheelResult number drawn.

See the loop on all bet types, which is checked bitmask game.bets and check if the bit type is selected, it is queried winMatrix.getCoeff(). winMatrix is the contract at 0x073D6621E9150bFf9d1D450caAd3c790b6f071f2loaded in the constructor SmartRoulettee().

As a parameter to this function is passed a combination of bet type and the number:

the
// unique combination of bet and wheelResult, used for access to WinMatrix
function getIndex(bet uint16, uint16 wheelResult) private constant returns (uint16)
{
return (bet+1)*256 + (wheelResult+1);
}

Parsing of the code contract for the WinMatrix I'll leave to you as homework, but nothing unexpected there: generates a matrix of coefficients, and when you call getCoeff() returns needed. If desired, it is easy to check call this function manually contract page.
Article based on information from habrahabr.ru

Комментарии