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