基于以太坊的博彩DAPP

区块链博彩项目(Nodejs+web3js+react)

开发步骤如下:

一、安装node

nodejs的版本必须大于8.0.0
点击下载node

二、配置npm镜像仓库

命令行指定淘宝镜像

npm --registry https://registry.npm.taobao.org info underscore 

配置后可通过下面方式来验证是否成功

npm config get registry

或者

npm info express
三、编写智能合约(solidity)

在线的remix-solidity IDE编辑器
点击编写智能合约
彩票项目代码如下:

pragma solidity ^0.4.17;            //指定solidity的版本

contract Lottery{

    address public manager;         //管理员地址
    address[] public players;       //投注者(玩家)地址
    address public winner;          //赢家(中奖者)地址

    //构造函数  指定管理员地址为合约部署者的地址
    function Lottery() public{
        manager =  msg.sender;
    }

    //拿到管理员的地址  view字段表示该操作对智能合约没有任何改变
    function getManager() public view returns (address){
        return manager;
    }

    //投注彩票  每注彩票1ether(可自行设置)
    function enter() public payable {
        require(msg.value == 1 ether);
        players.push(msg.sender);
    }

    //返回所有的投注彩票的人
    function  getAllPlayers() public view returns (address[]){
        return players;
    }

    //获取智能合约账户的余额 即奖池的奖金
    function getBalance() public view returns(uint){
        return this.balance;
    }

    //获取参加博彩项目的玩家人数
    function getPlayersCount() public view returns(uint){
        return players.length;
    }

    //产生一个随机数  据此决出中奖者
    function random() private view  returns (uint){
        return  uint(keccak256(block.difficulty, now, players));
    }

    //决出中奖者  并将奖池奖金全部发送到其账户
    function  pickWinner() public onlyManagerCanCall {
        uint index = random() % players.length;
        winner =  players[index];
        players = new address[](0) ;   //将奖池玩家清空  便于下一轮游戏的开始
        winner.transfer(this.balance);
    }

    //如果由于特殊原因游戏中止  则将奖池奖金退还给所有玩家
    function refund() public onlyManagerCanCall{
        for(uint i = 0; i < players.length; i++){
            players[i].transfer(1 ether);
        }
        players = new address[](0) ;    //将奖池玩家清空  便于下一轮游戏的开始
    }

    //只有管理员才能进行操作  关键字段modifier 
    modifier onlyManagerCanCall(){
        require(msg.sender == manager);
        _;
    }
}
四、solidity开发环境搭建

1、solidity编译器,编译环境
2、 mocha 抹茶测试环境
3、部署智能合约的脚本 到指定网络

npm init

一直yes即可

开发环境的目录结构:

  • contracts目录
    -lottery.sol
  • test目录
    -lottery.test.js
  • package.json
  • compile.js
  • deploy.js
    这里写图片描述
五、编译智能合约
  • 导入solidity编译器(solc)
npm install --save solc
  • 读取智能合约文件并编译导出相应的interface和bytecode
//编译的脚本   compile.js
const path = require('path');
const fs = require('fs');
const solc = require('solc');   //引入solidity编译器 -> solc相当于javac

//找到智能合约所在的路径
const srcpath = path.resolve(__dirname, 'contracts', 'Lottery.sol');

//读取智能合约的源代码 utf-8
const source = fs.readFileSync(srcpath, 'utf-8');

//编译智能合约 生成两部分内容 1.bytecode字节码 2.便于调用的应用程序二进制接口(即ABI) interface
const result = solc.compile(source, 1);

//将智能合约暴露出去 便于调用
module.exports = result.contracts[':Lottery'];
六、测试智能合约(ganache)
  • 导入web3js

    web3js简介:web3js是以太坊提供的一个javascript库,封装了以太坊的RPC通信API,提供了一系列与区块链交互的方法,使得javacsript与以太坊交互变得简单。
    web3js与以太坊节点的交互,其底层用的是jsonRPC,不过web3js屏蔽了复杂的编码解码过程,提供了更易用的接口。

npm install --save web3
  • 导入ganache(ganache是testrpc的升级版, (一个local test network))
npm install --save ganache-cli
  • 导入mocha测试框架
npm install --save mocha

这里只测试其中一部分功能(test.lottery.js)

const assert = require('assert');              //引入assert框架
const ganache = require('ganache-cli');        //引入ganache
const Web3 = require('web3');                  //引入Web3
const web3 = new Web3(ganache.provider());     //设置测试的provider
const {interface, bytecode} = require('../compile');

let lottery;       //智能合约
let accounts;      //账户

//获取智能合约实例
beforeEach(async ()=>{
   accounts = await web3.eth.getAccounts(); 
    lottery = await new web3.eth.Contracts(JSON.parse(interface))
    .deploy({
        data:bytecode
    }).send({
        from:accounts[0],
        gas:'1000000'})
    });

//测试智能合约地址是否存在
describe('彩票智能合约',()=>{
       it('部署智能合约',()=>{
           //断言
           assert.ok(lottery.options.address);  
       }) ;

       //问你自己问题, 我们关心的业务是什么?
       //1.测试彩票投注
       it('一个账户投注彩票', async()=>{
          await lottery.methods.enter().send({
              from:accounts[0],
              value:web3.utils.toWei('1','ether');
          });
       });
        const players =  await lottery.methods.getPlayers().call({
            from:accounts[0]
        });

        //断言
        assert.equal(accounts[0], players[0]);
        assert.equal(1, players.length);

    });

        it('多个账户投注彩票', async()=>{
          await lottery.methods.enter().send({
              from:accounts[0],
              value:web3.utils.toWei('1','ether');
          });
           await lottery.methods.enter().send({
              from:accounts[1],
              value:web3.utils.toWei('1','ether');
          });
           await lottery.methods.enter().send({
              from:accounts[2],
              value:web3.utils.toWei('1','ether');
          });

       });
        const players =  await lottery.methods.getPlayers().call({
            from:accounts[0]
        });

        assert.equal(accounts[0],players[0]);
        assert.equal(accounts[1],players[1]);
        assert.equal(accounts[2],players[2]);
        assert.equal(3,players.length);

    });

    //确定只有给正确钱才能买到彩票  测试失败的情况
     it('只有给正确钱才能买到彩票', async()=>{
        try{
          await lottery.methods.enter().send({
              from:accounts[0],
              value:web3.utils.toWei('0.1','ether');
          });
          assert(false);
          }catch(err){
              //代码应该执行到这里
              assert.equal(1,1);
          }
       });
    });
    //3.测试调用修饰符,只有经理可以开奖,测试正常的逻辑和错误的逻辑

    // 4. 测试整个彩票投注流程, 投注,开奖,奖池清空一个人投注,直接开奖,那这个人应该得奖. 
七、部署智能合约到以太坊网络(rankeby)
  • 导入truffle-hdwallet-provider(0.0.3版本)

npm install --save truffle-hdwallet-provider@0.0.3

通过infura提供的以太坊节点将智能合约部署上去

//部署智能合约到真实的rankeby网络   deploy.js
const Web3 = require('web3');
const {interface,bytecode} = require('./compile');
const HDWalletProvider = require('truffle-hdwallet-provider');
const mnemonic = "crouch fiction income edge cluster turtle plastic ozone mom predict goddess express";
const provider = new HDWalletProvider(mnemonic, "https://rinkeby.infura.io/v3/b5193966085f4ae0a469a7a77215b0ba");
const web3 = new Web3(provider);

deploy =async ()=>{
    //获取账户
    const accounts = await web3.eth.getAccounts();   

    //打印accounts[0],即管理员的地址 
    console.log(accounts[0]);

    //result即为智能合约实例
    const result = await new web3.eth.Contract(JSON.parse(interface))
        .deploy({
            data:bytecode
            //如果构造函数有参数  可以进行添加 arguement:[]
        }).send({
            from:accounts[0],     //指定从哪个账户扣费
            gas:'3000000'     
        });

    //打印智能合约的地址
    console.log('address:' + result.options.address);
    console.log('------------------');   //分割线
    //打印智能合约的abi  通过abi和地址可以获取该智能合约实例  然后进行调用
    console.log(interface);
};
deploy();
八、前端页面的编写
  • 1、关于web3的处理
    由于MetaMask提供的web3是v0.2版本的,我们导入的web3是v1.0版本的,因此,我们需要把web3 v0.2的provider 注入到web3 v1.0的provider的里面。provider相当于电话卡,用户投注彩票需要花费的是自己的钱,也就是说使用MateMask注入到浏览器的web3 0.2版本的provider进行操作。
import Web3 from 'web3';                             //引入的1.0版本d的web3
const web3 = new Web3(window.web3.currentProvider);   //安装了Metamask的web3
export default web3;
  • 2、导出彩票智能合约实例
    前面说过,通过智能合约地址和相应的abi可以得到智能合约的的地址
//导入安装了用户provider的web3 通过这个调用彩票智能合约实例
import web3 from './web3'

const address = '0xb4858082B1A03069b09f0ff8E2992e2B3728810B';

const abi = [{"constant":true,"inputs":[],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"manager","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"refund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"pickWinner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"player","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getManager","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"enter","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getAllPlayers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getAllPlayerCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}];

//生成彩票智能合约实例
const lottery = new web3.eth.Contract(abi, address);

//导出彩票智能合约实例  通过这个可以调用智能合约里面的方法
export default lottery;
  • 3、前端页面部分
import React, {Component} from 'react';
import {Message, Card, Image, Icon, Statistic, Button, Label} from 'semantic-ui-react';
import web3 from './web3';
import lottery from './lottery';

class App extends Component {

    constructor(props){
        super(props);
        this.state={
            manager:"",
            balance:0,
            players:0,
            ck:false,
            isManger:'none'
        }
    }

    async componentDidMount(){
        const address = await lottery.methods.getManager().call();
        const balance = await lottery.methods.getBalance().call();
        const players = await lottery.methods.getAllPlayerCount().call();
        this.setState({
            manager:address,
            balance,
            players,
        });

        let accounts = await web3.eth.getAccounts();
        if(accounts[0] === address) {
            this.setState({isManger:'inline'})
        }else {
            this.setState({isManger:'none'})
        }
    }

    //点击购买彩票
    enter = async () =>{
        this.setState({ck:true});
        const accounts = await web3.eth.getAccounts();
        await lottery.methods.enter().send({
            from:accounts[0],
            value:'1000000000000000000'
        });
        this.setState({ck:false});
        window.location.reload(true);   //重新刷新页面
    };

    pickWinner= async ()=>{
        this.setState({ck:true});
        const account = await web3.eth.getAccounts();
        await lottery.methods.pickWinner().send({
            from:account[0]
        });
        this.setState({ck:false});
        window.location.reload(true);   //重新刷新页面
    };

    refund = async () =>{
        this.setState({ck:true});
        const account = await web3.eth.getAccounts();
        await lottery.methods.refund().send({
            from:account[0]
        });
        this.setState({ck:false});
        window.location.reload(true);   //重新刷新页面
    };

    render() {
        console.log(web3.version);
        return (
            <div>
                <br/>
                <Message info>
                    <Message.Header>区块链博彩游戏</Message.Header>
                    <p>有胆你就来!</p>
                </Message>
                <Card>
                    <Image src='/logo.jpg'/>
                    <Card.Content>
                        <Card.Header>骗人彩</Card.Header>
                        <Card.Meta>管理员地址:
                            <Label size='mini'>
                            {this.state.manager}
                            </Label>
                        </Card.Meta>
                        <Card.Description>每周日晚上8点整开奖</Card.Description>
                        <Card.Content extra>
                            <Statistic color='red'>
                                <Statistic.Value>
                                    {this.state.balance} e
                                </Statistic.Value>
                                <p>奖池奖金</p>
                            </Statistic>
                        </Card.Content>
                    </Card.Content>
                    <Card.Content extra>
                        <a>
                            <Icon name='user'/>
                            已有{this.state.players}人参与
                        </a>
                    </Card.Content>
                    <Button animated='fade' onClick={this.enter} loading={this.state.ck} disabled={this.state.ck}>
                        <Button.Content visible>点击投注</Button.Content>
                        <Button.Content hidden>每注2元</Button.Content>
                    </Button>
                    <Button inverted color='orange' style={{display:this.state.isManger}} onClick={this.pickWinner} loading={this.state.ck} disabled={this.state.ck}>
                        点击开奖
                    </Button>
                    <Button inverted color='green' style={{display:this.state.isManger}} onClick={this.refund} loading={this.state.ck} disabled={this.state.ck}>
                        点击退款
                    </Button>
                </Card>
            </div>
        );
    }
}

export default App;

猜你喜欢

转载自blog.csdn.net/qq_42815754/article/details/82256869