区块链研究实验室-基于以太坊猜数字游戏并在IPFS上部署(中)

智能合约编程

在本章中,将从零开始讲述智能合约编写,并在正式部署前调试成功。

首先,在contracts目录下添加文件:Casino.sol。

在文件头添加Solidity版本声明和程序框架:

pragma solidity ^0.4.20;

contract Casino {//只有两种类型:contract和library。类名必须要与文件名完全一致

addresspublicowner;//定义owner变量

functionCasino()public{

owner = msg.sender;//构造函数只在智能合约部署时一次性运行,将运行该合约的账户设为合约所有人owner

}

functionkill()public{

if(msg.sender == owner) selfdestruct(owner);//如果是合约所有人发送的kill命令,则将此合约销毁。合约销毁后,该合约中的所有代币将自动转入Owner账户。此操作只在被黑客攻击导致无法挽回时使用,但是建议每个合约都需要部署该方法。

}

}

结合该项目的目的,我们需要考虑以下事情:

记录有多少用户已经下注,以及每个用户下注的数字

每注的最小下注金额(ETH)

总的下注金额(ETH)

一个记录总下注数的变量

合适结束下注,并开奖

将奖金扣除费用后,自动发放给每位中奖者

如果没有中奖者,则将奖金平均后返还每位参与者

下面我们将逐一讲述:

首先,创建一个玩家的struct数据类型,然后通过mapping类型定义玩家数组:

structPlayer{

uint256 amountBet;//下注金额

uint256 numberSelected;//选择的数字

}

mapping(address => Player)publicplayerInfo;//实例化playerInfo

以后,可以使用playerInfo[用户的以太坊地址].amountBet来获取或设置该用户的下注金额。

然后,定义一些公共变量:

uint256publicminimumBet =100finney;//最小下注金额,0.1ETH

uint256publictotalBet;//总下注金额

uint256publicnumberOfBets;//已下注人数

uint256publicmaxAmountOfBets =100;//最多下注人数

address[]publicplayers;//玩家数组

这里再加两种常用的数据类型:byte32和string,这两种都是字符串,需要加双引号。

我们修改一下之前的构造函数,让初始化合约时,传入一个最小下注金额的常量:

functionCasino(uint256 _minimumBet){

owner = msg.sender;

if(_minimumBet >0) minimumBet = _minimumBet;

}

注意:在Solidity中,代币的单位统一为wei,不存在小数。一个ETH=1000...0 wei(一共有18个0),建议使用换算计算器

接下去,添加以下代码:

functionbet(uint256 numberSelected)publicpayable{

require(!checkPlayerExists(msg.sender));//判断发送指令的人是否已在玩家列表中登记

require(numberSelected >=1&& numberSelected <=10);//判断选择的数字范围

require(msg.value >= minimumBet);//判断玩家下注的金额是否满足最低下注额

playerInfo[msg.sender].amountBet = msg.value;//记录下注金额到数组

playerInfo[msg.sender].numberSelected = numberSelected;//记录下注的数字到数组

numberOfBets++;//总下注次数+1

players.push(msg.sender);//把用户帐号压入player数组

totalBet += msg.value;//总下注金额增加

if(numberOfBets >= maxAmountOfBets) generateNumberWinner();//判断下注的人次是否超过了100个

}

该代码是用户下注时执行,其中:

msg.sender 是发送者帐号,msg.value 是发送者金额。

payable 是一个modifier,函数修改器,用在智能合约中进行一些函数功能行为的修改,例如对函数执行前置条件的自动检查,有点像条件函数,或者事件触发器。

在这里,所有涉及支付的函数,都需要设定为payable,如果没有该modifier函数,打到合约的代币会被自动退回。

常见的modifier函数还有onlyOwner,表示只有owner才能执行该函数,代码如下:

modifier onlyOwner {

require(msg.sender == owner) _;

}

require()函数用来判断括号中的条件,如果是True,则继续,否则,将用户的代币返回给用户账户,函数结束。与0.4.10版本前Solidity使用if...throw一样,但代码更整洁。类似的还有assert(),只是assert()对于错误的操作会扣除Gas,require()不会。

checkPlayerExists函数用来判断用户是否存在,代码如下:

functioncheckPlayerExists(address player)publicconstantreturns(bool){

for(uint256 i =0; i < players.length; i++){

if(players[i] == player)returntrue;

}

returnfalse;

}

以上函数中的返回值类型为constant,是指直接返回某个值,而无需改变账户状态,这个操作无需消耗Gas。

一般constant会和returns()返回值定义,一起使用。

上面代码的最后一句,判断下注人数如果超过100,则执行开奖函数generateNumberWinner:

functiongenerateNumberWinner()public{

uint256 numberGenerated = block.number %10+1;

distributePrizes(numberGenerated);//执行奖金分发函数

}

这里使用了不安全的随机数获取方法,即当前块的高度block.number,并取其个位数加1,该方法只能用于教学,不能用于实际使用。因为很容易被猜到。

随机数产生是区块链智能合约编程中的一个重点和难点,建议可以使用第三方Oracle随机数种子,如:Oraclize,ChainLink, ZAP

实现奖金分发函数

functiondistributePrizes(uint256 numberWinner)public{

address[100] memory winners;//保存获胜者的临时数组,memory类型的数据在函数执行完毕后释放,这类临时变量必须是固定长度,本例中定义长度为100,即最多全部猜中情况下,获胜者不会超过100名

uint256 hasWiner =0;//用来判断是否有赢家

uint256 count =0;// 因为winners数组指定长度,所以无法通过length获取获奖人数,只能设这个变量

for(uint256 i =0; i < players.length; i++) {

address playerAddress = players[i];

if(playerInfo[playerAddress].numberSelected == numberWinner) {

//如果玩家选的数字等于随机生成的获奖数字,则将获奖者压入到数组

winners[count] = playerAddress;

count++;

}

deleteplayerInfo[playerAddress];// 无论是否获奖,都将释放玩家数据,不做保存

}

if(count ==0) {

//如果没有人猜中

count = maxAmountOfBets;

hasWiner =0;

}else{

hasWiner =1;

}

uint256 winnerEtherAmount = totalBet *95/100/ count;// 系统抽取5%

for(uint256 j =0; j < count; j++) {

if(hasWiner ==1) {

//如果有人猜中,则分给猜中的人

if(winners[j] != address(0)) winners[j].transfer(winnerEtherAmount);

}else{

//如果没有人猜中,则分给所有竞猜的人

if(players[j] != address(0)) players[j].transfer(winnerEtherAmount);

}

}

players.length =0;// Delete all the players array

totalBet =0;

numberOfBets =0;

}

到此为止,合约部分全部完成,接下去通过RemixIDE工具进行调试。调试前,需要在浏览器中装好Metamask钱包软件

1- 准备测试账户

在Metamask钱包的Rospten测试网络中新建一个账户,在浏览器中打开以太坊水龙头工具https://faucet.metamask.io/,点击绿色按钮,就可以免费获得测试用的以太币。

区块链研究实验室-基于以太坊猜数字游戏并在IPFS上部署(中)_第1张图片

2- 配置Rimix环境

打开Remix网站,将以上代码复制到Remix调试工具中,并刷新Remix网页。

点击右上角Run标签,注意在Environment一栏中,选择:Injected Web Rospten。然后会在Account一栏中看到Metamask钱包中的账户名,如下图:

区块链研究实验室-基于以太坊猜数字游戏并在IPFS上部署(中)_第2张图片

3- 编译智能合约

点击右上角Compile标签,点击Start to Comile开始编译。如果一切正常,编译通过。

4- 部署智能合约

回到Run标签,点击Deploy按钮,开始部署合约。正常情况,会跳出Metamask钱包,输入Gas费用(注意,千万不要是0),点击Submit,在console窗口中会提示transaction的区块链链接地址,过一会,会提示部署成功的信息,然后点击Remix监控窗口提示的链接,点击打开浏览器,查看部署合约的交易,并获取合约地址。

注意:请务必将合约地址复制到记事本。

至此合约部署完毕。

在部署过程中,经常会提示错误,不要强行发送transaction,仔细查看代码,每个很小的bug,都可能会让程序无法正常运行,并提示报错。

每次修改bug,都需要重新部署合约,一个成熟的合约,要不厌其烦的反反复复检查代码、优化代码。

5- 使用合约

将复制的智能合约地址,复制到at Address文本框中,点击按钮调用该合约。

使用每个public函数,以及所有public变量。

其他:

使用javascript部署合约

在migrations目录下,新建一个文件2_deploy_contracts.js,内容如下:

varCasino = artifacts.require("./Casino.sol");

module.exports =function(deployer){

//前面两个参数分别对应合约构造函数中的两个参数,gas为部署合约时的Gas费用

deployer.deploy(web3.toWei(0.1,'ether'),100, {gas:3000000});

};

使用truffle部署智能合约

除了Remix调试工具之外,Truffle也有一套部署工具。运行truffle compile编译源码,编译成功后,在build/contracts/目录下,会生成Casino.json文件。

你可能感兴趣的:(区块链研究实验室-基于以太坊猜数字游戏并在IPFS上部署(中))