本文旨在引导对 DApp 开发感兴趣的开发者,构建一个基于以太坊去中心化应用,经过开发一款功能完备的竞猜游戏,迈出 DApp 开发的第一步,经过实例讲解 Solidity 语言的经常使用语法,以及前端如何与智能合约进行交互。javascript
若是正在阅读的你,从未接触过 DApp 开发也没关系,能够先阅读【区块链上编程:DApp开发简介】进行前置知识补充。前端
随着加密猫、FOMO3D 等游戏的火爆,去中心化应用在游戏领域遍地开花,下面就带着你们一块儿开发一款简单有趣的 DApp 游戏,帮助你们熟悉 DApp 开发。本 DApp 实现的合约功能:用户从 0-6 的数字中,任意组合三位数进行投注,合约计算出 3 位随机数,根据随机数的组合规则分别给予用户不一样倍数的奖励,如随机数为 AAA ,A 等于 6 则奖励至少 20 倍投注金额,即奖池全部余额;A 不等于 6 则奖励 5 倍投注金额;随机数为 AAB,则奖励 2 倍投注金额;随机数为 ABC 则不奖励,同时用户可查看奖池余额和我的投注记录。java
能够看出合约须要实现用户投注、生成随机数、发放奖励、奖池余额查询的功能,接下来编写咱们的合约代码。git
新建Lottery.sol
合约文件,声明合约版本,^
表示合约编译版本为 0.4.0 至 0.5.0(不含 0.5.0)。程序员
pragma solidity ^0.4.0;
复制代码
定义合约基本内容,同时声明最低投注金额。github
contract Lottery {
uint public betMoney = 10 finney;
}
复制代码
生成随机数,经过区块难度block.difficulty
和内置函数keccak256
生成随机数,在EVM
中经常使用的数据存储位置:memory
、storage
,函数的参数、返回值默认存储在memory
中,状态变量默认存储在storage
中,咱们能够经过声明memory
、storage
改变默认存储位置,二者的存储都须要消耗gas
,但storage
的开销远大于memory
。web
contract Lottery {
...
function generateRandomNumber() private view returns(uint[]) {
uint[] memory dices = new uint[](3);
for (uint i = 0; i < 3; i++) {
dices[i] = uint(keccak256(abi.encodePacked(block.difficulty, now, i))) % 6 + 1;
}
return dices;
}
...
}
复制代码
获取合约余额,address
类型比较重要的成员属性主要有balance
、transfer
,分别为获取地址余额、转移eth
至该地址,默认eth
单位是wei
。编程
contract Lottery {
...
function getBankBalance() public view returns(uint) {
return address(this).balance;
}
...
}
复制代码
用户投注,投注方法须要使用payable
关键字描述,用来表示能够接收eth
;经过msg.sender
和msg.value
得到交易发送者地址和当前交易附带的eth
。一般使用require
来校验外部输入参数,当断定条件为false
时,则会将剩余的gas
退回,同时回滚交易;assert
则用来处理合约内部的逻辑错误,当错误发生时会消耗掉全部gas
,同时回滚交易。浏览器
contract Lottery {
...
function bet() public payable {
uint amount = msg.value;
require(amount >= betMoney, 'bet money not less than 10 finney');
require(address(this).balance >= amount * 20, 'contract money not enough to pay reward');
uint[] memory dices = generateRandomNumber();
require(dices.length == 3, 'dices illegal');
address addr = msg.sender;
bool isReward = false;
uint reward = 0;
if ((dices[0] == dices[1]) && (dices[1] == dices[2]) && (dices[0] == 6)) {
isReward = true;
reward = address(this).balance;
} else if ((dices[0] == dices[1]) && (dices[1] == dices[2]) && (dices[0] != 6)) {
isReward = true;
reward = amount * 5;
} else if ((dices[0] == dices[1]) || (dices[0] == dices[2]) || (dices[1] == dices[2])) {
isReward = true;
reward = amount * 2;
}
if (isReward) {
addr.transfer(reward);
}
}
...
复制代码
定义事件,经过合约内部触发事件,web3 监听到事件回调进行相应逻辑处理,从而进行页面 UI 更新;同时前端也能够经过对应事件名称获取日志信息。安全
contract Lottery {
...
event BetList(
address addr,
uint amount,
bool isReward,
uint reward,
uint[] dices
);
function bet() public payable {
...
emit BetList(addr, amount, isReward, reward, dices);
}
...
复制代码
至此,咱们已经写完了合约代码,前端页面实现就不在此赘述,主要介绍如何使用 web3 与合约交互,这里使用到的 web3 版本是 1.0,web3 1.0 和 0.2x.x 的 API 调用方式差异较大,1.0 的 API 支持异步调用。
安装 Metamask 浏览器插件后,会在浏览器页面内注入一个 web3 实例。检测页面中是否存在 web3 实例,若是不存在则链接本身的实例。
import Web3 from 'web3';
if (typeof web3 !== 'undefined') {
web3 = new Web3(web3.currentProvider);
} else {
web3 = new Web3(new Web3.providers.HttpProvider(NODE_NRL));
}
复制代码
传入合约 ABI,合约地址,实例化合约对象。
this.contract = new web3.eth.Contract(
CONTRACT_ABI,
CONTRACT_ADDR,
);
复制代码
调用合约中的投注方法,经过try catch
能够捕获到 Metamask 弹窗取消交易操做。
userBet = async () => {
try {
await this.contract.methods
.bet(
...
)
.send({
from: ACCOUNT,
value: MONEY,
});
} catch (error) {
...
}
}
复制代码
查询记录的日志,能够经过指定事件名称、区块高度及过滤条件来进行日志查询,值得注意的是,在合约内不能查询到日志信息。
queryEvent = async () => {
const event = await this.contract.getPastEvents(
EVENT_NAME,
{
filter: {},
fromBlock: 0,
toBlock: 'latest',
}
)
}
复制代码
好比修改用户投注金额及充值这类敏感操做,就须要管理员的权限来进行操做。一样地,咱们也能够拓展赞助商的功能,经过充值奖池的累计金额排名来展现赞助商的广告,这里就不作展开了。
定义修饰器,在构造函数里设置管理员地址,将建立合约的帐户设置为管理员。
contract Lottery {
...
address public owner;
modifier onlyOwner() {
require(owner == msg.sender);
_;
}
constructor() public {
owner = msg.sender;
}
...
}
复制代码
实现修改投注金额的功能,仅管理员帐户可触发。
contract Lottery {
...
function setBetMoney(uint _betMoney) public onlyOwner {
betMoney = _betMoney;
}
function deposit() public payable onlyOwner {}
...
}
复制代码
当前随机数的实现经过链上信息生成,这种生成随机数的方式容易受到不诚实的节点攻击。下一篇文章将经过多个实例介绍相应的第三方工具库的使用(Oricalize、SafeMath、OpenZepplin),生成安全的随机数。
前置文章:
文 / ielapp
一个简单的程序员
编 / 荧声
本文已由做者受权发布,版权属于创宇前端。欢迎注明出处转载本文。本文连接:knownsec-fed.com/2018-10-07-…
想要订阅更多来自知道创宇开发一线的分享,请搜索关注咱们的微信公众号:创宇前端(KnownsecFED)。欢迎留言讨论,咱们会尽量回复。
感谢您的阅读。