以太坊Solidity编程:合约调用与Web3.js
合约部署方法
合约的编译
- 使用浏览器编译器Remix
- 使用truffle编译,目前是最常用的编译方式
- Solc或者Web3.js编译合约,使用相对较少
基于Remix的编译部署
- Remix直接编译即可
- 部署使用remix的web3 provider形式
- 部署也可用remix+metamask,但metamask安装需要科学上网
- Remix访问的时候,建议使用http,而不是https
Truffle编译
- 合约应该位于./contracts目录
- 编译合约命令:truffle compile
- Truffle仅仅默认自上次编译后被修改过的文件,来减少不必要的编译。如果想要编译全部文件,可以使用–compile-all选项。
Truffle编译约定
- Truffle需要定义的合约名称和文件名准确匹配,如文件名为MyContract.sol,那么合约文件须为如下两者之一:
- contract MyContract {…}
- library MyContract {…}
- Truffle文件名匹配是区分大小写的,也就是说大小写也要一致。
- 可以通过使用import来声明依赖。Truffle将会按正确顺序依次编译合约,并在需要的时候自动关联库。
- 编译的输出位于./build/contracts目录。如果目录不存在会自动创建。
Truffle部署
Truffle通过truffle.js指定的以太坊网络来部署,部署命令为truffle deploy,如果truffle.js有多个网络,可以使用networkc参数来指定。
Truffle配置文件
配置文件是truffle.js。位于项目的根目录下。这个文件是Javascript文件,支持执行代码来创建配置:
- BUILD:这个是前端的构建配置。默认调用默认构建器。
- NETWORKS:指定在部署时使用哪个网络。
- RPC:关于如何连接到以太坊客户端的一些细节。host和port是必须的。还包括gas(部署时的Gas限制),gasPrice(部署时的Gas价格),from(移植时使用的源地址,默认是你的以太坊客户端第一个可用账户).
Truffle配置文件示例:
module.export = {
networks:{
development:{
host:"localhost",
port:8545,
network_id:"*" //匹配任何network id
}
}
};
Truffle deployer部署参数示例:
var Hello = artifacts.require("./Hello.sol");
var Multi = artifacts.require("./Multi.sol");
module.exports = function(deployer){
//部署单个合约,不带任何构造参数
deployer.deploy(Hello);
//部署单个合约带有构造参数
deployer.deploy(Multi,11);
//部署多个合约,一些有参数,一些没有参数
/*
deployer.deploy([
[A,arg1,arg2,...],
B,
[C,arg1]
]);
*/
}
合约调用与web3.api
智能合约调用
以太坊支持通过各种方式与节点进行交互:
- JSON-RPC
- JavaScript Console
- web3
JSON RPC
- JSON RPC可以理解为一个rest服务
- 大部分客户端均通过JSON RPC调功能、传数据
- JSON RPC只是一个传输通道,以太坊还有IPC的接口。
RPC调用客户端命令
假设我们要调用客户端命令eth.getBalance(),查询地址为0x407的账号的余额,命令如下:
curl --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x407","latest"],"id":1}' localhost:8123
其中,jsonrpc字段指定JSON-RPC版本号,method字段指定需要调用的api方法名,params字段为传送的参数,id为消息标识字段;
RPC调用智能合约
假设目前有部署的智能合约,地址为0x6ff93,我们要调用的合约方法签名multiply(uint256),传入的参数值为6,那么调用命令的格式如下:
curl --data
{
"jsonrpc":"2.0",
"method":"eth_sendtransaction",
"param":[{
"from":"0xeb85a5",
"to":"0x6ff93",
"data":"0xcddddd"
}],
"id":8
}
localhost:8123
其中,from为扣除GAS账户地址,to为智能合约部署的地址,data为调用方法的签名和传入参数,编码方式为“0x”+sha3(“multiply(uint256)”).substring(0,8)+to_32bit_Hex_str(6)
RPC合约调用
直接使用RPC对智能合约的调用需要进行复杂的编码。具体规则可以参考Ethereum Contract ABI文档。实际编程中,基本都使用web3等方式,对RPC进行了友好封装。
Web3概述
- 与合约交互,最常用的方式就是使用web3.js library提供的web3
- 底层实现上,它通过RPC调用与本地节点通信
- web3.js可以与任何暴露了RPC接口的以太坊节点连接。
- Web3已随truffle安装。
Web3调用合约
- web3.js封装了合约调用的方法。
- 使用可以直接使用web3.eth.contract的里的sendTransaction来修改区块链数据
- 调用合约,可能需要from等参数,否则可能出现调用异常。
Web3 API体系
- Web3-eth:以太坊区块链基本操作和智能合约相关操作。
- Web3-ssh:实现whisper相关操作,包括p2p和广播操作。
- Web-bzz:swarm协议相关,分布式存储
- web3-utils:Dapp 开发辅助功能
Web3版本说明
- npm ls web3,使用该命令查看版本
- npm update web3,如果版本过低,请升级
版本文档以此为准:
https://web3.js.readthedocs.io/en/1.0/web3-eth.html
Web3初始化
// 引入web3
// in node.js use: var Web3=require('web3');
//如果浏览器按照了MetaMask,则会提供一个默认的web3.currentProvider
//如果为空,则连接远程/本地节点
var web3 = new Web3(Web3.givenProvider || "ws://localhost:8545");
Web3常用API
web3.setProvider,设置Provider
- 参数:无
- 返回值:无
- 示例:web3.setProvider(new web3.providers.HttpProvider(‘http://localhost:8545’));
web3.toWei
- 按对应货币转为以wei为单位。最常用的单位为ether。
- 示例:var value = web3.toWei(‘1’,‘ether’);
console.log(value); // “10000000000000000000”
web3.eth.account,以太坊账号
- 示例:web3.eth.getAccounts([callback])
web3.eth.getBalance(address[,defaultBlock] [,callback])
web3.eth.contract, 创建一个Solidity的合约对象,用来在某个地址上初始化合约。
- 参数:Array,一到多个描述合约的参数,事件的API对象。
- 返回值:Object,一个合约对象。
- 示例:var MyContract = new web3.eth.Contract(abiArray);
合约对象的方法
- 显示对象call。myContract.methods.myMethod([param1[,param2[,…]]]).call(options[,callback]);(不修改数据)
- 显示调用send。myContract.methods.myMethod([param1[,params[,…]]]).send(options[,callback]);(修改数据,消耗gas)
合约调用方法代码示例:
//合约实例
var contract = new web3.eth.Contract(abi,address);
//callback
contract.methods.helloWorld().call(function(error,result){
colsole.log(result);
});
//promise
contract.methods.helloWorld().call().then(
function(result)(
console.log(result);
)
);
合约对象的事件
- 参数:
-
- Object, 你想返回的索引值(过滤哪些日志)。如{‘valueA’,1,‘valueB’:[myFirstAddress,mySecondAddress]}。默认情况下,所有过滤项被设置为null。意味着默认匹配的是合约所有的日志。
-
- OBject,附加的过滤选项。参见web3.eth.filter的第一个参数。默认情况下,这个对象会设置address为当前合约地址,同时第一个主题为事件的签名。
-
- Function,(可选)传入一个回调函数,将立即开始监听,这样就不用主动调。
合约对象的事件,回调返回值:
- OBject,事件对象,如下:
-
- address:String,32字节,日志产生的合约地址
-
- args:Object,事件的参数
-
- blockHash:String,32字节,日志所在块的哈希。如果是pending的日志,则为null。
-
- blockNumber:Number,日志所在块的块号。如果是pending的日志,则为null。
-
- logIndex:Number,日志在区块中的序号。如果是pending的日志,则为null。
-
- event:String,事件名称
-
- removed:bool,标识产生事件的这个交易是否被一处(因为孤块),或从未生效(被拒绝的交易)。
-
- transactionIndex:Number,产生日志的交易在区块中的序号。如果是pending的日志,则为null。
-
- transactionHash:String,32字节,产生日志的交易哈希值。
Web3.js的使用与案例
合约调用的基本流程
- 初始化web3,连接以太坊节点rpc服务,获得一个provider对象
- 初始化合约的对象。
- 合约对象的provider设置为已知初始化的web3对象。
- 调用合约
- 监听合约
合约调用
- 合约调用可以使用call或者send。
- myContract.methods.myMethod([param1[,param2[,…]]]).call/send(options[,callback])
- options可以包括from,gasPrice,gas,value。分别代表调用者地址,gas价格,消耗的最低gas,发送的以太币数量。
Web3调用合约的例子
var Web3 = require('web3');
console.log(Web3.version);
//设置web3对象
var web3 = new Web3('http://localhost:8545');
var json = require("../build/contracts/Hello.json");
var abi = json["abi"];
var address = "0x91ab99f3983y798cenu9eh49erjj88q3u4rjeqd903q4uytr04";
//合约实例
var contract = new web3.eth.Contract(abi,address);
//callback
contract.methods.helloWorld().call(function(error,result){
console.log(result);
});
Truffle对Web3的封装
// 1.引用编辑好的合约文件结果
var json = require("./build/contracts/MyContract.json");
// 2.将合约转为合约抽象层实例
var contract = require("truffle-contract");
var MyContract = contract(json);
// 3. 设置合约抽象层实例的web3 provider
MyContract.setProvider(new Web3.providers.HttpProvider("http://localhost:8545"));
// 4. 开始使用
MyContract.deployed().then(function(deployed){
return deployed.someFunction();
});
Truffle封装web3的优点
- 对以太坊的智能合约做了更好的抽象,使用简单
- 同步的交易:可以确保在交易生效之后再继续执行其他操作。
- 返回Promise:每个封装的合约函数会返回Promise,可以对它进行.then操作,避免了回调地狱(callback hell)问题。
- 为交易提供了默认参数:例如from或gas。