意义:
在上一节知识学习中,我们已经了解如何实现一个基础区块链,并重构了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项目中导回合约地址即可使用