以太坊彩票项目

半世苍凉 提交于 2020-08-15 13:25:02

目录

项目概述

solidity 编写合约,node.js 编译、部署、获取、交互合约,react搭建前端界面
超详细~

(1)彩票业务规则-智能合约lottery.sol
[1] 全民参与(play函数)
[2] 每次投注只能投注1eth
[3] 每个人可以投多注
[4] 仅限管理员可以开奖(KaiJiang函数)
[5] 仅限管理员可以退奖(TuiJiang函数)
(2)编译智能合约 01-compile.js
[1] 导入solc编译器和fs库
[2] fs读取contracts文件夹下lottery.sol合约
[3] solc编译合约
[4] 导出bytecode(机器码)和interface(ABI)
(3)部署智能合约上链 02-deploy.js
[1] 获取bytecode和interface
[2] 导入web3
[3] 设置网络,管理员(部署合约的人)实例化web3(.setProvider)
[4] 拼接合约数据
[5] 拼接bytecode
[6] 返回合约地址(instanceAddress)
(4)从区块链获取合约实例
[1] 获取interface(ABI)和合约地址(instanceAddress)
[2] 导入web3
[3] 并设设置网络,用户(使用者)实例化web3(.setProvider)
[4] 利用interface(ABI)和合约地址(instanceAddress)获取合约
[5] 返回合约,用于交互
(5)设计前端,同合约交互
[1] 利用react完成界面搭建
[2] 在界面中显示返回的数据
[3] 待输入

1.合约上链交互5部曲示意图
001合约上链交互5部曲示意图
在这里插入图片描述

在这里插入图片描述

项目具体实现

[1].彩票业务规则示例图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述



[2].整体项目搭建

系统环境 : MacOS,IDE : Goland2020

  1. 整体项目结构图(环境搭建完毕图)
    在这里插入图片描述
  2. 使用react脚手架

[step-1] 终端进入goland项目文件夹下: cd /XXXXXX/GoProjects/src/
[step-2] 终端安装react应用程序骨架: npm install -g create-react-app
[step-3] 终端创建一个空的项目: create-react-app lottery-react
[step-4] 在goland中打开此项目


  1. goland终端里输入npm start启动项目,最终效果如图
    在这里插入图片描述

  2. 安装react组件库和配套样式库,并清理工程

[step-1] npm install semantic-ui-react --save
[step-2] npm install semantic-ui-css --save
[step-3] 进⼊src⽬录,保留App.js和index.js,删除掉其他⽂件
[step-4] 删掉App.js和index.js中对删除⽂件的依赖代码
[step-5] 删掉App.js中render函数渲染的内容
[step-6] 手动输入萌新认证:Hello World
[step-7] 在goland中打开此项目





//App.js 清理后代码
import React, {Component} from 'react';

class App extends Component {
  render() {
    return(
        <p>Hello World !</p>
    );
  }
}

export default App;
//index.js 清理后代码
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
    <App />,
  document.getElementById('root')
);

最终效果图
在这里插入图片描述

  1. 安装solc编译器和web3模块,并配置项目

[step-1] npm install solc@0.4.25 --save
[step-2] npm install web3@1.2.11 --save
注意: 若版本不一致,可以通过 name@xxx,指定版本(这两个版本尽量保持一直,否则容易出问题)
[step-3] (我是mac版goland2020,位置同其他版可能不一样,请通过搜索引擎解决)如图配置
[step-4] 新建一个testEnvironment.js 文件,输入最后一幅图,结果如果所示,则配置完成
注意:新建文件后,若显示红色,为git为添加问题,右键文件,找到"Git",选择"+Add";或者上一步中,右下角选择"Always Add"。




在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述





  1. 最后安装的库 package.json
{
  "name": "review-lottery",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.5.0",
    "@testing-library/user-event": "^7.2.1",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.1",
    "semantic-ui-css": "^2.4.1",
    "semantic-ui-react": "^1.1.1",
    "solc": "^0.4.25",
    "web3": "^1.2.11"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
  1. 创建目录与文件

[step-1] goland终端输入图中命令,特别注意当前所在目录
[step-2]根目录创建合约目录contracts,创建合约lottery.sol文件,命令如图
最终效果如图

在这里插入图片描述
在这里插入图片描述

[3].彩票合约 lottery.sol

[step-1] Google浏览器,打开Remix在线编译器(选择0.4.24) http://remix.ethereum.org/
[step-2] 在线测试代码Lottery.sol

pragma solidity ^0.4.24;

contract Lottery{
    // ---整体逻辑
    //1. 管理员:负责开奖和退奖,添加modifier函数限定两个函数
    //2. 彩民池:address[] players,开奖退奖都要清空
    //3. 当前期数:每次开奖退奖后都要加1
    //4. 开奖:
    //       (1)求随机数,确定中奖地址 --这里需要注意
    //       (2)分配奖金池,分为奖金和管理费
    //       (3)给中奖地址和管理员地址转钱
    //       (4)期号+1,清空彩民池
    //5. 退奖:
    //       (1)根据数组长度,for循环地址转钱
    //       (2)期号+1,清空彩民池
    //6. 投注play:
    //       (1)限制条件:require(要求投注金额必须是1个以太币)
    //       (2)将投注地址添加到彩民池子
	
	//变量,这里public,下面也用getXXX形式返回了。
	//管理员地址
    address public manager;
    //中奖彩民
    address public winner;
    //第round期
    uint256 public round = 1;
    //所有参与的彩民(管理员也可以参与游戏)
    address[] public players;

	//构造函数,部署合约的人就是管理员
    constructor() public{
        manager = msg.sender;
    }

    //---彩民投注函数
    //1. 合约⾥⾯的单位默认为 wei ,需要进⾏ ether 修饰,也可以使⽤ 1 * 10 ** 18 进⾏转换。
    //2. payable关键字必须添加,否则⽆法购买 
    //3. msg.value全局变量能够接收到交易中的 value 字段 
    //4. 数组添加使⽤ push
    function play() payable public{
    	//require限定投注金额为1ETH
        require(msg.value == 1 ether);
        //将投注者添加到彩民池
        players.push(msg.sender);
    }
	
	//---管理员开奖函数,用onlyManager限定权限
	//1. 随机中奖需要⼀个随机的索引值,我们使⽤难度值,当前时间,参与⼈数作为种⼦⽣成⼀个⼤的 数字⽣成索引。
	//2. 开奖前要注意校验有效性,如果⽆⼈参与可以不开奖。 
	//3. 本轮结束后 round++ ,进⼊下⼀轮。 
	//4. 删除所欲的参与⼈,注意delete只是删除内部元素,不会删除 players 。
    function KaiJiang() onlyManager public{
		//随机一个下标值,表示获奖者
        bytes memory tmp1 = abi.encodePacked(block.timestamp, block.difficulty, players.length);
        bytes32 tmp2 = keccak256(tmp1);
        uint256 tmp3 = uint256(tmp2);
        
		//确定获奖者地址
        uint256 index = tmp3 % players.length;
        winner = players[index];
        
		//根据9-1分成规则转钱
        uint256 contractMoney = address(this).balance;
        uint256 winnerMoney = contractMoney / 100 * 90;
        uint256 managerMoney = contractMoney - winnerMoney;
        winner.transfer(winnerMoney);
        manager.transfer(managerMoney);
		
		//本期结束后期数+1,并清空彩民池
        round++;
        delete players;
    }
    
	//---管理员退奖
    function TuiJiang() onlyManager public{
    	//遍历数组,逐一转账
        for(uint i = 0; i < players.length; i++){
            players[i].transfer(1 ether);
        }
        //期数+1
        round++;
        //清空彩民池
        delete players;
    }
	
	//---修饰器
    modifier onlyManager{
    	//限定作用,非管理员不允许调用开奖和退奖函数
        require(msg.sender == manager);
        _;
    }

    //返回奖池金额
    function getBalance() view public returns(uint){
        return address(this).balance;
    }

    //返回奖池人数
    function getPlayersLength() view public returns(uint){
        return players.length;
    }

    //以数组形式返回奖池人地址
    function getPlayers() view public returns(address []){
        return players;
    }
	
	//返回管理员
    function getManager() view public returns(address){
        return manager;
    }
	
	//返回管理员
    function getWinner() view public returns(address){
        return winner;
    }
	
	//返回当前期号
    function getRound() view public returns(uint256){
        return round;
    }
}

[4].编译合约 01-compile.js

//1.导入相关包
let solc = require('solc')
let fs = require('fs')
//2.读取合约
let sourceCode = fs.readFileSync('./contracts/Lottery.sol', 'utf-8')
//3.编译合约
//将1设置为第二个参数将激活优化器
let  output = solc.compile(sourceCode, 1)
//4.导出合约
//console.log(output)
module.exports = output[ 'contracts'][':Lottery']

[5].部署合约 02-deploy.js

//1. 引入
let {bytecode, interface} = require('./01-compile')
let Web3 = require('web3')
//let HDWalletProvider = require('truffle-hdwallet-provider')

//2. new一个web3实例
let web3 = new Web3();

//3. 设置网络,这里用Ganache本地测试网络环境,自行搜索安装
//助记词
//let terms = '注意这里需要输入助记词';
let netIp = 'http://localhost:7545'
//let provider = new HDWalletProvider(terms, netIp)
//const web3 = new Web3();

web3.setProvider(netIp)

let contract = new web3.eth.Contract(JSON.parse(interface))


let deploy = async () => {
    //4. 先获取所有的账户
    let accounts = await web3.eth.getAccounts() //获取账户列表
    console.log('account:', accounts)
    //5. 执行部署
    let instance = await contract.deploy({
        data:bytecode, //合约的bytecode
        //argument : ['HelloWorld']
    }).send({
        from : accounts[0], //部署合约人(管理员地址),需要从这里扣钱!!!
        gas : '6721975',
        gasPrice : '1'
    })
    console.log('instance address : ', instance.options.address)
}

deploy()

[6]. 从区块链获取合约实例

initWeb3.js
注意重要更新,很重要

let Web3 = require('web3')
let web3 = new Web3();

//重要更新:为了保护隐私,默认关闭,需要手动开启,才能获取用户当前地址
//information : https://medium.com/metamask/https-medium-com-metamask-breaking-change-injecting-web3-7722797916a8
window.ethereum.enable();

//说明:
//实例化web3需要一个provider(服务商),管理员部署的时候用自己的账号
//但是彩民参与这个活动,投注要用自己账号的钱
//实现
//1.用Ganache的某个账户地址的私钥,在MetaMask中创建一个账户
//2.在浏览器启动后,MetaMask会向浏览器注入一个web3实例
//3.利用window.web3.currentProvider获得实例
web3.setProvider(window.web3.currentProvider)
console.log("Injected web3 found!")

module.exports = web3

lottery.js
注意:不要直接复制abi和address,将合约放到在线编译器http://remix.ethereum.org/中编译通过,从在线编译器中复制abi

let web3 = require('../utils/initWeb3')
//这里可以从remix solidity在线编译器中直接获取
const abi = [{
    "constant": false,
    "inputs": [],
    "name": "KaiJiang",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
}, {
    "constant": false,
    "inputs": [],
    "name": "play",
    "outputs": [],
    "payable": true,
    "stateMutability": "payable",
    "type": "function"
}, {
    "constant": false,
    "inputs": [],
    "name": "TuiJiang",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
}, {"inputs": [], "payable": false, "stateMutability": "nonpayable", "type": "constructor"}, {
    "constant": true,
    "inputs": [],
    "name": "getBalance",
    "outputs": [{"name": "", "type": "uint256"}],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}, {
    "constant": true,
    "inputs": [],
    "name": "getManager",
    "outputs": [{"name": "", "type": "address"}],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}, {
    "constant": true,
    "inputs": [],
    "name": "getPlayers",
    "outputs": [{"name": "", "type": "address[]"}],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}, {
    "constant": true,
    "inputs": [],
    "name": "getPlayersLength",
    "outputs": [{"name": "", "type": "uint256"}],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}, {
    "constant": true,
    "inputs": [],
    "name": "getRound",
    "outputs": [{"name": "", "type": "uint256"}],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}, {
    "constant": true,
    "inputs": [],
    "name": "getWinner",
    "outputs": [{"name": "", "type": "address"}],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}, {
    "constant": true,
    "inputs": [],
    "name": "manager",
    "outputs": [{"name": "", "type": "address"}],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}, {
    "constant": true,
    "inputs": [{"name": "", "type": "uint256"}],
    "name": "players",
    "outputs": [{"name": "", "type": "address"}],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}, {
    "constant": true,
    "inputs": [],
    "name": "round",
    "outputs": [{"name": "", "type": "uint256"}],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}, {
    "constant": true,
    "inputs": [],
    "name": "winner",
    "outputs": [{"name": "", "type": "address"}],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}]
//注意这里是合约地址,从deploy.js返回值中获取
const address = '0x6988851A341E179C29733e8C824D5658A5572507'

let lottery = new web3.eth.Contract(abi, address)
module.exports = lottery

[7].完善界面

ui.js

import React from 'react'
import { Card, Icon, Image, Statistic, Button, Label} from 'semantic-ui-react'


const CardExampleCard = (props) => (
    <Card>
        <Image src='images/logo.jpg' wrapped ui={false} />
        <Card.Content>
            <Card.Header>中国体育彩票</Card.Header>
            <Card.Meta>
                <p>管理员地址:</p>
                <Label size='mini'>
                    <Icon name='hand point right' />{props.manager}
                </Label>
                <p>当前地址:</p>
                <Label size='mini'>
                    <Icon name='hand point right' />{props.account}
                </Label>
            </Card.Meta>
            <Card.Description>
                每晚八点半准时开奖,不见不散!!!
            </Card.Description>
        </Card.Content>
        <Card.Content extra>
            <a>
                <Icon name='user' />
                {props.playerCounts}人参与
            </a>
        </Card.Content>

        <Card.Content extra>
            <Statistic color='red'>
                <Statistic.Value>{props.balance}ETH</Statistic.Value>
                <Statistic.Label>奖金池</Statistic.Label>
            </Statistic>
        </Card.Content>

        <Card.Content extra>
            <Statistic color='blue'>
                <Statistic.Value>{props.round}</Statistic.Value>
                <a href='www.baidu.com'>点我查看历史交易记录</a>
            </Statistic>
        </Card.Content>

        <Button animated='fade' color='orange' onClick={props.play} loading={props.isPlaying}>
            <Button.Content visible>投注产生希望</Button.Content>
            <Button.Content visible>购买放飞梦想</Button.Content>
        </Button>

        <Button inverted color='red' style={{display:props.isShowButton}} onClick={props.Kaijiang} loading={props.isDrawing}>
            开奖
        </Button>
        <Button inverted color='green' style={{display:props.isShowButton}} onClick={props.Tuijiang} loading={props.isDrawBacking}>
            退奖
        </Button>
    </Card>
)

export default CardExampleCard

App.js

import React,{Component} from 'react';
import CardExampleCard from './display/ui'
let web3 = require('./utils/initWeb3')
let lottery = require('./eth/lottery')

class App extends Component{

    constructor(props) {
        super(props);
        this.state = {
            manager : '',
            round:'',
            winner:'',
            playerCounts:0,
            balance:0,
            players:[],
            account : '',
        };
    }

    async componentWillMount(){
        let accounts = await web3.eth.getAccounts()
        let manager = await lottery.methods.manager().call();
        let round = await lottery.methods.getRound().call();
        let winner = await lottery.methods.getWinner().call();
        let playerCounts = await lottery.methods.getPlayersLength().call();
        let balanceWei = await lottery.methods.getBalance().call();
        let balance = web3.utils.fromWei(balanceWei, 'ether')
        let players = await lottery.methods.getPlayers().call();
        this.setState({
            manager,
            round,
            winner,
            playerCounts,
            balance,
            players,
            account:accounts[0],
            isPlaying:false,
            isDrawing:false,
            isDrawBacking:false,
            isShowButton:accounts[0] === manager ? 'inline' : 'none',
        });
    }

    play = async() =>{
        console.log("Play Button Clicked!!!");
        this.setState({isPlaying:true})
        try{
            let accouts = await web3.eth.getAccounts();
            await lottery.methods.play().send({
                from:accouts[0],
                value:1*10**18
            })
            alert(`Success: play successfully!`);
            this.setState({isPlaying:false});
            window.location.reload(true);
        }catch (e) {
           alert(`Error: play failed! ${e}`);
           this.setState({isPlaying:false});
        }
    }

    Kaijiang = async() =>{
        console.log("Kaijiang Button Clicked!!!");
        this.setState({isDrawing:true})
        try{
            let accouts = await web3.eth.getAccounts();
            await lottery.methods.KaiJiang().send({
                from:accouts[0],
            })
            let winner_ = await lottery.methods.getWinner().call();
            alert(`开奖喽!中奖者为${winner_}`);
            this.setState({isDrawing:false});
            window.location.reload(true);
        }catch (e) {
            alert(`Error: kaijiang failed! ${e}`);
            this.setState({isDrawing:false});
        }
    }
    Tuijiang = async() =>{
        console.log("Tuijiang Button Clicked!!!");
        this.setState({isDrawBacking:true})
        try{
            let accouts = await web3.eth.getAccounts();
            await lottery.methods.TuiJiang().send({
                from:accouts[0],
            })
            alert(`退奖成功!!!`);
            this.setState({isDrawBacking:false});
            window.location.reload(true);
        }catch (e) {
            alert(`Error: Tuijiang failed! ${e}`);
            this.setState({isDrawBacking:false});
        }
    }


    render() {
        return (
            <div>
                <CardExampleCard
                    manager={this.state.manager}
                    round={this.state.round}
                    winner={this.state.winner}
                    balance={this.state.balance}
                    players={this.state.players}
                    playerCounts={this.state.playerCounts}
                    account={this.state.account}
                    play={this.play}
                    Tuijiang={this.Tuijiang}
                    Kaijiang={this.Kaijiang}
                    isPlaying={this.state.isPlaying}
                    isDrawing={this.state.isDrawing}
                    isDrawBacking={this.state.isDrawBacking}
                    isShowButton={this.state.isShowButton}
                />
            </div>
        );
    }
}

export default App;

[8].最终效果

在这里插入图片描述

创作声明

本文是作者学习"传智播客"旗下"区块链"课程时,基于自己思考,实现并总结的学习笔记。
本文项目思路,图片源于"传智播客"的学习资料,若有不当,请联系删除。
若有需要,请支持并购买"传智播客"旗下正版学习资料。

备注

一枚区块链萌新,希望同大家多多学习交流!!!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!