专栏名称: 区块链技术学习
致力于区块链技术的学习和普及,对区块链技术和相关企业事件进行深度分析和研判,探索去中心化账本技术应用领域。
目录
相关文章推荐
芋道源码  ·  2月,Java岗又爆了?! ·  昨天  
每日豆瓣  ·  网上精致生活看多了,来看看普通人生活吧 ·  3 天前  
每日豆瓣  ·  童年友谊让母亲有了上学的机会 ·  2 天前  
编程茶座  ·  这心态真是绝了! ·  昨天  
51好读  ›  专栏  ›  区块链技术学习

区块链上编程:DApp 开发实践

区块链技术学习  · 公众号  ·  · 2018-10-24 11:47

正文

来自:创宇前端(微信号:KnownsecFED)

链接:https://knownsec-fed.com/2018-10-07-dapp-dev-practice/


导读:
本文旨在引导对 DApp 开发感兴趣的开发者,构建一个基于以太坊去中心化应用,通过开发一款功能完备的竞猜游戏,迈出 DApp 开发的第一步,通过实例讲解 Solidity 语言的常用语法,以及前端如何与智能合约进行交互。

如果正在阅读的你,从未接触过 DApp 开发也不要紧,可以先阅读 区块链上编程:DApp 开发简介 进行前置知识补充。

随着加密猫、FOMO3D 等游戏的火爆,去中心化应用在游戏领域遍地开花,下面就带着大家一起开发一款简单有趣的 DApp 游戏,帮助大家熟悉 DApp 开发。

本 DApp 实现的合约功能:

用户从 0-6 的数字中,任意组合三位数进行投注,合约计算出 3 位随机数,根据随机数的组合规则分别给予用户不同倍数的奖励,如随机数为 AAA ,A 等于 6 则奖励至少 20 倍投注金额,即奖池所有余额;A 不等于 6 则奖励 5 倍投注金额;随机数为 AAB,则奖励 2 倍投注金额;随机数为 ABC 则不奖励,同时用户可查看奖池余额和个人投注记录。

合约编写

可以看出合约需要实现用户投注、生成随机数、发放奖励、奖池余额查询的功能,接下来编写我们的合约代码。

新建 Lottery.sol 合约文件,声明合约版本, ^ 表示合约编译版本为 0.4.0 至 0.5.0(不含 0.5.0)。

pragma solidity ^0.4.0;

定义合约基本内容,同时声明最低投注金额。

contract Lottery {
  uint public betMoney = 10 finney;
}

生成随机数,通过区块难度 block.difficulty 和内置函数 keccak256 生成随机数,在 EVM 中常用的数据存储位置: memory storage ,函数的参数、返回值默认存储在 memory 中,状态变量默认存储在 storage 中,我们可以通过声明 memory storage 改变默认存储位置,两者的存储都需要消耗 gas ,但 storage 的开销远大于 memory 。、

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






请到「今天看啥」查看全文