带你玩转区块链--以太坊基础、发币、基于智能合约实现彩票项目-第二章-第一节【以太坊篇】

意义:

        在上一节知识学习中,我们已经了解如何实现一个基础区块链,并重构了BTC关键代码。对比传统的中心化项目,区块链项目拥有很多优势,如:追溯性、不可传篡改性。在中心化项目中的网络协议是:【数据层-----网络层--------传输层-------应用层】而在区块链中的网络协议为:【数据层------网络层--------共识层(pow、poc、dpos等)--------激励层(各种币)-------应用层】。这些优势和特定让区块链成为了有特点的超级账本。

       在区块链1.0时代中,Btc开启了数字货币时代,但实质上pow的算题既浪费了大量电力却又没有实际上的意义。这时候而ETH横空而出,让区块链的应用层有了更好的前景和意义。以太坊是一个开源的有智能合约功能的公共区块链平台,是区块链应用层的一个典型币种。作为区块链的第二大货币,我们有必要详细研究一番。

---------------------------------------------------------------------------------------------------------------------------------------

如果您改进了代码或者想对照代码学习,请访问我的Github。

如果您有问题想要讨论。请加我微信:laughing_jk(加我,请备注来源,谢谢)

彩票项目源码:https://github.com/lsy-zhaoshuaiji/lottey.git

---------------------------------------------------------------------------------------------------------------------------------------

一、准备工作

一、以太坊IDE

由于以太坊目前没有专门的IDE只能在Chrome和火狐上编译,所以为了防止文件丢失,我们需要安装remix-ide,让文件关联到本地。点击网址,即可进行solidity合约编写。

http://remix.ethereum.org/

npm install remix-ide -g
remix-ide

或者

git clone https://github.com/ethereum/remix-ide.git
git clone https://github.com/ethereum/remix.git # only if you plan to link remix and remix-ide repositories and develop on it.

cd remix  # only if you plan to link remix and remix-ide repositories and develop on it.
npm install  # only if you plan to link remix and remix-ide repositories and develop on it.
npm run bootstrap  # only if you plan to link remix and remix-ide repositories and develop on it.

cd remix-ide
npm install
npm run setupremix  # only if you plan to link remix and remix-ide repositories and develop on it.
npm start

二、安装metamask

Metamask是与以太坊交互的重要工具,使用请务必安装

https://github.com/MetaMask/metamask-extension/releases

三、安装geth

https://ethfans.org/wikis/Ethereum-Geth-Mirror

点击下载下来的exe安装文件,选择安装目录,安装后会自动生成geth和keystore文件夹,在keystore会保存账户密码,也就是你的钱包的重点,为了防止丢失可以多复制几份,存在不同的地方。打开cmd输入geth --help  若有反应则代表成功

四、.安装以太坊钱包(可略)

下载网址如下,需要科学上网

https://ethfans.org/wikis/Ethereum-Wallet-Mirror

二、学习SOLIDITY

Solidity是面向对象的语言,是以太坊智能合约开发的必备知识之一,所以我们需要学习一下solidity。特别注意在remix中是不支持直接进行中文注释的,所以您需要在其他地方注释后 复制过来才能正常使用。

一、solidity基础

pragma solidity ^0.4.24;                       //版本号

contract Test{
    uint256 ui=100;
    int256  i =50;
    function add()returns(uint256){            //没有main函数调用则执行
        return ui + uint256(i);
    }
    
}

1.private view为私有函数,只能在合约内调用,public view为公有函数,任何用户都能调用,函数默认为public view

2.view/constant/pure:,如果函数中只读引用了状态变量,那么函数应该修饰为view/constant,若未引用状态变量则修饰为pure,如果修改了状态变量则都不用。

3.如果调用需要转钱,则需要将函数标注为payable

4.获取当前合约余额,return this.balance   this指代当前合约

5.wei与ETH的转换率为10**18 (10的18次方,wei为最小单位,1个ETH=1*10**8)

6.send 返回ture或者flase ,transfer返回异常,即使没有判断send的返回值,合约也会返回成功。所以属于transfer更安全,

7.转账转的是合约的钱,所以谁调用transfer谁就受益。

pragma solidity ^0.4.24;

contract Test {
    address add0=0x00ca35b7d915458ef540ade6068dfe2f44e8fa733c;
    address add1=0x0014723a09acff6d2a60dcdf7aa4aff308fddc160c;
    function ()public payable{
        
    }
    function getBalance() public view returns(uint256){
        return address(this).balance;
    }
    function Transfer() public{
        add1.transfer(10* 10**18);
    }
    function getAdd2Balance()public view returns(uint256){
        return add1.balance;
    }
}

8.动态bytes 可以不分配空间,直接用字符串进行赋值(新版本IDE不可以)

9.动态bytes,若未分配空间,直接通过下标获取则会报错

10.动态bytes可以通过bytes.lenth进行下标赋值,自动分配空间,默认值为0

11.动态bytes可以通过下标进行修改

12.动态bytes支持push操作,类似于append,可以追加元素

13.定长bytes不能修改数据、不能修改长度,可以通过下标访问。定义方法例如: bytes5 publikc test 

14.string是不支持lenth和push等操作的,但是可以借助bytes实现,如byte(str)s.lenth

15.参数变量默认为memory,状态变量默认是storage,函数内局部变量默认也为storage,但可以修改为memory。

16.如果变量想在函数间进行引用传递,需要定义参数变量类型为storage,  如: setTest(string storage str1)

17.结构体定义如下:

pragma solidity ^0.4.24;

contract Test{
    struct student{
        string Name;
        uint8  Age;
        string Sex;
    }
    student[] public Students;
    student public stu1=student("laughing",18,"b");
    student public stu2=student("fancen",19,"g");
    student public stu3=student({Name:"jim",Age:30,Sex:"g"});
    function SetStruct() public {
        Students.push(stu1);
        Students.push(stu2);
        Students.push(stu3);
        stu1.Name="Lif";
        
    }
    function ShowData()public view returns(string,uint8,string){
        return (stu2.Name,stu2.Age,stu2.Sex);
    }
}

18.mapping定义如下:

pragma solidity ^0.4.24;

contract Test{
    mapping(uint64 => string) public id_nums;
    constructor() public{
        id_nums[1]="hello";
        id_nums[2]="world";
    }
    function ShowData(uint64 id)public view returns(string){
        string storage tmp=id_nums[id];
        return tmp;
    }
}



//若mapping值不存在,则返回对应类型的空值

19.msg.sender是一个可变的值,谁调用msg.sender,msg.sender就是谁

20.在部署合约的时候,设置一个全局唯一的所有者,后面可以使用权限控制

21.msg.value可以获取合约的钱,函数使用了msg.value 就一定要把此函数修饰为payable

20.全局变量,如下:

pragma solidity ^0.4.24;


contract Test {
    
    bytes32 public blockhash1;
    address public coinbase;
    uint public difficulty;
    uint public gaslimit;
    uint public blockNum;
    uint public timestamp;
    bytes public calldata;
    uint public gas;
    address public sender;
    bytes4 public sig;
    uint public msgValue;
    uint public now1;
    uint public gasPrice;
    address public txOrigin;
    
    function tt () public payable {
        
        blockNum = block.number;// (uint)当前区块的块号。
        //给定区块号的哈希值,只支持最近256个区块,且不包含当前区块
        blockhash1 = blockhash(block.number - 1);
        coinbase = block.coinbase ;//当前块矿工的地址。
        difficulty = block.difficulty;//当前块的难度。
        gaslimit = block.gaslimit;// (uint)当前块的gaslimit。

        timestamp = block.timestamp;// (uint)当前块的时间戳。
        calldata = msg.data;// (bytes)完整的调用数据(calldata)。
        gas = gasleft();// (uint)当前还剩的gas。
        sender = msg.sender; // (address)当前调用发起人的地址。
        sig = msg.sig;// (bytes4)调用数据的前四个字节(函数标识符)。
        msgValue = msg.value;// (uint)这个消息所附带的货币量,单位为wei。
        now1 = now;// (uint)当前块的时间戳,等同于block.timestamp
        gasPrice = tx.gasprice;// (uint) 交易的gas价格。
        txOrigin = tx.origin;// (address)交易的发送者(完整的调用链)  
    }
}

22.require(A==B)和assert(a==b)的判断是真,才会执行,而revert是直接退出,需要在revert前提前判断好;

23.modify中的_;代表要修饰的真实代码,用法:只需在要修饰的函数名称public前加上modify名称即可

24.创建方法合约地址如下:

pragma solidity ^0.4.24;


contract T1{
    string public data;
    constructor(string input)public{
        data=input;
    }
    
}
contract T2{
    T1 public t1;
    function getValue()public returns(string){
        address add1=new T1("hello");
        t1=T1(add1);
        return t1.data();
    }
    
}


contract T3{
    T1 public t3=new T1("world");
    function getValue2()public view returns(string){
        return t3.data();
    }
}


contract T4{
    T1 public t4;
    function getValue3(address input) public returns(string){
        t4=T1(input);
        return t4.data();
    }
}

25.合约之间转账,使用T1.info.value.gas(500);

pragma solidity ^0.4.24;


contract T1{
    function info() payable public{
        
    }
    function getT1Value()public view returns(uint256){
        return address(this).balance;
    }
}

contract T2{
    T1 public t1;
    function getT2Value()public view returns(uint256){
        return address(this).balance;
    }
    function setContract(address add) public{
        t1=T1(add);
    }
    function callFeed()public{
        t1.info.value(5).gas(800)();
    }
    function() payable public{
        
    }
}

26.加密函数由sha3变为keccak256

pragma solidity ^0.4.24;


contract T1{
    function tes() public pure returns (bytes32){
        bytes memory dataBytes=abi.encodePacked("hello",uint256(1),"world");
        bytes32 hash=keccak256(dataBytes);
        return hash;
    }
}

27.solidity使用is进行继承,若出现多个继承,继承原则为,最远继承。

二、基于solidity进行eth发币:

pragma solidity ^0.4.24;

/**
 * Math operations with safety checks
 */
contract SafeMath {
  //internal > private 
    //internal < public
    //修饰的函数只能在合约的内部或者子合约中使用
    //乘法
  function safeMul(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a * b;
    //assert断言函数,需要保证函数参数返回值是true,否则抛异常
    assert(a == 0 || c / a == b);
    return c;
  }
//除法
  function safeDiv(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b > 0);
    uint256 c = a / b;
    //   a = 11
    //   b = 10
    //   c = 1
      
      //b*c = 10
      //a %b = 1
      //11
    assert(a == b * c + a % b);
    return c;
  }

    //减法
  function safeSub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    assert(b >=0);
    return a - b;
  }

  function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c>=a && c>=b);
    return c;
  }
}


contract HangTouCoin is SafeMath{
    
    string public name;
    string public symbol;
    uint8 public decimals;
    uint256 public totalSupply;
    
	address public owner;

    /* This creates an array with all balances */
    mapping (address => uint256) public balanceOf;
    
    
    //key:授权人                key:被授权人  value: 配额
    mapping (address => mapping (address => uint256)) public allowance;
    
    mapping (address => uint256) public freezeOf;

    /* This generates a public event on the blockchain that will notify clients */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /* This notifies clients about the amount burnt */
    event Burn(address indexed from, uint256 value);
	
	/* This notifies clients about the amount frozen */
    event Freeze(address indexed from, uint256 value);
	
	/* This notifies clients about the amount unfrozen */
    event Unfreeze(address indexed from, uint256 value);

    /* Initializes contract with initial supply tokens to the creator of the contract */
    
    //1000000, "HangTouCoin", "HTC"
     constructor(
        uint256 _initialSupply, //发行数量 
        string _tokenName, //token的名字 HTCoin
        //uint8 _decimalUnits, //最小分割,小数点后面的尾数 1ether = 10** 18wei
        string _tokenSymbol //HTC
        ) public {
            
        decimals = 18;//_decimalUnits;                           // Amount of decimals for display purposes
        balanceOf[msg.sender] = _initialSupply * 10 ** 18;              // Give the creator all initial tokens
        totalSupply = _initialSupply * 10 ** 18;                        // Update total supply
        name = _tokenName;                                   // Set the name for display purposes
        symbol = _tokenSymbol;                               // Set the symbol for display purposes
     
		owner = msg.sender;
    }

    /* Send coins */
    //某个人花费自己的币
    function transfer(address _to, uint256 _value) {
        if (_to == 0x0) throw;                               // Prevent transfer to 0x0 address. Use burn() instead
		if (_value <= 0) throw; 
        if (balanceOf[msg.sender] < _value) throw;           // Check if the sender has enough
        if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows
        
        balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);                     // Subtract from the sender
        balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value);                            // Add the same to the recipient
        emit Transfer(msg.sender, _to, _value);                   // Notify anyone listening that this transfer took place
    }

    /* Allow another contract to spend some tokens in your behalf */
    //找一个人A帮你花费token,这部分钱并不打A的账户,只是对A进行花费的授权
    //A: 1万
    function approve(address _spender, uint256 _value)
        returns (bool success) {
		if (_value <= 0) throw; 
        //allowance[管理员][A] = 1万
        allowance[msg.sender][_spender] = _value;
        return true;
    }
       

    /* A contract attempts to get the coins */
    function transferFrom(address _from /*管理员*/, address _to, uint256 _value) returns (bool success) {
        if (_to == 0x0) throw;                                // Prevent transfer to 0x0 address. Use burn() instead
		if (_value <= 0) throw; 
        if (balanceOf[_from] < _value) throw;                 // Check if the sender has enough
        
        if (balanceOf[_to] + _value < balanceOf[_to]) throw;  // Check for overflows
        
        if (_value > allowance[_from][msg.sender]) throw;     // Check allowance
           // mapping (address => mapping (address => uint256)) public allowance;
           
           
        balanceOf[_from] = SafeMath.safeSub(balanceOf[_from], _value);                           // Subtract from the sender
        
        balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value);                             // Add the same to the recipient
       
        //allowance[管理员][A] = 1万-五千 = 五千
        allowance[_from][msg.sender] = SafeMath.safeSub(allowance[_from][msg.sender], _value);
        emit Transfer(_from, _to, _value);
        return true;
    }

    function burn(uint256 _value) returns (bool success) {
        if (balanceOf[msg.sender] < _value) throw;            // Check if the sender has enough
		if (_value <= 0) throw; 
        balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);                      // Subtract from the sender
        totalSupply = SafeMath.safeSub(totalSupply,_value);                                // Updates totalSupply
        emit Burn(msg.sender, _value);
        return true;
    }
	
	function freeze(uint256 _value) returns (bool success) {
        if (balanceOf[msg.sender] < _value) throw;            // Check if the sender has enough
		if (_value <= 0) throw; 
        balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);                      // Subtract from the sender
        freezeOf[msg.sender] = SafeMath.safeAdd(freezeOf[msg.sender], _value);                                // Updates totalSupply
        Freeze(msg.sender, _value);
        return true;
    }
	
	function unfreeze(uint256 _value) returns (bool success) {
        if (freezeOf[msg.sender] < _value) throw;            // Check if the sender has enough
		if (_value <= 0) throw; 
        freezeOf[msg.sender] = SafeMath.safeSub(freezeOf[msg.sender], _value);                      // Subtract from the sender
		balanceOf[msg.sender] = SafeMath.safeAdd(balanceOf[msg.sender], _value);
        Unfreeze(msg.sender, _value);
        return true;
    }
	
	// transfer balance to owner
	function withdrawEther(uint256 amount) {
		if(msg.sender != owner)throw;
		owner.transfer(amount);
	}
	
	// can accept ether
	function() payable {
    }
}

三、前端知识学习

一、ES6、React语法复习

1.var 可以重定义、let 不能重定义但是可以修改变量,const 为常量,不能修改变量。所以,我们在ES6中建议使用let

2. 解构,如下:

//解构赋值  数组
let arr=[1,2,3,4,5];
let [a,b,c,d]=arr;
console.log(a,b,c,d);

//对象解构
let person={
    name:"zhangsan",
    age:"18",
};

let {name,age}=person;
console.log(name,age);

let {name:name1,age:age1}=person;
console.log(name1,age1);

function PrintPerson({name,age}) {
    console.log(`"name is :  "${name},"age is : "${age}`);
}
PrintPerson(person);

3.箭头函数以及函数扩展

//箭头函数
let tmp=function (a,b) {
    return a+b;
};
console.log(tmp(1,3));

let add=(a,b)=>{
    return a+b;
};
console.log(add(1,4));

let add1=(a,b)=>a+b;
console.log(add1(1,5));

//函数扩展
function PrintData(name,address="上海") {
    console.log(`姓名: ${name}  地址:  ${address}`)
}
PrintData("张三");
PrintData("李四","成都");

function PrintData1(name="么么哒",address) {
    console.log(`姓名: ${name}  地址:  ${address}`)
}
PrintData1("黄哥");
PrintData1("牛逼","杭州");

4.ES6类,与solodity非常相似

class Person {
    constructor(name1,name2){
        this.name1=name1;
        this.name2=name2;
    }
    SayHello(){
        console.log(`大家好我是${this.name1},我是${this.name2}`)
    }
}

let buleFun=new Person("张家辉","古天乐");
buleFun.SayHello();
class Person {
    constructor(name1,name2){
        this.name1=name1;
        this.name2=name2;
    }
    SayHello(){
        console.log(`大家好我是${this.name1},我是${this.name2}`)
    }
}
class Movie extends Person{
    constructor(name1,name2){//重构Persion属性
        super(name1,name2);
        this.actor1=name1;
        this.actor2=name2;
    }
    SayHello(){
        console.log(`666${this.name1},777${this.name2}`)
    }
}

let buleFun=new Person("渣渣辉","古天乐");
buleFun.SayHello();

let  Film=new Movie("扫毒","使徒行者");
Film.SayHello();

5.等同(==)、恒等(===)

例如:"1" == true   //类型不等,true会先转换成数值 1,现在变成 "1" == 1,再把"1"转换成 1,比较 1 == 1, 相等。

=赋值

==等于

===严格等于

二、Node.JS学习

5.Node.js中异步和同步 读取文件,同步会等主线程,异步不需要等待主线程,所以需要一个回调函数。

let fs=require('fs');
let filename='test.txt';
let data=fs.readFileSync(filename,'utf-8');
console.log(data);


fs.readFile(filename,'utf-8',function (err,data) {
   if (err){
       console.log(err);
   }
    console.log(data)
});

console.log("finish...");

6.require模块的使用(加载其他模块函数)使用module.export =ex={};如:

//文件export.js中
let SayHello=()=>{
  console.log("hello");
};

let SayHello2=(a,b)=>a+b;

module.exports=ex={
  SayHello,
  SayHello2,
};




//文件test.js中
let ex=require('./export.js');
ex.SayHello();
let tmp =ex.SayHello2(1,2);
console.log(tmp);

7.Node Path模块

let path=require('path');
//返回路径中代表文件夹的部分
let res=path.dirname("F:\\gopath\\pkg\\node\\test.js");
console.log(res);//F:\gopath\pkg\node

//规范化路径,
let res1=path.normalize("F:\\\\gopath\\/pkg\\node");
console.log(res1);//F:\gopath\pkg\node

//返回文件拓展名
let res2=path.extname("F:\\gopath\\pkg\\node\\test.js");
console.log(res2);//.js

//返回路径的最后一个部分
let res3=path.basename("F:\\gopath\\pkg\\node\\test.js");
console.log(res3);//test.js

//拼接路径
let res4=path.join("F:\\gopath\\pkg\\node","666/","777","888.js");
console.log(res4);//F:\gopath\pkg\node\666\777\888.js

//智能拼接,基于当前目录拼接某个部分,并返回
let res5=path.resolve("test.txt");
console.log(res5);//F:\gopath\pkg\node\test.txt

//用于将绝对路径转为相对路径,返回从 from 到 to 的相对路径(基于当前工作目录)。
let res6=path.relative("F:\\gopath\\pkg\\node\\test.js", "F:\\gopath\\pkg\\node\\export.js");
console.log(res6);

8.Node require 中的fs模块

let fs=require('fs');
let data=fs.readFileSync("test.txt","utf-8");
console.log(data);
fs.readFile("test.txt","utf-8",(err,data)=>{
   if (err){
       console.log(`读取失败 ${err}`)
   }
   console.log(`读取成功:  --> : ${data}`)
});


let n=fs.writeFileSync("./test2.txt",data,"utf-8");
console.log(n);


fs.writeFile("./test3.txt",data,"utf-8",(err)=>{
    if (err){
        console.log(`读取失败 ${err}`)
    }
    console.log(`写入成功:  -->`)
});
let info=fs.statSync("./test3.txt",);
console.log(info.isDirectory());


fs.unlinkSync("./test3.txt");


9.node.js实现删除文件夹

            一般的删除文件夹是使用方法fs.rmdir,但是这种方法不能删除里面有内容的文件夹,下面就用代码实现以下可以删除里面有东西的文件夹。这种方法的思路就是遍历文件夹,里面的内容如果是文件就直接删掉,如果是文件夹的话,就递归再次执行一次这个函数,参数就变成了这个文件夹,最后在把原本的文件夹删掉就ok了,这种方法最后打印结果也是在控制台打印的。

const fs=require("fs");
const p=require("path");
let path=p.join(__dirname,"./test2");
deleteFolder(path);
function deleteFolder(path) {
    let files = [];
    if( fs.existsSync(path) ) {
        files = fs.readdirSync(path);
        files.forEach(function(file,index){
            let curPath = path + "/" + file;
            if(fs.statSync(curPath).isDirectory()) {
                deleteFolder(curPath);
            } else {
                fs.unlinkSync(curPath);
            }
        });
        fs.rmdirSync(path);
    }
}

10.readFilePromise 处理复杂回调

let fs=require("fs");

let readFilePromise=new Promise(function (resolve, reject) {
   fs.readFile("./test.txt","utf-8",(err,data)=>{
       if (err){
           reject(err);
       }
       resolve(data);
   });
});

readFilePromise.then(res=>{
   console.log(res)
}).catch(err=>{
   console.log(err);
});

11.如果多次使用回调函数,则用async、await封装

let fs=require("fs");
let readFilePromise=()=>{
    return new Promise((resolve, reject) => {
       fs.readFile("./test.txt","utf-8",function (err,data) {
           if (err){
               reject(err);
           }
           resolve(data);
       })
    });
};

let writeFilePromise=(data)=>{
  return new Promise((resolve, reject) => {
     fs.writeFile("./test3.txt",data,"utf-8",function (err) {
         if (err){
             reject(err);
         }
         console.log("success...")
     })

  })
};

//async修饰函数, await修饰promise

let RunAsync=async ()=>{
    try {
        let data=await readFilePromise();
        console.log(data);
        await writeFilePromise(data);
    }catch (e) {
        console.log(e);
    }
};

RunAsync();

三、Node.JS与WEB3交互

为了方便演示,我们将创建一个react-app项目进行项目演示。

1.使用create-react-app创建react项目,并新增contracts文件夹进行合约管理。新增compile.js进行编译,新增deploy.js进行部署,新增instance.js进行创建合约实例,新增interface.js进行合约交互。

2.创建compile.js

//导入solc编译器
let solc = require('solc') ;//0.4.25

let fs = require('fs');

//读取合约
let sourceCode = fs.readFileSync('./contracts/SimpleStorage.sol', 'utf-8');

// Setting 1 as second paramateractivates the optimiser
let output = solc.compile(sourceCode, 1);

 //console.log('output :', output)

console.log('abi :', output['contracts'][':SimpleStorage']['interface']);

module.exports=output['contracts'][':SimpleStorage'];

3.创建deploy.js

//require过程中是转换json存储的,所以需要json.parse
let {bytecode,interface}=require("./01-compile");

// console.log(bytecode);
const account="0xa3E8DB71C969DeC7020609233Cae940957D3b750";

let Web3=require("web3");

let web3=new Web3();
web3.setProvider("HTTP://127.0.0.1:7545");


let contract=new web3.eth.Contract(JSON.parse(interface));
contract.deploy({
    data:bytecode,
    arguments:['HelloWorld']
}).send({
    from:account,
    gas: 1500000,
    gasPrice: '30000000000000'
}).then(instance=>{
    console.log("address is :%s",instance.options.address)
});





4.创建instance.js获取实例

let Web3=require('web3');
let web3=new Web3();
web3.setProvider('http://127.0.0.1:7545');
const account="0x7cB65819e1622Ada868cc802D293fb5b0f0B3943";
//获取abi和address
let fs=require('fs');

let abi=[{"constant":true,"inputs":[],"name":"myFunction","outputs":[{"name":"myNumber","type":"uint256"},{"name":"myString","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"}]
let contractInstance=new web3.eth.Contract(abi,account);
console.log("address:",contractInstance.options.address);
module.exports=contractInstance


5.创建interface.js进行合约交互

let instance = require('./03-instance');
const from='0xa3E8DB71C969DeC7020609233Cae940957D3b750';
let test = async () => {
    try {

        let res = await instance.methods.setValue('Hello HangTou').send({
            from: from,
            value: 0,
        })

        console.log('res:', res)

        let v2=await instance.methods.getValue().call()

        console.log('v2:', v2)
    } catch (e) {
        console.log(e)
    }
}

test()

6.下载infura连接真实环境,并获取EthApi地址:mainnet.infura.io/v3/2215d2c779474731b703ce908e8ae00e

7.安装truffle-hdwallet-provider,npm install [email protected]

8.获取账户地址的代码:

let accounts=await web3.eth.getAccounts();

9.重构deploy.js

//require过程中是转换json存储的,所以需要json.parse
let {bytecode,interface}=require("./01-compile");
let HdWallet=require('truffle-hdwallet-provider');

let Web3=require("web3");

let web3=new Web3();
// console.log(bytecode);
let memoryWords='poem laugh believe dizzy worth legal film laundry model earn judge film';
let ProviderIp='https://ropsten.infura.io/v3/2215d2c779474731b703ce908e8ae00e';
let provider=new HdWallet(memoryWords,ProviderIp);

web3.setProvider(provider);


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

let Run=async ()=>{
    try {
        let accounts = await web3.eth.getAccounts();
        let instance = await contract.deploy({
            data: bytecode,
            arguments: ['hhh']
        }).send({
            from: accounts[0],
            gas: '500000',
        });
        console.log("address is :%s", instance.options.address)
    } catch (e) {
        console.log(e)
    }

};
Run();

四、基于Node.js/solidity/react实现彩票项目

一、需求分析:

1.彩民点击投注后花费1ETH

2.管理员点击开奖后随机抽取一名彩民,将99%奖金转给该彩民。

3.管理员点击退款后,将1%奖金转给管理员

4.管理员点击退款后,将奖金转还给彩民

5.该过程不可作弊,可追溯,可详查。

二、概要设计

该项目采用(B/S架构),具体设计如下:

1.底层使用ETH智能合约实现该项目的数据存储、 (数据库模块)

2.使用React实现前端与用户的交互                        ( 前端模块)

3.使用node.js/web3实现对智能合约的控制和交互 ( 后台模块)

三、详细设计

一、编写lottery.sol实现智能合约

1.1定义变量和投注函数

1.1.1定义变量:管理员地址:address manager 、彩民池:address []plays、期数:roud

1.1.2定义play方法负责将投注的彩民赋值到彩民池中,若投注额度不是1eth,则报错

pragma solidity ^0.4.24;
contract Lottery {
    address manager;
    address [] plays;
    uint256 roud;
    constructor()public{
        manager=msg.sender;
    }
    
    function play() payable public {
        require(msg.value== 1 ether);
        plays.push(msg.sender);
        
    }
    
    function getBlance()public view returns(uint256){
        return uint256(address(this).balance);
    }
    
    function getPlays() public view returns(address[]){
        return plays;
    }
}

1.2.定义KaijJiang函数实现开奖函数

1.2.1 将(时间戳+挖矿难度+彩民数量)进行哈希赋值,作为随机数

1.2.2 将此随机数进行uint转后与彩民求余,得到一个一定小于彩民数量的uint值,该值即为要赋值的plays中的索引

1.2.3 将奖金赋值给中奖的彩民,并将期数roud+1

1.2.4清空plays数组


    function KaiJiang() public returns(address){
        bytes memory dataBytes=abi.encodePacked(block.timestamp,block.difficulty,plays.length);
        bytes32 hash=keccak256(dataBytes);
        uint256 roudInt=uint256(hash);
        uint256 index=roudInt%plays.length;
        address weinner=plays[index];
        uint256 money1=address(this).balance * 99 / 100;
        uint256 money2=address(this).balance - money1;
        weinner.transfer(money1);
        manager.transfer(money2);
        roud++;
        delete plays;
        return weinner;
    }

1.3定义TuiJiang函数实现退奖

1.3.1 循环palys数组进行退款

1.3.2 roud++

1.3.3 plays清空

13.4 定义modify函数,定kaiJiang和TuiJiang函数进行管理员限定

pragma solidity ^0.4.24;
contract Lottery {
    address public manager;
    address [] public plays;
    uint256 public roud;
    constructor()public{
        manager=msg.sender;
    }
    
    function play() payable public {
        require(msg.value== 1 ether);
        plays.push(msg.sender);
        
    }
    modifier OnlyManager(){
        require(msg.sender==manager);
        _;
    }
    
    function KaiJiang() OnlyManager public returns(address){
        bytes memory dataBytes=abi.encodePacked(block.timestamp,block.difficulty,plays.length);
        bytes32 hash=keccak256(dataBytes);
        uint256 roudInt=uint256(hash);
        uint256 index=roudInt%plays.length;
        address weinner=plays[index];
        uint256 money1=address(this).balance * 99 / 100;
        uint256 money2=address(this).balance - money1;
        weinner.transfer(money1);
        manager.transfer(money2);
        roud++;
        delete plays;
        return weinner;
    }
    
    function TuiJiang() OnlyManager public{
        for (uint256 i=0;i<plays.length;i++){
            plays[i].transfer(1 ether);
        }
        roud++;
        delete plays;
    }
    
    
    function getPlayBalance(address input)public view returns(uint256){
        return uint256(input.balance);
    }
    
    function getBlance()public view returns(uint256){
        return uint256(address(this).balance);
    }
    
    function getPlays() public view returns(address[]){
        return plays;
    }
}

二、创建lottey-react项目部署合约

目录和源码请点击github:https://github.com/lsy-zhaoshuaiji/lottey.git

1.准备工作

create-react-app lottey-react
cd lottery-react
npm install [email protected]
npm install web3
npm install [email protected]

//如果报错,就不要担忧,只要package.json里面有上述模块,且能使用就行

2.创建contracts目录存放合约(合约,如上述1.3.4)

2.1创建01-compile文件编译合约

let solc=require('solc');

let fs=require('fs');

let data=fs.readFileSync('./contracts/Lottery.sol','utf-8');

let output=solc.compile(data,1);
//console.log(output['contracts'][':Lottery']);

module.exports=output['contracts'][':Lottery'];

2.2创建utils目录存放iniWeb3.js获取调用者web3,特别注意新版本会引入授权问题

let Web3=require('web3');
let web3=new Web3();
let web3Provider;
if (window.ethereum) {
    web3Provider = window.ethereum;
    try {
        // 请求用户授权
        window.ethereum.enable().then()
    } catch (error) {
        // 用户不授权时
        console.error("User denied account access")
    }
} else if (window.web3) {   // 老版 MetaMask Legacy dapp browsers...
    web3Provider = window.web3.currentProvider;
}
web3.setProvider(web3Provider);//web3js就是你需要的web3实例

web3.eth.getAccounts(function (error, result) {
    if (!error)
        console.log(result,"")//授权成功后result能正常获取到账号了
});
module.exports=web3;

2.3.创建eth文件存放lottey.js获得合约实例

let web3=require('../utils/initWeb3');

let abi=[ { "constant": false, "inputs": [], "name": "KaiJiang", "outputs": [ { "name": "", "type": "address" } ], "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": "getBlance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "input", "type": "address" } ], "name": "getPlayBalance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "getPlays", "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": "plays", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "roud", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" } ]
let address='0xc31719a0644c7Aa430262Dcd585fC46B9BA987Ad';

let lotteryInstance=new web3.eth.Contract(abi,address);

console.log(lotteryInstance.options.address);

module.exports=lotteryInstance;

3.修改APP.JS文件,编写前端代码

3.1类中值传递使用this.state,设置值为this.setstate

3.2页面、组件之间传递值 使用 props,例如 <Test manager={this.state.manager} />;

3.3wei转ether         let balance=web3.utils.fromWei(ContractBalanceWei,'ether')

3.3ether转wei         let balance=web3.utils.to.Wei(1,'ether')

import React,{Component} from 'react';
import CardExampleCard from './display/ui'

let web3=require('./utils/initWeb3');
let lotteryInstance=require('./eth/lottery');


class App extends Component{
    constructor(){
        super();
        this.state={
            manager: '',
            round: '',
            winner: '',
            playerCounts: 0,
            balance: 0,
            currentAccount: '',
        }
    }
    async componentWillMount(){
        let accounts = await web3.eth.getAccounts();
        let manager = await lotteryInstance.methods.manager().call();
        let round= await  lotteryInstance.methods.roud().call();
        let winner= await  lotteryInstance.methods.winner().call();
        let ContractBalanceWei= await  lotteryInstance.methods.getBlance().call();
        let playerCounts= await  lotteryInstance.methods.playerCounts().call();
        let balance=web3.utils.fromWei(ContractBalanceWei,'ether');

        this.setState({
            manager,
            currentAccount: accounts[0],
            round,
            // players:plays,
            winner,
            balance,
            playerCounts,
        })
    }



    render(){
    return(
        <div>
            <CardExampleCard
                 manager={this.state.manager}
                 round={this.state.round}
                 winner={this.state.winner}
                 balance={this.state.balance}
                 playersCounts={this.state.playerCounts}
                 currentAccount={this.state.currentAccount}
            />
        </div>

    );
  }
}
// function App() {
//   return (
//     <h1>heelo world </h1>
//   );
// }

export default App;

3.4.修改/display/ui进行前端渲染

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

const CardExampleCard = (props) => (
    <Card>
        <Image src='/img/logo.jpg'/>
        <Card.Content>
            <Card.Header>黑马福利彩票</Card.Header>
            <Card.Meta>
                <p>管理员地址: {props.manager}</p>
                <p>当前地址: {props.currentAccount}</p>
            </Card.Meta>
            <Card.Description>每晚八点准时开奖, 不见不散!</Card.Description>
        </Card.Content>
        <Card.Content extra>
            <a>
                <Icon name='user'/>
                {props.playersCounts}人参与
            </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='#'>点击我查看交易历史</a>
            </Statistic>
        </Card.Content>

    </Card>
)

export default CardExampleCard
//import  es6

3.5调用投注方法

play=async ()=>{
        try {
            await lotteryInstance.methods.play().send({
                from: this.state.currentAccount,
                value: web3.utils.toWei('1', 'ether'),
                gas: '3000000',
            });
            alert('successfully')
            window.location.reload(true);
        } catch (e) {
            console.log(e)
        }
    };

3.6. 调用开奖/退奖

3.7 ui.js中html部分disable字段,可以表现为禁止效果,若disable=true则禁止,无法点击

3.8 使用  style={{display:props.isShowButton}} 隐藏或展示。

KaiJiang=async ()=>{
        try {
            this.setState({isClicked:true});
            await lotteryInstance.methods.KaiJiang().send({
                from: this.state.currentAccount,
                // value: web3.utils.toWei('1', 'ether'),
                gas: '3000000',
            });
            this.setState({isClicked:false});
            alert('开奖successfully');
            window.location.reload(true);
        } catch (e) {
            this.setState({isClicked:false});
            console.log(e)
        }
    };


html:

<Button inverted color='orange'  onClick={props.KaiJiang} disabled={props.isClicked} style={{display:props.isShowButton}}>

三、将项目部署到Ropsten测试网上

1.通过remix编译部署,不需要通过solc编译和contract.deploy

2.在react项目中导回合约地址即可使用

发布了20 篇原创文章 · 获赞 0 · 访问量 3797

猜你喜欢

转载自blog.csdn.net/Laughing_G/article/details/104192714