帶你玩轉區塊鏈--以太坊基礎、發幣、基於智能合約實現彩票項目-第二章-第一節【以太坊篇】

意義:

        在上一節知識學習中,我們已經瞭解如何實現一個基礎區塊鏈,並重構了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項目中導回合約地址即可使用

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章