# 如何做智能合约审计？

，未经允许拒绝转载。

pragma solidity ^0.4.11;
import “github.com/oraclize/ethereum-api/oraclizeAPI.sol”;

/// @title Contract to bet Ether for a number and win randomly when the number of bets is met.
/// @author Merunas Grincalaitis
contract Casino is usingOraclize {

// The minimum bet a user has to make to participate in the game
uint public minimumBet = 100 finney; // Equal to 0.1 ether

// The total amount of Ether bet for this current game
uint public totalBet;

// The total number of bets the users have made
uint public numberOfBets;

// The maximum amount of bets can be made for each game
uint public maxAmountOfBets = 10;

// The max amount of bets that cannot be exceeded to avoid excessive gas consumption
// when distributing the prizes and restarting the game
uint public constant LIMIT_AMOUNT_BETS = 100;

// The number that won the last game
uint public numberWinner;

// Array of players

// Each number has an array of players. Associate each number with a bunch of players

// The number that each player has bet for

// Modifier to only allow the execution of functions when the bets are completed
modifier onEndGame(){

if(numberOfBets >= maxAmountOfBets) _;

}

/// @notice Constructor that’s used to configure the minimum bet per game and the max amount of bets
/// @param _minimumBet The minimum bet that each user has to make in order to participate in the game
/// @param _maxAmountOfBets The max amount of bets that are required for each game
function Casino(uint _minimumBet, uint _maxAmountOfBets){

owner = msg.sender;

if(_minimumBet > 0) minimumBet = _minimumBet;
if(_maxAmountOfBets > 0 && _maxAmountOfBets <= LIMIT_AMOUNT_BETS)
maxAmountOfBets = _maxAmountOfBets;

// Set the proof of oraclize in order to make secure random number generations
oraclize_setProof(proofType_Ledger);

}

/// @notice Check if a player exists in the current game
/// @param player The address of the player to check
/// @return bool Returns true is it exists or false if it doesn’t

if(playerBetsNumber[player] > 0)
return true;
else
return false;

}

/// @notice To bet for a number by sending Ether
/// @param numberToBet The number that the player wants to bet for. Must be between 1 and 10 both inclusive
function bet(uint numberToBet) payable{

// Check that the max amount of bets hasn't been met yet
assert(numberOfBets = 1 && numberToBet = minimumBet);

// Set the number bet for that player
playerBetsNumber[msg.sender] = numberToBet;

// The player msg.sender has bet for that number
numberBetPlayers[numberToBet].push(msg.sender);

numberOfBets += 1;
totalBet += msg.value;

if(numberOfBets >= maxAmountOfBets) generateNumberWinner();

}

/// @notice Generates a random number between 1 and 10 both inclusive.
/// Must be payable because oraclize needs gas to generate a random number.
/// Can only be executed when the game ends.
function generateNumberWinner() payable onEndGame {

uint numberRandomBytes = 7;
uint delay = 0;
uint callbackGas = 200000;

bytes32 queryId = oraclize_newRandomDSQuery(delay, numberRandomBytes, callbackGas);

}

/// @notice Callback function that gets called by oraclize when the random number is generated
/// @param _queryId The query id that was generated to proofVerify
/// @param _result String that contains the number generated
/// @param _proof A string with a proof code to verify the authenticity of the number generation
function __callback(

bytes32 _queryId,
string _result,
bytes _proof

) oraclize_randomDS_proofVerify(_queryId, _result, _proof) onEndGame {

// Checks that the sender of this callback was in fact oraclize

numberWinner = (uint(sha3(_result))%10+1);
distributePrizes();

}

/// @notice Sends the corresponding Ether to each winner then deletes all the
/// players for the next game and resets the totalBet
and numberOfBets

function distributePrizes() onEndGame {
uint winnerEtherAmount = totalBet / numberBetPlayers[numberWinner].length; // How much each winner gets

// Loop through all the winners to send the corresponding prize for each one
for(uint i = 0; i < numberBetPlayers[numberWinner].length; i++){
numberBetPlayers[numberWinner][i].transfer(winnerEtherAmount);
}

// Delete all the players for each number
for(uint j = 1; j <= 10; j++){
numberBetPlayers[j].length = 0;
}

totalBet = 0;
numberOfBets = 0;

}
}

1、免责声明

2、概述

】，译者注）进行编写，这可以帮助我们快速地理解程序是如何工作。

Oraclize是一种为智能合约和区块链应用提供数据的独立服务，官网：【 http://www.oraclize.it
】。因为类似于比特币脚本或者以太坊智能合约这样的区块链应用无法直接获取链外的数据，所以就需要一种可以提供链外数据并可以与区块链进行数据交互的服务。Oraclize可以提供类似于资产/财务应用程序中的价格信息、可用于点对点保险的天气信息或者对赌合约所需要的随机数信息。

3、对合约进行的攻击

totalBet += msg.value;

import ‘./SafeMath.sol’;
contract Casino {

using SafeMath for uint256;
function example(uint256 _value) {
}

}

】，从Geth的1.5.3版本和Parity的1.4.4版本开始，已经增加了对这个攻击的防护。

EIP，即Ethereum Improvement Proposal（以太坊改进建议），官方地址【 https://github.com/ethereum/EIPs
】是由以太坊社区所共同维护的以太坊平台标准规范文档，涵盖了基础协议规格说明、客户端API以及合约标准规范等等内容。

】来获得更多关于这种攻击的信息。

4、合约中发现的严重漏洞

5、合约中发现的中等漏洞
checkPlayerExists() 应该是一个常态（constant）函数，然而实际上它并不是。因此这增加了调用这个函数的gas消耗，当有大量对此函数的调用发生时会产生很大的问题。

Solidity语言中的常态（constant）函数，指的是在运行时不会改变合约状态的函数，也就是不会改变合约级别的状态变量（state variable）的值的函数。因为状态变量的更改是会保存到链上的，所以对状态变量的更改都要消耗gas（来支付给矿工），这是非常昂贵的。在本例中，因为 checkPlayerExists() 函数中访问了状态变量 playerBetsNumber 来判断是否已经有人下过注了，虽然这是个合约级别的变量，但这个函数并没有改变它的值，所以这个函数应该声明为 constant 以节省其对gas的消耗。

6、低严重性的漏洞

assert() 和 require() 大体上是相同的，但assert函数一般用来在更改合约状态之后做校验，而require通常在函数的开头用做输入参数的检查。

7、逐行评注

8、审计总结