社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  区块链

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

区块链技术学习 • 6 年前 • 291 次点击  

来自:创宇前端(微信号: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中常用的数据存储位置:memorystorage,函数的参数、返回值默认存储在memory中,状态变量默认存储在storage中,我们可以通过声明memorystorage改变默认存储位置,两者的存储都需要消耗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类型比较重要的成员属性主要有 balancetransfer,分别为获取地址余额、转移eth至该地址,默认eth单位是wei

contract Lottery {
  ...
  function getBankBalance(public view returns(uint{
    return address(this).balance;
  }
  ...
}

用户投注,投注方法需要使用payable关键字描述,用来表示可以接收eth;通过msg.sendermsg.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),生成安全的随机数。

参考资料

  • Solidity:https://solidity.readthedocs.io/en/v0.4.24

  • Web3js:https://web3js.readthedocs.io/en/1.0

  • Cryptozombies:https://cryptozombies.io/zh/course

  • Coursetro:https://coursetro.com/courses/20/Developing-Ethereum-Smart-Contracts-for-Beginners



●编号104,输入编号直达本文

●输入m获取到文章目录


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/mCcCFrAKEt
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/25503
 
291 次点击