IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 区块链 -> 以太坊合约技术相关 -> 正文阅读

[区块链]以太坊合约技术相关

作者:33_solcfor_solidity_445

前言:创建创世文件genesis.json、初始化创世文件genesis.json 并 搭建私链。

1.创建创世区块配置文件 genesis.json

创建私链文件夹 PrivateChain,在其目录下创建 genesis.json文件,genesis.json文件配置内容如下:

{
  "nonce": "0x0000000000000042",
  "difficulty": "2000",
  "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "coinbase": "0x0000000000000000000000000000000000000000",
  "timestamp": "0x00",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
  "gasLimit": "2100000",
  "alloc": {
    "0x6E53739cBbDDAbEfC8B9070a6dEB49937bBD13F2": {
      "balance": "1337000000000000000000"
    },
    "0xB16807bCe0BAe252a9ad684f9bE7F09b215c9018": {
      "balance": "229070000000000000000"
    }
  },

  "config": {
    "chainId": 717,
    "homesteadBlock": 0,
    "eip150Block": 0,
    "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "eip155Block": 0,
    "eip158Block": 0,
    "byzantiumBlock": 0,
    "constantinopleBlock": 0,
    "petersburgBlock": 0,
    "istanbulBlock": 0,
    "ethash": {}
  }
}



2. 初始化创世文件genesis.json 并 搭建私链。


geth --datadir node1 init genesis.json
geth --datadir node2 init genesis.json
geth --datadir node3 init genesis.json

geth --datadir node1 --networkid 20200101 --http --http.api "db,eth,net,web3,personal" --http.addr 0.0.0.0 --http.port 8545 --http.corsdomain "*" --http.vhosts "*" --nodiscover  --port 30303  --allow-insecure-unlock --ipcdisable console
geth --datadir node2 --networkid 20200101 --http --http.api "db,eth,net,web3,personal" --http.addr 0.0.0.0 --http.port 8546 --http.corsdomain "*" --http.vhosts "*" --nodiscover  --port 30304  --allow-insecure-unlock --ipcdisable console
geth --datadir node3 --networkid 20200101 --http --http.api "db,eth,net,web3,personal" --http.addr 0.0.0.0 --http.port 8547 --http.corsdomain "*" --http.vhosts "*" --nodiscover  --port 30305  --allow-insecure-unlock --ipcdisable console

miner.start(); admin.sleepBlocks(1); miner.stop()

admin.nodeInfo.enode
admin.addPeer(" ")

一、solidty语言相关


// pragma 杂注版本  如:pragma solidity ^0.4.22 即 标注sol版本 >=0.4.22 <=0.5.0
// import XXX as XXX  引入模块或文件 as别名XXX


// address  地址类型定义  存储一个20 字节的值(以太坊地址大小,20字节 = 160位二进制)
/** 特别注意:1. 发送转移时候得 from to关系; 2. msg.sender是调用人地址; 3. address(this)在合约内部是当前合约得地址。
*
* address(0)  0地址转换
* address payable(v0.5.0引入,后合约不再是从地址派生) ?带payable得address地址类型,与普通地址类型基本相同,不过多出了transfer和send 两个成员变量,即可以交易。
* 可交易发送得address得地址类型 到 普通地址类型得转换需要惊醒 uint160得中间转换。  合约地址如果有payable定义则可以显示转换。
*
*
* address地址类型 成员变量 和 用法/底层函数调用:
*
* <address>.balance(uint256)  ?直接获取该地址的ether 余额,以Wei为单位。
*
* <addresspayable>.transfer(uint256amount)    ?向指定地址发送数量为amount 的ether(以Wei为单位),失败时抛出异常,发送2300 gas 的矿工费,不可调节
* <addresspayable>.send(uint256amount)  returns(bool)   ?向指定地址发送数量为amount的ether(以Wei为单位),失败时返回false,发送2300 gas 的矿工费用,不可调节
*
*
* <address>.call(bytesmemory)  returns(bool,bytesmemory)    ?发出底层函数CALL函数调用,失败时返回false,发送所有可用gas,可调节
* <address>.delegatecall(bytes memory)  returns(bool,bytesmemory)    ?发出底层函数DELEGATECALL受托代理调用函数,注意sender不为我方地址,失败时返回false,发送所有可用gas,可调节
* <address>.staticcall(bytes memory)  returns(bool,bytesmemory)  ?发出底层函数STATICCALL静态调用函数,失败时返回false,发送所有可用gas,可调节
*
*/



// uint  整型(int/uint):分别表示有符号和无符号的不同位数的整型变量;
/** 支持关键字uint8到uint256(无符号,从8 位到256 位)以及int8到int256,以8位为步长递增*/

// string  字符串类型定义utf-8编码 注意:需要使用 "" 来指定,如果参数里设置为string要注意其不限长度的弊端。 不可使用push()方法 。
// bytes32  定长字符数组,指定长度的32字节十六进制Hex表示(可替代string的不限长度弊端);变长字符数组不常用。
/*
*包含.length属性,返回数组长度(定长字符数组 长度指定即只读属性)
*注意:bytes字节数组如果需要显示成string需要进行 web3.toHex() 或 web3.toUtf8()等操作。
*/

// mapping(address ==> uint)  映射数据类型定义  {地址类型 对应映射 256整数类型},可以看作哈希表。
/* 映射账户地址的时候 注意不要public,因为地址太过庞大 */


// enum  枚举{}  从0开始递增得序列号,至少一个成员,一般用来模拟合约得状态。注意:如果想显示成uint需要类型转换uint(enum.***)则显示序列号。


// storage  存储型数据类型  永久存储。 注意:变长型得数据类型可以更改属性和元素;引用类型得局部变量默认存储storage;
// memory  内存型数据类型 临时存储。  注意:内存型数据类型.length长度等一旦指定不支持更改属性和元素; public公共参数默认存储内存memory;
/*
*函数参数(包括返回的参数)的数据位置默认是memory,局部变量的数据位置默认是storage,状态变量的数据位置强制是storage;
*还存在第三种数据位置,calldata,这是一块只读的,且不会永久存储的位置,用来存储函数参数;
*外部函数的参数(非返回参数)的数据位置被强制指定为calldata,效果跟memory差不多。
*
*数据位置总结
*
********强制指定的数据位置
*?外部函数的参数(不包括返回参数):calldata;
*?状态变量:storage
*
********默认数据位置
*?函数参数(包括返回参数):memory;
*?引用类型的局部变量:storage;  这一点需要格外注意,如函数内部引用得外部变量或结构体等。
****大坑:函数内部的局部变量必须赋值:如果可变数组局部变量不赋值则是指针,默认存储可变数组的长度,默认指针指向第一个storage变量,即 给第一个storage变量赋值添加该局部变量的长度。
*?值类型的局部变量:栈(stack)
*
********特别要求
*?公开可见(publicly visible)的函数参数一定是memory 类型,
*?如果要求是storage 类型则必须是private 或者internal 函数,这是为了防止随意的公开调用占用资源
*/




// 数组(Array)  数组可以在声明时指定长度如:T[K](定长数组),也可以动态调整大小如:T[](变长数组、动态数组) 或 5个动态数组元素得数组uint[][5]
/*
*包含属性 .length 和 方法.push() 和 .pop()方法。
*注意:添加/删除末尾新元素使用.push()/.pop()方法(不支持memory存储类型) 或 赋值.length属性变大(不支持memory存储类型);
*
*越界方法数组会导致调用失败回退;
*对于存储型(storage)的数组来说,数组元素类型可以是任意的(即元素也可以是数组类型,映射类型或者结构体);
*对于内存型(memory)的数组来说,数组元素类型不能是映射(mapping)类型;
*
*/




// struct() 构造结构体函数  支持通过构造结构体的形式定义新的类型。


// constructor()  构造函数 初始化函数  在合约contract实例化部署的时候会首先调用该constructor()构造函数来传入初始化参数构造实例。
/*注意:0.4.22版本以后引入constructor()构造函数,在之前的版本里构造函数通过设置和 contract 同名的 function函数来实现。*/



// funcition()  函数定义
/*
* 函数的可见性可以指定为external外部使用,public公共函数,internal内部使用或者private私有函数;
* 注意:对于状态变量,不能设置为external,默认是internal,当给状态变量public时的实质是追加了一个同名可见函数。
*
* 函数的使用位置类型(如将函数赋值到另一个函数内部的变量)有两类:-内部(internal)函数和外部(external)函数.
*
*?external:  外部函数 作为合约接口的一部分,意味着我们可以从其他合约和交易中调用。一个外部函数f不能从内部调用(即f不起作用,但this.f()可以)。当收到大量数据的时候,外部函数有时候会更有效率。
*?public:  公共函数 public 函数是合约接口的一部分,可以在内部或通过消息调用。对于public 状态变量,会自动生成一个getter 函数。
*?internal:  内部函数 这些函数和状态变量只能是内部访问(即从当前合约内部或从它派生的合约访问),不使用this调用,继承方式可直接调用。
*?private:  私密函数 private 函数和状态变量仅在当前定义它们的合约中使用,并且不能被派生合约使用。
*/




// payable  可支付类型定义,允许从消息调用中接受以太币Ehter。  注意:如果想调用合约同时转账到合约地址需要给withdraw()函数添加 payable属性
// view  只读类型定义,不允许修改状态。 注意:view类型定义只读函数,只读不写不调用合约。
// pure  纯函数类型定义,不允许修改或访问状态。 即 没有进行更改操作等,用于纯计算等内容。
// constant  与view相同,一般只修饰状态变量,不允许赋值(除初始化以外)


// returns()  定义函数返回内容
// public  公共类型定义,注意如果对变量定义为public则该变量可直接显示获取 即等同于添加了一个返回函数。
// origin_*  returns()定义函数返回内容的参数设置里 可以指定返回参数为指定别名 用于区别函数的参数名;

// event Fun() 事件定义 注意:编译器版本0.4.21以后事件监听需要启用emit,之前不需要。
// emit Fun() 启动事件 通常在函数内部最后启用,传入传输,用以在函数运行到最后启动该事件。


// require() 断言 条件需要为true,不满足条件则抛出异常。  require() 应用于确保满足有效条件(如输入或合约状态变量),或验证调用外部合约的返回值
// assert()  断言 条件必须为真,不能为False,否则错误异常触发。  assert() 一般只应用于测试内部错误,并检查常量



// 余额单位 和 时间单位 :
/*
* 1. Ehter余额单位:
* 以太币Ether单位之间的换算就是在数字后边加上wei、finney、szabo或ether来实现的,如果后面没有单位,缺省为Wei。例如2ether==2000finney的逻辑判断值为true
*
* 2. 时间单位:
* 秒是缺省时间单位,在时间单位之间,数字后面带有seconds、minutes、hours、days、weeks和years的可以进行换算,基本换算关系如下:
* ?1==1seconds  ?1minutes==60seconds  ?1hours==60minutes  ?1days==24hours  ?1weeks==7days  ?1years==365days
* 注意:时间后缀不能直接用在变量后边。如果想用时间单位(例如days)来将输入变量换算为时间,使用如下方式来完成:
* functionf(uintstart, uintdaysAfter) public{ if(now>= start + daysAfter * 1 days) { // ...} }
*/




// modifier 函数修饰器  在函数名称前面添加 modifier指定该函数位修饰器,函数内部需要指定下划线_来代指将要修饰的代码内容,即指定了修饰器的运行顺序。
/*
* 注意:modifier函数修饰器在调用的时候,需要放在要修饰函数的返回值returns()前面,要和类型设定使用一般。
* 参考:modifier代码示例: 函数修饰器onlySeller() 即 只允许合约创建者调用的修饰器。
*/



// () 回退函数fallback  特殊函数:没有名字,不能有参数也不能有返回值。
/*
* 注意:
* 1. 如果对一个合约进行函数调用,如果提供的调用函数在合约中没有设置,那么会调用该合约的fallback回退函数。
* 即:回退函数调用方式有两种:1.调用()空方法 或者 2.调用合约中没有的方法。
*
* 2. 每当合约收到转账(没有任何数据),回退函数就会执行。此外为了接受以太币,fallback函数必须标记为payable.
*(The Dao 硬分叉漏洞 即 寻找到了回退函数漏洞, 通过让他方账户 给 我方设置的合约账户发送转账 来 触发我方合约内设置的回退函数,继而触发他方回退函数继续给我方发送转账)
* 参考:回退函数_TheDao漏洞示例:
*/

二、remix在线创建、编译和部署合约

1. remix编辑代码时打开优化optimization
2. remix编译代码需要注意编译器版本,链上验证合约时需要注意填写正确的编译器版本否则验证不通过

3. 交易的gas费用预估 通过web3调用接口 eth.estimateGas({}) 来实现,示例如 : <br>
eth.estimateGas({ from: eth.accounts[0], to: eth.accounts[1], value: 10 })  //注意:是官方实现的预估实现,需要真实数据。


4. 链上交互 分为两种情况:发送交易 和 调用合约。

4.1 发送交易 传递value, 解析交易数据:
eth.sendTransaction({ from:  , to:  , value: })


4.2 调用合约 传递DATA内容,解析DATA数据:
DATA位32字节即64位的十六进制数据,其中:
DATA前8位为函数名称sha3()后的前8位十六进制编码,DATA后几位为函数参数的十六进制编码,中间空余的全部填充0。

即:发送DATA数据给合约地址,EVM会解释为函数调用,从payload 里解码出函数名称和参数,调用该函数并传入参数。

注意:
发送给合约的数据有效负载是32字节的十六进制序列化编码,如 :0x2e1a7d4d0000000000000000000000000000000000000000000000000000000000000064
即:调用 简单水龙头合约withdraw(100)  则: 函数:withdraw(uint256), 参数:100, 随后观察签名信息里的DATA会显示如下:
十六进制数据: 36 BYTES
0x2e1a7d4d0000000000000000000000000000000000000000000000000000000000000064
其中:

// 可在geth私链目录下打开geth测试链来测试验证: geth --datadir node_dev --dev console

4.2.1 函数选择器:函数原型 'withdraw(uint256)' 通过Keccak256哈希后的前4个字节前8位作为DATA的前面8位。
即:2e1a7d是函数名称 可通过 web3.sha3()求得:
web3.sha3('withdraw(uint256)')  // "0x2e1a7d4d13322e7b96f9a57413e1525c250fb7a9021cf91d1540d5b69f16a49f"

4.2.2 函数参数:函数参数 100 的十六进制表示作为DATA数据的后面几位。 即:64 是参数100的十六进制编码表示。
即:64是函数参数的十六进制表示,可通过 web3.toHex()求得:
web3.toHex(100)  //0x64

三、开发环境终端工具

node(js后端运行环境)、npm(node包管理器)、solc(终端命令行编译器)、ganache - cil(测试环境私链)、truffle(框架工具)

3.1 node(js后端运行环境)

参考教程:
https://www.runoob.com/nodejs/nodejs-install-setup.html


//windows安装node 官网下载地址:https://nodejs.org/zh-cn
node --version  // 验证版本


//ubuntu安装node:
sudo apt install nodejs
sudo apt install npm
node --version
npm --version


//升级node 和 npm
sudo apt install  n -g  //安装用于安装nodejs的n
sudo n latest  //通过n安装指定的nodejs:支持字段latest/stable/lts
sudo npm install npm@latest -g  //升级npm为最新版本
sudo node --version  //查看版本
sudo npm --version


//启动、变量赋值 和 变量输出
node  // 启动node交互式编辑器

//变量声明需要使用 var 关键字,如果没有使用 var 关键字变量会直接打印出来。
var x = 10  // undefined
x  // 10
y = 20  // 20  没有使用Var关键字将直接打印


//下划线(_)获取上一个表达式的运算结果
var x = 10
var y = 20
x + y
var sum = _
console.log(sum)  // 30


3.2 npm(node包管理器)


npm - v  // 查看版本/--version
npm install npm - g  // 更新升级npm版本

//升级node 和 npm
sudo apt install  n -g  //安装用于安装nodejs的n
sudo n latest  //通过n安装指定的nodejs:支持字段latest/stable/lts
sudo npm install npm@latest -g  //升级npm为最新版本
sudo node --version  //查看版本
sudo npm --version


//使用help帮助
npm help show  // 查看show命令的用法

//查看已安装的模块
npm list - g  // 查看所有全局安装的模块
npm list web3 - g  // 查看全局安装的web3模块的版本号,注意:全局安装不代表可以任意目录下require引用。
npm list web3  // 查看当前目录本地安装web3模块的版本号,注意:本地安装可在当前工作目录下require引用。

//查看服务器上注册的模块
npm show web3  // 或者使用 view、info
npm show web3 @0.20.1 dependencies  // 显示指定版本 依赖项 ronn版本包
npm view web3 repository.url  // 显示最新版本的git 存储库 URL

//查看包安装的目录
npm root


//npm的包安装分为本地安装(local)、全局安装(global)两种,安装命令的差别只是有没有-g而已,但使用方式有区别,一般不推荐全局安装,如果需要两种方式都安装。
npm install < Module Name >      //# 本地安装:将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),可工作目录下require()引入
   npm install < Module Name > -g   //# 全局安装:将安装包放在 /usr/local 下或者你 node 的安装目录,可在终端命令行使用,不可直接require()引入(需配置环境变量)

npm config set proxy null  // 解决安装模块出现错误:npm err! Error: connect ECONNREFUSED 127.0.0.1:8087



//npm淘宝镜像:
npm install - g cnpm --registry = https://registry.npm.taobao.org  // 然后就可以使用cnpm来完全代替npm

npm get registry  //显示当前镜像地址
npm config set registry http://registry.npm.taobao.org  //使用淘宝镜像地址
npm get registry  //查看当前镜像地址是否设置成功




//更新、探索、卸载模块
npm update < Module Name >  // 更新模块
npm search < Module Name >  // 探索模块
npm uninstall < Module Name >  // 卸载本地模块
npm uninstall < Module Name > -g  // 卸载全局模块


//创建模块:init自动生成必不可少的package.json 文件,内容包括:
npm init  // 初始化该创建模块目录,可添加参数 -y 默认初始化生成 package.json 文件,后期可以继续更改。

/*package.json文件内容解读:
Press ^C at any time to quit.
name: (node_modules) runoob                   # 模块名
version: (1.0.0)
description: Node.js 测试模块(www.runoob.com)  # 描述
entry point: (index.js)
test command: make test
git repository: https://github.com/runoob/runoob.git  # Github 地址
keywords:
author:
license: (ISC)
About to write to ……/node_modules/package.json:      # 生成地址*/

//发布模块:在npm资源库中注册用户、设置密码、然后发布模块。
npm adduser  // 设置用户名、密码、email
npm publish  // 发布模块



//智能合约工作环境准备:创建并切换合约工作目录contracts,使用 npm init 来初始化新的npm包管理环境,然后使用npm install下载独立的工具包:
//npm init
npm install solc -g  //安装后查
npm install ganache-cli
npm install truffle

3.3 solc(终端命令行编译器for solidity)


//全局安装solc模块,用于在命令行编译指定sol文件
npm install - g solc  // -g全局安装后可做命令行工具使用
solcjs  --version  // 查看solcjs版本  注意使用命令 solcjs 而不是solc

//编译sol文件
solcjs --abi Coin.sol  // 命令行里编译 Coin.sol文件 到当前目录下,生成abi文件json接口,格式:文件名称_文件格式_合约名称.abi
solcjs --bin Coin.sol  // 命令行里编译 Coin.sol文件 到当前目录下,生成bin文件字节码,格式:文件名称_文件格式_合约名称.bin

3.4 ganache-cil(测试环境私链)

//注意:如果是windows的shell运行可能存在策略组的问题,
//查看策略组情况:Get-ExecutionPolicy -List,
//更改策略组情况:Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope CurrentUser

//全局安装ganache-cli
npm install ganache-cli -g

//查看版本
ganache-cli --version

//启动
ganache-cli

//启动选项
-a 或 –accounts: 指定启动时要创建的测试账户数量。
-e 或 –defaultBalanceEther: 分配给每个测试账户的ether数量,默认值为100。
-b 或r –blockTime: 指定自动挖矿的blockTime,以秒为单位。默认值为0,表示不进行自动挖矿。
-d 或 –deterministic: 基于预定的助记词(mnemonic)生成固定的测试账户地址。
-n 或 –secure: 默认锁定所有测试账户,有利于进行第三方交易签名。
-m 或 –mnemonic: 用于生成测试账户地址的助记词。
-p 或 –port: 设置监听端口,默认值为8545。
-h 或 –hostname: 设置监听主机,默认值同NodeJS的server.listen()。
-s 或 –seed: 设置生成助记词的种子。.
-g 或 –gasPrice: 设定Gas价格,默认值为20000000000。
-l 或 –gasLimit: 设定Gas上限,默认值为90000。
-f 或 –fork: 从一个运行中的以太坊节点客户端软件的指定区块分叉。输入值应当是该节点旳HTTP地址和端口,例如http://localhost:8545。 可选使用@标记来指定具体区块,例如:http://localhost:8545@1599200。
-i 或 –networkId:指定网络id。默认值为当前时间,或使用所分叉链的网络id。
–db: 设置保存链数据的目录。如果该路径中已经有链数据,ganache-cli将用它初始化链而不是重新创建。
–debug:输出VM操作码,用于调试。
–mem:输出ganache-cli内存使用统计信息,这将替代标准的输出信息。
–noVMErrorsOnRPCResponse:不把失败的交易作为RCP错误发送。开启这个标志使错误报告方式兼容其他的节点客户端,例如geth和Parity。

特殊选项

–account: 指定账户私钥和账户余额来创建初始测试账户。可多次设置:
ganache-cli --account="<privatekey>,balance" [--account="<privatekey>,balance"]

注意私钥长度为64字符,必须使用0x前缀的16进制字符串。账户余额可以是整数,也可以是0x前缀的17进制字符串,单位为wei。
使用–account选项时,不会自动创建HD钱包。

-u 或 –unlock: 解锁指定账户,或解锁指定序号的账户。可以设置多次。当与–secure选项同时使用时,这个选项将改变指定账户的锁定状态:
ganache-cli --secure --unlock "0x1234..." --unlock "0xabcd..."

也可以指定一个数字,按序号解锁账号:
ganache-cli --secure -u 0 -u 1


将geth链接到ganache-cli,需要在另外一个终端里执行geth的attach子命令,链接到运行中的ganache-cli:
geth attach http://localhost:8545

3.5 mocha(测试工具)


npm install mocha -g  //全局安装
npm install mocha --save-dev  //开发环境依赖,也可以不指定依赖选项
mocha --version  //查看版本信息

mocha ./test.js  //执行测试文件test.js

mocha  //不带命令,则默认执行测试当前目录下的 test目录 内的所有测试文件


//示例参考 测试脚本 内容
describe('name', () => {
    beforeEach(()=>console.log('当前小次测试开始.'))  //每一次组内小次测试开始前执行
    afterEach(()=>console.log('当前小次测试完成.'))  //每一次组内小次测试完成后执行
    after(()=>console.log('全部测试完成。'))  //全部测试完成后执行
    before(()=>console.log('全部测试开始...'))  //全部测试开始前执行,不局限代码位置。

    it('should has initial brand', async () => {
        const brand = await contract.methods.brand().call();  // 注意:await阻塞调用合约方法获取brand
        assert.equal(brand, initialBrand);  // 断言初始brand = 构造函数参数传入的brand
    });

    // ...
})

3.6 truffle(框架工具)

待更新

四、web3库


//web3.js_0.2手册:http://cw.hubwiz.com/card/c/web3.js/1/1/1/
//web3.js_1.0手册:http://cw.hubwiz.com/card/c/web3.js-1.0/

在geth客户端里web3库有很多工具包可以使用, 如 HEX十六进制与Decimal十进制的转换:web3.toHex(.) 和 web3.toDecimal(0x..) 等。

//注意:geth客户端里内置的web3类型截止目前(20211010)仍然为0.20.1,目前更新的web3版本为1.6

//创建两个不同版本的web3工作目录,如:testWeb3_0.20.1  和 testWeb3_1.6,然后来分别安装不同版本的web3尝试操作。
//npm init
npm install web3 @0.20.1
npm install web3  //将安装最新版本的web3,如果windows下安装不顺畅则使用: cnpm install web3

npm install solc  //当前工作目录中安装solc,用于在当前工作目录内打开的node环境的文件中编译读取到的sol内容,注意版本问题 @0.4.24
npm list solc  //查看当前目录安装版本等信息
//注意版本问题:npm uninstall solc; npm install solc@0.4.24

npm install solc - g  // 全局安装solc模块,用于在命令行编译sol文件
npm list solc -g  // 查看全局安装版本等信息
solcjs--version  // 查看solcjs版本  注意使用命令 solcjs 而不是solc
solcjs --abi Coin.sol  // 命令行里编译 Coin.sol文件 到当前目录下,生成abi文件json接口,格式:文件名称_文件格式_合约名称.abi
solcjs --bin Coin.sol  // 命令行里编译 Coin.sol文件 到当前目录下,生成bin文件字节码,格式:文件名称_文件格式_合约名称.bin

npm install ganache-cli -g  //模拟区块链环境
ganache-cli --version  //查看版本信息
ganache-cli //打开模拟区块链环境

//web3模块加载
var Web3 = require('web3')  // 引入web3模块
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))  // 连接web3的节点,即创建web3对象

//web3环境检查
if (typeof web3 !== 'undefined') {
   web3 = new Web3(web3.currentProvider);
} else {
   web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}


//注意:1.0以上版本全部升级为异步版本,直接可以链式调用then()使用。

//网络状态查询
web3.isConnected()   //? 同步:查看是否连接到节点; 或者 web3.net.listening
web3.net.getListening((err,res)=>console.log(res))  //? 异步:查看是否连接到节点;
web3.eth.net.isListening().then(console.log)  // 1.0版本查看是否连接到节点;


//获取 network id
web3.version.network  // ? 同步:
web3.version.getNetwork((err, res)=>{console.log(res)})  //? 异步:
web3.eth.net.getId().then(console.log)  //? v1.0.0:


//获取节点的以太坊协议版本
web3.version.ethereum  //? 同步:  测试网禁用
web3.version.getEthereum((err, res)=>{console.log(res)})  //? 异步:  测试网禁用
web3.eth.getProtocolVersion().then(console.log)  //? v1.0.0:  测试网禁用


//当前连接的peer节点
web3.net.peerCount  //? 同步:
web3.net.getPeerCount((err,res)=>console.log(res))  //? 异步:
web3.eth.net.getPeerCount().then(console.log)  // ? v1.0.0:


//Provider 节点客户端
//查看当前设置的 web3 provider
web3.currentProvider

//查看浏览器环境设置的 web3 provider(v1.0.0)
web3.givenProvider

//设置更换 provider  :web3.setProvider(provider)
web3.setProvider(new web3.providers.HttpProvider('http://localhost:8546'))  //更换到8546端口的节点客户端




//web3通用工具方法



//大数处理
var BigNumber = require('bignumber.js')  // 引入bignumber.js模块 注意:引入的名称要和node_modules包文件里的名称一致
var balance = new BigNumber("6546546541679746513213203654846541546879684654635135153464")  // 定义一个大数
balance  // s:代表符号正数为1负数为-1;  e:代表科学计数法位数; c:代表所有有效数字的数组[,]每14位切割一次作为数组元素。
balance.toString()  // 转换字符串 默认科学记数法显示
balance.toString(10)  // 转换字符串 十进制格式  注意:10进制的转换字符串小数位只保留20位。推荐内部总是用 wei 来表示余额(大整数),只有在需要显示给用户看的时候才转换为ether或其它单位
balance.toString(16)  // 转换字符串 十六进制格式


//以太单位转换
//1.0版本全部放在web3.utils模块内
web3.fromWei(323452348972923840980920389,'ether')
web3.utils.fromWei('323452348972923840980920389','ether')   //? v1.0.0:

web3.toWei(1,'ether')
web3.utils.toWei('1','ether')  //? v1.0.0:

//数据类型转换 (对象方法)
web3.toString([10,16])
web3.toDecimal()
web3.toBigNumber()

//字符编码转换(对象方法)
web3.toHex()
web3.toAscii()
web3.toUtf8()
web3.fromUtf8()

//地址相关
web3.isAddress('0x23f511D961955fB78c294D5dEAEB3d16F3e8DB3f')
web3.utils.isAddress('0x23f511D961955fB78c294D5dEAEB3d16F3e8DB3f')  //? v1.0.0:

web3.toChecksumAddress('0x23F511D961955Fb78c294D5dEAEB3d16F3e8DB3f')  // 转换大小写字母为标准地址字母
web3.utils.toChecksumAddress('0x23F511D961955Fb78c294D5dEAEB3d16F3e8DB3f')  // 转换大小写字母为标准地址字母  //? v1.0.0:



//web3.eth – 账户相关
//旷工coinbase 查询
web3.eth.coinbase  //? 同步:
web3.eth.getCoinbase( (err, res)=>console.log(res) )  //? 异步:
web3.eth.getCoinbase().then(console.log)  //? v1.0.0:

//账户查询
web3.eth.accounts  //? 同步:
web3.eth.getAccounts( (err, res)=>console.log(res) )  //? 异步:
web3.eth.getAccounts().then(console.log)  //? v1.0.0:
(async () => {const accounts = await web3.eth.getAccounts();console.log(accounts[0])})()  //v1.6版本异步获取账户0


//区块相关

//区块高度查询
web3.eth.blockNumber  //? 同步:
web3.eth.getBlockNumber( (err, res)=>console.log(res) )  //? 异步:
web3.eth.getBlockNumber().then(console.log);  //? v1.0.0:

//gasPrice 查询
web3.eth.gasPrice  //? 同步: 默认BN大数格式,可直接后面使用 .toString(10) 转换为十进制
web3.eth.getGasPrice( (err, res)=>console.log(res.toString(10)) )  //? 异步:   .toString(10) 转换为十进制
web3.eth.getGasPrice().then(console.log);   //? v1.0.0:

//区块查询
//web3.eth.getBlock( hashStringOrBlockNumber[ ,returnTransactionObjects] )  //? 同步:  可选参数当设置为true时,返回块中将包括所有交易详情,否则仅返回交易哈希。
//web3.eth.getBlock( hashStringOrBlockNumber, callback )  //? 异步:
web3.eth.getBlock(25)
web3.eth.getBlock(25,(err, res)=>console.log(res) )  //? 异步:
web3.eth.getBlock(25).then(console.log);  //? v1.0.0:



//块中交易数量查询
//web3.eth.getBlockTransactionCount( hashStringOrBlockNumber )  //? 同步:
//web3.eth.getBlockTransactionCount( hashStringOrBlockNumber, callback )  //? 异步:
web3.eth.getBlockTransactionCount(25)
web3.eth.getBlockTransactionCount("0x407d73d8a49eeb85d32cf465507dd71d507100c1").then(console.log);  //? v1.0.0:



//交易相关
//余额查询
//web3.eth.getBalance(addressHexString [, defaultBlock])  //? 同步:
//web3.eth.getBalance(addressHexString [, defaultBlock][, callback])  //? 异步:
web3.eth.getBalance(web3.eth.accounts[0], 100)  //测试链指定太靠前区块查询余额时有BUG,最近15个区块内可查。
web3.eth.getBalance("0x23f511D961955fB78c294D5dEAEB3d16F3e8DB3f").then(console.log);  //? v1.0.0:


//交易查询
web3.eth.getTransaction(transactionHash)  //? 同步:
web3.eth.getTransaction(transactionHash [, callback])  //? 异步:


//交易执行相关
//? 交易收据查询(已进块)
web3.eth.getTransactionReceipt(hashString)  //? 同步:
web3.eth.getTransactionReceipt(hashString [,callback])  //? 异步:

//? 估计 gas 消耗量
//web3.eth.estimateGas(callObject)  //? 同步:  参数:callObject:Object - 交易对象,其from属性可选
//web3.eth.estimateGas(callObject [, callback])  //? 异步: 参数:callObject:Object - 交易对象,其from属性可选
web3.eth.estimateGas({to: "0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe",
                      data: "0xc6888fa10000000000000000000000000000000000000000000000000000000000000003"}
                      ).then(console.log);  //? v1.0.0:  去掉.then链式则为0.2版本方法


//发送交易
web3.eth.sendTransaction(transactionObject [, callback])  //
/*? 交易对象:
? from:发送地址
? to:接收地址,如果是创建合约交易,可不填
? value:交易金额,以wei为单位,可选
? gas:交易消耗 gas 上限gaslimit,可选
? gasPrice:交易 gas 单价,可选
? data:交易携带的字串数据,可选,如果是合约发送data则注意函数选择器内容十六进制头部为函数名称sha后的前8位,后面几位为合约参数的十六进制。
? nonce:整数 nonce 值,可选
返回:交易哈希*/

web3.eth.getBalance(web3.eth.accounts[0]).toString()  //首先查询账户余额情况
web3.eth.getBalance(web3.eth.accounts[1]).toString()
web3.personal.unlockAccount(web3.eth.accounts[0])  //解锁账户才能进行操作
//web3.personal.unlockAccount(web3.eth.accounts[0],"******")

web3.eth.sendTransaction({
    from: web3.eth.accounts[0],
    to: web3.eth.accounts[1],
    value: '1000000000000000'})

web3.eth.getBalance(web3.eth.accounts[1]).toString()  //复查账户余额情况,pow需要挖矿



//消息调用
web3.eth.call(callObject [, defaultBlock] [, callback])  //
/*? 参数:
? 调用对象:与交易对象相同,只是from也是可选的
? 默认区块:默认“latest”,可以传入指定的区块高度
? 回调函数,如果没有则为同步调用*/
web3.eth.call({to:"0xc4abd0339eb8d57087278718986382264244252f",
               data:"0xc6888fa10000000000000000000000000000000000000000000000000000000000000003" }
             ).then(console.log);  //? v1.0.0:  去掉.then链式则为0.2版本方法







//日志过滤(事件监听)//参考网址:http://cw.hubwiz.com/card/c/web3.js/1/6/29/  注意日志过滤的方法有两种:字符串(latest/pending)或者 条件过滤。

//web3.eth.filter( filterOptions [ , callback ] )  // 0.20版本
//参数:filterString 可以是 'latest'出块 or 'pending'针对交易的发送状态 ,或者可以填入一个日志过滤 options  即://var filter = web3.eth.filter(options);

//var filter = web3.eth.filter('latest')
var filter = web3.eth.filter('pending')  //"pending"字段检测一笔交易的发送状态,即监听开启后可收到交易发送的信息推送

// 事件监听对象返回一个过滤对象,该过滤对象包含以下方法:
/*
Filter.get(callback): 返回满足过滤条件的日志。
Filter.watch(callback): 监听满足条件的状态变化,满足条件时调用回调。
Filter.stopWatching(): 停止监听,清除节点中的过滤。你应该总是在监听完成后,执行这个操作。
*/

// 监听日志变化:返回区块哈希
filter.watch((err, res)=>console.log(res));
//filter.watch(function(error, result){ if (!error) console.log(result);});

// 还可以用传入回调函数的方法,立刻开始监听日志
web3.eth.filter(options, function(error, result){if (!error) console.log(result);});

// 返回满足过滤条件的日志
filter.get((err, res)=>console.log(res));
//filter.get(function(error, result){ if (!error) console.log(result); });

// 停止监听
filter.stopWatching()







//合约相关 —— 创建合约
//参考视频:https://www.bilibili.com/video/BV1NJ411D7rf?p=54&spm_id_from=pageDriver

//web3.eth.contract

// 通过ABI创建交互json接口
//var MyContract = web3.eth.contract(abiArray);
var ABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Sent","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[],"stateMutability":"nonpayable","type":"function"}]

var myContract = web3.eth.contract(ABI)


// 已部署的合约通过地址初始化合约实例
//var contractInstance = MyContract.at(address);

// 未部署的合约通过.new()部署一个新合约
//var contractInstance = MyContract.new([constructorParam1] [, constructorParam2], {data: '0x12345...', from:myAccount, gas: 1000000});
//参数constructorParam: 合约的构造函数中的参数
//参数{}:data内容为byteCode字节码;  注意:通过solcjs --bin File.sol  编译的bin字节码文件的内容格式为十六进制,但是没有0x头指定,需要手动添加如:var bin = '0x'+"3151321*****"

var BIN='0x'+'608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061057c806100606000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063075461721461005157806327e235e31461006f57806340c10f191461009f578063d0679d34146100bb575b600080fd5b6100596100d7565b604051610066919061033d565b60405180910390f35b61008960048036038101906100849190610389565b6100fb565b60405161009691906103cf565b60405180910390f35b6100b960048036038101906100b49190610416565b610113565b005b6100d560048036038101906100d09190610416565b6101c5565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60016020528060005260406000206000915090505481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461016b57600080fd5b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101ba9190610485565b925050819055505050565b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561021157600080fd5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461026091906104db565b9250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546102b69190610485565b925050819055507f3990db2d31862302a685e8086b5755072a6e2b5b780af1ee81ece35ee3cd33453383836040516102f09392919061050f565b60405180910390a15050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610327826102fc565b9050919050565b6103378161031c565b82525050565b6000602082019050610352600083018461032e565b92915050565b600080fd5b6103668161031c565b811461037157600080fd5b50565b6000813590506103838161035d565b92915050565b60006020828403121561039f5761039e610358565b5b60006103ad84828501610374565b91505092915050565b6000819050919050565b6103c9816103b6565b82525050565b60006020820190506103e460008301846103c0565b92915050565b6103f3816103b6565b81146103fe57600080fd5b50565b600081359050610410816103ea565b92915050565b6000806040838503121561042d5761042c610358565b5b600061043b85828601610374565b925050602061044c85828601610401565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610490826103b6565b915061049b836103b6565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156104d0576104cf610456565b5b828201905092915050565b60006104e6826103b6565b91506104f1836103b6565b92508282101561050457610503610456565b5b828203905092915050565b6000606082019050610524600083018661032e565b610531602083018561032e565b61053e60408301846103c0565b94935050505056fea26469706673582212208b41e31e83edf02c534595cebe3107ed49f266d316a53185f4cefacee9be4c5664736f6c63430008090033'
var dataObj={from:web3.eth.accounts[0], data:BIN, gas:1000000}
var myContractInstance=myContract.new(dataObj)  //部署Coin合约。返回哈希。 该Coin合约没有构造函数,所以只春如data对象就可以,否则需要首先传入构造函数的参数。

//部署合约后 直接查看myContractInstance 或者 可以查看返回的哈希
myContractInstance  //没有挖矿前部署尚未完成地址为空,挖矿后部署完成可查看地址。
web3.eth.getTransaction("0xc1daa95d0af7aeeed095993b39cd13d0107ffe4c0ddefce9429b0d60557fd18d")
myContractInstance.address  //挖矿后部署完成可查看地址。  返回PC1地址:'0xbeeb1a5f1decc75d20d2f75fb964199ba4c8e4e9'  ;或者 Dell地址:'0xe9dc504a4da9d543dcebf9ab31e21d433c09f221',



//注意:如果没有解锁账户无法交互
web3.personal.unlockAccount(web3.eth.accounts[0])  //解锁账户才能进行操作
//web3.personal.unlockAccount(web3.eth.accounts[0],"******")





//调用合约函数
//特别注意:在web3.js中调用合约函数要在函数参数中添加dataObj对象 {from:0x46546***} 来指定由谁来发送,否则显示错误。

//? 可以通过已创建的合约实例:1、.myMethod()直接调用合约函数(只读方法私链不适用) ;2、.myMethod.call()显示调用只读函数;3、.myMethod.sendTransaction()显示发送交易调用函数

// 1、.myMethod()直接调用,自动按函数类型决定用 sendTransaction 还是 call, 实测私链环境不能自动调用只读函数需要通过.call调用view函数。
//myContractInstance.myMethod(param1 [, param2, ...] [,transactionObject] [, defaultBlock] [, callback]);

// 2、.myMethod.call()显式以消息调用形式 call 该函数(用于只读函数,不改变状态变量)
//myContractInstance.myMethod.call(param1 [, param2, ...] [,transactionObject] [, defaultBlock] [, callback]);

// 3、.myMethod.sendTransaction()显式以发送交易形式调用该函数(用于写入函数,改变状态变量)
//myContractInstance.myMethod.sendTransaction(param1 [,param2, ...] [, transactionObject] [, callback]);

//实例如:

myContractInstance.minter({from:web3.eth.accounts[0]})  //直接调用Coin合约中的minter()方法,如果再web3中不加入dataObj对象的from内容会报错。如果不显式.call调用则只返回哈希。
myContractInstance.minter.call({from:web3.eth.accounts[0]})  //直接调用Coin合约中的minter()方法,如果再web3中不加入dataObj对象的from内容会报错。如果不显式.call调用则只返回哈希。

myContractInstance.balances.call(web3.eth.accounts[0], {from:web3.eth.accounts[0]}) //只读函数注意使用.call,实测私链环境不能自动调用只读函数需要通过.call调用view函数。

myContractInstance.mint(web3.eth.accounts[0], 5555555555,{from:web3.eth.accounts[0]})  //写入函数可以直接调用
myContractInstance.mint(web3.eth.accounts[0], 5555555555,{from:web3.eth.accounts[0]}, (err, res)=>console.log(res))  //异步
myContractInstance.mint.sendTransaction(web3.eth.accounts[0], 5555555555,{from:web3.eth.accounts[0]}, (err, res)=>console.log(res))  //异步

myContractInstance.balances.call(web3.eth.accounts[0], {from:web3.eth.accounts[0]},(err, res)=>console.log(res)) //只读函数注意使用.call,实测私链环境不能自动调用只读函数需要通过.call调用view函数。
myContractInstance.balances.call(web3.eth.accounts[1], {from:web3.eth.accounts[0]},(err, res)=>console.log(res)) //只读函数注意使用.call,实测私链环境不能自动调用只读函数需要通过.call调用view函数。

myContractInstance.send(web3.eth.accounts[1], 12345, {from:web3.eth.accounts[0]})

myContractInstance.balances.call(web3.eth.accounts[1], {from:web3.eth.accounts[0]},(err, res)=>console.log(res)) //只读函数注意使用.call,实测私链环境不能自动调用只读函数需要通过.call调用view函数。
myContractInstance.balances.call(web3.eth.accounts[0], {from:web3.eth.accounts[0]},(err, res)=>console.log(res)) //只读函数注意使用.call,实测私链环境不能自动调用只读函数需要通过.call调用view函数。








//监听合约事件

//? 合约的 event事件 类似于 filter过滤器,可以设置过滤选项来监听

// 创建事件
//var event = myContractInstance.MyEvent({valueA: 23} [, additionalFilterObject])
var event = myContractInstance.Sent("pending")


// 监听事件
event.watch((err, res)=>console.log(res));
//event.watch(function(error, result){ if (!error) console.log(result); });


// 创建事件(如果传入回调函数则)立即监听。
var event1 = myContractInstance.Sent([{valueA: 23}] [, additionalFilterObject] , function(error, result){if (!error) console.log(result);});



五、web3.js脚步编写实现:自动发送交易 、转发代币等交互和监听合约(script)

5.1 自动发送交易脚本


var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))

var _from = web3.eth.accounts[0]
var _to = web3.eth.accounts[1]
var _value = 5000000

web3.eth.sendTransaction({from:_from, to:_to, value:_value}, (err,res)=>console.log(res))

5.2 自动转发代币脚本

// autoSendTokenFromArgumentsoS.js

var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))

var arguments = process.argv.splice(2)  //命令行输入参数切割指定从索引2开始切割。注意索引0为 node,索引1为文件名称
if (!arguments || arguments.length != 2){
         console.log('Error: Parameter error:must be 2 ')
    }  //异常处理

var _from = web3.eth.accounts[0]  //发送方指定为第一个账户
var _to = arguments[0]  //接收方通过命令行指定
var amount = arguments[1]  //发送数量通过命令行指定

if (web3.isAddress(_to) != true) {
        console.log('Error: You must give me a Address in The arguments parameter 3')
        return
    }  //接收方地址检查

//var _from = web3.eth.accounts[0]
//var _to = web3.eth.accounts[1]
//var _value = 5000000

var coinAbi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Sent","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[],"stateMutability":"nonpayable","type":"function"}]
var coinAddress = '0xe9dc504a4da9d543dcebf9ab31e21d433c09f221'
var coinContract = web3.eth.contract(coinAbi)
var coinInstance = coinContract.at(coinAddress)


//web3.personal.unlockAccount(_from,"******",3600, (err,res)=>{
//    if (err){
//        console.log('Error:',err)
//        }
//    else
//        coinInstance.send(_to, amount, {from:_from},(err,res)=>console.log(res))
//    }
//)  //可升级控制台解锁,解锁账户注意:开启控制台需要指定--http.api "web3, eth, personal"

coinInstance.send(_to, amount, {from:_from},(err,res)=>console.log(res))

5.3 自动查询代币余额

// autoGetAmountOfTokenFromArgumentsoS.js

var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))

var arguments = process.argv.splice(2)  //命令行输入参数切割指定从索引2开始切割。注意索引0为 node,索引1为文件名称
if (!arguments || arguments.length != 1){
         console.log('Error: Parameter error:must be 1 ')
    }  //异常处理

var _addr = arguments[0]  //接收方通过命令行指定

if (web3.isAddress(_addr) != true) {
        console.log('Error: You must give me a Address in The arguments parameter 3')
        return
    }  //接收方地址检查

var coinAbi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Sent","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[],"stateMutability":"nonpayable","type":"function"}]
var coinAddress = '0xe9dc504a4da9d543dcebf9ab31e21d433c09f221'
var coinContract = web3.eth.contract(coinAbi)
var coinInstance = coinContract.at(coinAddress)

coinInstance.balances.call(_addr, (err,res)=>console.log(res))

5.4 监听执行脚本

// scriptForEvent.js  //node scriptForEvent.js

var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))

var coinAbi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Sent","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[],"stateMutability":"nonpayable","type":"function"}]
var coinAddress = '0xe9dc504a4da9d543dcebf9ab31e21d433c09f221'
var coinContract = web3.eth.contract(coinAbi)
var coinInstance = coinContract.at(coinAddress)

coinInstance.Sent('latest', (err,res)=>console.log("Sent Event occurs: ",res))  //传入回调函数 即 立即开始监听。

六、简单投票Dapp_Voting编写+编译+部署+测试+简单前端 的工作流程

6.1 编写合约Voting

// in the workdir: Voting.sol
pragma solidity >=0.4.22;

contract Voting {
    mapping(bytes32 => uint8) public votesReceived;  //映射字典:候选人映射得票数:{候选人名称: 票数}
    bytes32[] public candidateList;  //候选人列表

    //构造函数:参数传入候选人列表名称
    constructor(bytes32[] candidateNames) public {
        candidateList = candidateNames;
    }

    //查询候选人的总得票数
    function totalVotesFor(bytes32 candidate) view public returns (uint8) {
        //声明参数候选人存在候选人列表为有效
        require(validCandidate(candidate));
        return votesReceived[candidate];
    }

    //投票给参数候选人
    function voteForCandidate(bytes32 candidate) public {
        //声明参数候选人存在候选人列表为有效
        require(validCandidate(candidate));
        votesReceived[candidate] += 1;
    }

    //有效候选人检查,是否存在候选人列表
    function validCandidate(bytes32 candidate) view public returns (bool) {
        for (uint i = 0; i < candidateList.length; i++) {
            if (candidateList[i] == candidate) {
                return true;
            }
        }
        return false;
    }
}

//["0x123b", "0x3454", "0xe324"]

6.2 编译部署交互合约Voting

//in the node
var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
web3.eth.accounts
var code = fs.readFileSync('Voting.sol').toString()  //读取合约内容,转换为string类型。  注意:fs为node里的内置模块

//编译读取到的合约内容
var solc = require('solc')
var compiledCode = solc.compile(code)  //文件内编译读取到的合约内容string.  注意版本问题:npm uninstall solc; npm install solc@0.4.24;npm install solc@0.4.26;

//部署合约
var abi = JSON.parse(compiledCode.contracts[':Voting'].interface)  //提取编译合约后的abi接口
var byteCode = compiledCode.contracts[':Voting'].bytecode  //提起编译合约后的byteCode字节码
var votingContract = web3.eth.contract(abi)  //合约变量赋值
var deployObj = {from: web3.eth.accounts[0], data: byteCode, gas: 3000000}  //合约交易对象赋值
var votingInstance = votingContract.new(['Alice','Bob', 'Cary'], deployObj)  //部署合约:传入构造函数参数,传入合约交易对象。

//交互合约测试
votingInstance.voteForCandidate('Alice', {from: web3.eth.accounts[0] })  //执行合约方法:投票给Alice
//votingInstance.voteForCandidate.sendTransaction('Alice', {from: web3.eth.accounts[0] })  //执行合约方法:投票给Alice

votingInstance.totalVotesFor('Alice', {from: web3.eth.accounts[0] }).toString()  //执行合约方法:查看总票数
//votingInstance.totalVotesFor.call('Alice', {from: web3.eth.accounts[0] }).toString()  //执行合约方法:查看总票数

6.3 网页交互dapp_Voting

6.3.1 创建服务器文件server.js

//server.js

//保存该文件server.js, 然后同级目录下创建一个 index.html 文件 + 合约执行文件 index.js。
//创建完成后 node server.js 执行服务器文件,打开网页 http://127.0.0.1:8080/index.html 来显示网页交互信息。

//参考教程:https://www.runoob.com/nodejs/nodejs-web-module.html

var http = require('http');
var fs = require('fs');
var url = require('url');

// 创建服务器
http.createServer( function (request, response) {
   var pathname = url.parse(request.url).pathname;   // 解析请求,包括文件名
   console.log("Request for " + pathname + " received.");   // 输出请求的文件名

   // 从文件系统中读取请求的文件内容
   fs.readFile(pathname.substr(1), function (err, data) {
      if (err) {
         console.log(err);
         response.writeHead(404, {'Content-Type': 'text/html'});
      }else{
         response.writeHead(200, {'Content-Type': 'text/html'});
         response.write(data.toString());  // 响应文件内容
      }
      response.end();  //  发送响应数据
   });
}).listen(8080);

// 控制台会输出以下信息
console.log('Server running at http://127.0.0.1:8080/');

6.3.2 创建index.html前端文件

<!-- // index.html -->

<!DOCTYPE html>
<html>

<head>
    <title>Voting DApp</title>
    <link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
</head>

<body class="container">
<h1>A Simple Voting Application</h1>
<div class="table-responsive">
    <table class="table table-bordered">

        <thead>
        <tr>
            <th>Candidate</th>
            <th>Votes</th>
        </tr>
        </thead>

        <tbody>
        <tr>
            <td>Alice</td>
            <td id="candidate-1"></td>
        </tr>
        <tr>
            <td>Bob</td>
            <td id="candidate-2"></td>
        </tr>
        <tr>
            <td>Cary</td>
            <td id="candidate-3"></td>
        </tr>
        </tbody>
    </table>
</div>

<input type="text" id="candidate"/>
<a href="#" onclick="voteForCandidate()" class="btnbtn-primary">Vote</a>
</body>

<!--<script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/web3@0.20.1/dist/web3.min.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="./index.js"></script>

</html>

6.3.3 创建index.js创建js合约交互文件

//index.js
//注意合约的 abi和address 信息变更问题。

web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

abi = JSON.parse('[{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"validCandidate","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"voteForCandidate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]' )  //输入编译后得到的abi
VotingContract = web3.eth.contract(abi);
contractInstance =VotingContract.at('0xc4f5bd0ab7740d29f58997a6591971496c659719');  //输入部署后得到的address

candidates = {"Alice": "candidate-1", "Bob": "candidate-2","Cary": "candidate-3"};

//调用合约方法投票
function voteForCandidate(candidate) {
    candidateName = $("#candidate").val();  //候选人的名称定义,从html的 candidate 的input输入id里获取.val()值
    try {
        contractInstance.voteForCandidate(candidateName,{from: web3.eth.accounts[0]},
            function() {
                let div_id = candidates[candidateName];  // 获取候选人名称,以便接下来查找#ID
                $("#"+div_id).html(contractInstance.totalVotesFor.call(candidateName).toString());  //填入得票数,通过id填入交互合约得到的得票数。
                }
        );
    } catch (err) {
    }
}

//页面全部加载完成后 调用回调函数 通过合约交互查询候选人得票数 填入进相关html页面中
$(document).ready(function() {
        candidateNames = Object.keys(candidates);  //提取参数对象里的所有键
        for (var i = 0; i < candidateNames.length; i++) {
            let name = candidateNames[i];  //赋值定义当前候选人
            let val = contractInstance.totalVotesFor.call(name).toString()  //获取投票数量:通过合约实例交互
            $("#"+candidates[name]).html(val);  //将候选人对应的得票数 填入到html页面中
        }
});

七、js脚本编写实现合约的自动编译、部署、测试等

其一:
通过脚本构建自动编译、部署、测试
首先需要构建工作目录分别存放合约文件、脚本文件、通过自动脚本实现的已编译文件、测试文件。目录结构如:
mkdir contracts
mkdir scripts
mkdir compiled
mkdir tests
在相应的目录中创建完成脚本文件后通过命令 node *** 来执行脚本文件完成编译、部署、测试等


其二:
也可以通过 npm包管理工具的script脚本机制来run运行相关指令实现自动编译、部署、测试。
操作如:
//npm run *** 的脚本机制实现需要:
//配置node工作目录下的npm包管理文件package.json(通过npm init命令生成)内的scripts键值内容,如下:

  "scripts": {
    "compile": "node scripts/compile.js",
    "deploy": "node scripts/deploy.js",
    "test": "mocha tests/",
    "start": "node server.js"
  },

//然后通过命令实现实现自动化编译/部署/测试工作流程:
npm run compile
npm run test
npm run deploy

7.1 自动编译脚本

// compile.js 自动编译脚本实现
// scripts目录下创建该 compile.js文件,部署脚本node执行命令:node scripts/compile.js


// 引入自动编译需要的包模块
const fs = require('fs-extra');  //fs模块的拓展,查看是否安装:npm list fs-extra,若无则安装:npm isntall fs-extra
const path = require('path');
const solc = require('solc');

// cleanup  健壮性处理:每次编译前清空目录存放已编译文件目录,然后重建该目录
const compiledDir = path.resolve(__dirname, '../compiled');  //解析绝对路径:参数路径拼接为绝对路径。其中参数__dirname为当前运行文件所在的目录
//console.log('当前保存拟编译文件的目录: ',compiledDir);  // 当前目录执行命令node compile.js来查看。注意后面代码处理。
fs.removeSync(compiledDir);  // 删除参数目录
//console.log('清空保存拟编译文件的目录: ',compiledDir);
fs.ensureDirSync(compiledDir);  // 确保参数目录存在。若无则创建。
//console.log('重建保存拟编译文件的目录: ',compiledDir);

// compile  编译指定文件内容 获得编译后的结果result
const contractPath = path.resolve(__dirname,'../contracts', 'Car.sol');  //解析待编译的指定文件地址。其中参数__dirname为当前运行文件所在的目录
const contractSource = fs.readFileSync(contractPath, 'utf8');  //读取待编译的指定文件内容
const result = solc.compile(contractSource, 1);  //编译指定文件内容
//console.log(result);  //查看指定文件编译后的编译结果

// check errors  健壮性处理:检查编译结果result错误信息,跑出错误信息详细内容
if (Array.isArray(result.errors) && result.errors.length) {
        throw new Error(result.errors[0]);
}

// save to disk  编译后二次加工处理:保存编译结果result经过二次加工后的json文件
Object.keys(result.contracts).forEach(name => {  // forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。编译后的内容结构为:{contracts:{":合约名称:{bytecode:'', ...}}}
        const contractName = name.replace(/^:/, '');  //将编译后contracts的 ':合约名称'键的名称字符串替换为 "合约名称"
        const filePath = path.resolve(compiledDir,`${contractName}.json`);  //解析已编译合约的拟保存 json文件地址
        fs.outputJsonSync(filePath, result.contracts[name]);  //输出保存已编译文件内容的 合约名称键 的 值内容,输出内容为编译结果的result.contracts[name]
        console.log(`save compiled contract ${contractName} to ${filePath}`);  //输出已保存
        }
);

7.2 自动部署脚本

//deploy.js  自动部署合约脚本_web3_1.0以上版本实现
//scripts目录下创建该 deploy.js文件,部署脚本node执行命令:node scripts/deploy.js


//注意web3版本
const path = require('path');
const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));  //连接rpc,也可以直接连接ganache.provider


// 1. 获取已编译合约的 abi 和 bytecode
const contractPath = path.resolve(__dirname, '../compiled/Car.json');  //解析绝对路径:已编译并二次加工的json文件
const { interface, bytecode } = require(contractPath);  //多重复值。注意必须对应一致引入文件的键名称。 引入已编译并二次加工的json文件,直接获取键对应的值内容。
console.log('合约编译ABI :',interface);


// 2. 获取钱包里面的账户
_from = web3.eth.accounts[0];
console.log('部署合约账户:', _from);

// 3. 部署合约  web3_0.20.1版本实现方法
const contract = web3.eth.contract(JSON.parse(interface));

const deployObj = {data:bytecode, from:web3.eth.accounts[0], gas:1000000};
let contractInstance = contract.new('volvo',deployObj, (err, res)=>console.log('合约部署地址:',res.address));




/*
//异步实现部署合约 web3_1.0以上版本方法
//注意:web3_1.0以上版本部署合约时的构造函数参数需要[]括起来,不同于0.20.1版本的本身数据及结构传入。
(async () => {

    // 2. 获取钱包里面的账户
    const accounts = await web3.eth.getAccounts();
    console.log('部署合约账户:', accounts[0]);

    // 3. 创建合约实例并且部署。 web3_1.0以上版本实现方法
    console.time('合约部署耗时');

    var result = await new web3.eth.Contract(JSON.parse(interface))  //部署合约对象
        .deploy({ data: bytecode, arguments: ['AUDI'] })  //部署合约:data传入bytecode字节码 和 arguments传入构造函数的参数
        .send({ from: accounts[0], gas: '1000000' });  //部署合约:发送交易对象即完成部署。返回部署信息。

    console.timeEnd('合约部署耗时');

    console.log('合约部署地址:', result.options.address);  //输出合约地址
    }

)();
*/

7.3 自动测试脚本

npm install mocha -g //全局安装
npm install ganache //注意:需要全局且在当前目录同时安装,用于当前文件Web3实例直接传入ganache.provider(),独立客观。

//tests目录下创建car.sper.js测试文件

// car.sper.js
//tests目录下创建car.sper.js测试文件,测试文件 用于命令行 mocha car.sper.js 来实现单元测试Car.sol合约;或者通过 npm run test 来实现。


const path = require('path');
const assert = require('assert');
const ganache = require('ganache-cli');  //注意:需要全局且在当前目录同时安装,用于当前文件Web3实例直接传入ganache.provider(),独立客观。
const Web3 = require('web3');

// 1. 配置 provider
const web3 = new Web3(ganache.provider());  //Web3实例直接传入ganache.provider(),独立客观。

// 2. 拿到 abi 和 bytecode
const contractPath = path.resolve(__dirname,'../compiled/Car.json');  //解析绝对路径:已编译并二次加工的json文件
const { interface, bytecode } = require(contractPath);  //多重复值。注意必须对应一致引入文件的键名称。 引入已编译并二次加工的json文件,直接获取键对应的值内容。

let accounts;
let contract;
const initialBrand = 'BMW';

//注意:describe(,)组测试,可以循环嵌套describe组测试。其中的每个 it就代表一个测试。
describe('contract', () => {

    // 3. 注意:beforEach()每一小次测试前都要执行。
    //每次跑单测时需要部署全新的合约实例,起到隔离的作用

    //异步实现部署合约 web3_1.0以上版本方法
    //注意:web3_1.0以上版本部署合约时的构造函数参数需要[]括起来,不同于0.20.1版本的本身数据及结构传入。
    beforeEach(async () => {
        console.log('当前小次测试开始.');
        accounts = await web3.eth.getAccounts();  //异步获取账户列表
        console.log('合约部署账户:', accounts[0]);

        //部署合约实例web3_1.0以上版本方法,注意异步的await阻塞方法
        contract = await new web3.eth.Contract(JSON.parse(interface))  // 合约对象创建
        .deploy({ data: bytecode, arguments: [initialBrand] })  //
        .send({ from: accounts[0], gas: '1000000' });
        console.log('合约部署成功:', contract.options.address); });

//    beforeEach(()=>console.log('当前小次测试开始.'))  //每一次组内小次测试开始前执行
    afterEach(()=>console.log('当前小次测试完成.'))  //每一次组内小次测试完成后执行
    after(()=>console.log('全部测试完成。'))  //全部测试完成后执行
    before(()=>console.log('全部测试开始...'))  //全部测试开始前执行,不局限代码位置。


    // 4. 编写单元测试
    // 每个 it就代表一个测试。
    it('deployed contract', () => {
        assert.ok(contract.options.address);  //asser.ok()断言参数内容为真
    });

    it('should has initial brand', async () => {
        const brand = await contract.methods.brand().call();  // 注意:await阻塞调用合约方法获取brand
        assert.equal(brand, initialBrand);  // 断言初始brand = 构造函数参数传入的brand
    });

    it('can change the brand', async ()=>{
        const newBrand = 'Benz';
        await contract.methods.setBrand(newBrand)  // 注意:await阻塞调用合约方法设置brand
        .send({from: accounts[0]}); // 发送交易对象 实现 调用合约设置Brand
        const brand = await contract.methods.brand().call();  // 调用合约查询brand
        assert.equal(brand, newBrand);  // 断言获取得到的brand = 调用合约设置的brand
    });
});

八 完整工作流程(npm run的脚本机制 实现自动化编译/部署/测试)

//npm run *** 的脚本机制实现需要:
//配置node工作目录下的npm包管理文件package.json(通过npm init命令生成)内的scripts键值内容,如下:

  "scripts": {
    "compile": "node scripts/compile.js",
    "deploy": "node scripts/deploy.js",
    "test": "mocha tests/",
    "start": "node server.js"
  },

//然后通过命令实现实现自动化编译/部署/测试工作流程:

npm run compile
npm run test
npm run deploy

  区块链 最新文章
盘点具备盈利潜力的几大加密板块,以及潜在
阅读笔记|让区块空间成为商品,打造Web3云
区块链1.0-比特币的数据结构
Team Finance被黑分析|黑客自建Token“瞒天
区块链≠绿色?波卡或成 Web3“生态环保”标
期货从入门到高深之手动交易系列D1课
以太坊基础---区块验证
进入以太坊合并的五个数字
经典同态加密算法Paillier解读 - 原理、实现
IPFS/Filecoin学习知识科普(四)
上一篇文章      下一篇文章      查看所有文章
加:2021-10-21 12:14:17  更:2021-10-21 12:14:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 20:19:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码