在本博客《第四篇 在墨客区块链(MOAC BlockChain)部署ERC-20合约》和《第五篇 在墨客区块链(MOAC BlockChain)部署ERC-721合约》,已经有使用网页版钱包部署智能合约的方法。
本文基于墨客区块链(MOAC BlockChain),详细说明使用代码部署和调用智能合约的方法。
环境:
墨客区块链版本:nuwa-vnode1.0.2.win.zip;
操作系统:64位Windows 10家庭版。
1.安装并启动本地节点
1.1 安装moac节点
请参考文档《第三篇 墨客区块链(MOAC BlockChain)节点安装教程》。
1.2 启动moac节点
打开命令终端(cmd),转到墨客当前目录,在命令行中执行:
D:\nuwa1.0.2.win>moac --rpc
确保本地节点中某个账号有足够的moac以进行智能合约部署和调用。
2.编写并编译智能合约
2.1 编写智能合约
本篇实际测试代码TestToken20.sol附在《第四篇 在墨客区块链(MOAC BlockChain)部署ERC-20合约》文章末尾,TestToken721.sol附在《第五篇 在墨客区块链(MOAC BlockChain)部署ERC-721合约》文章末尾。
修改20合约TestToken20.sol的参数部分,直接写入参数值。
uint256 public totalSupply = 1000000; //发行总量
string public name = "My test token"; //名称
uint8 public decimals = 8; //返回token使用的小数点位数。
string public symbol = "MTT"; //token简称
这样在部署的时候不需要导入参数。
注意:这些代码为测试实例使用,非标准部署智能合约代码。
2.2 使用本地solidity编译器编译合约
需要有本地的solidity编译环境。安装命令:
C:>npm install -g solc //默认安装最新版本
C:>npm install -g solc@0.4.21 //安装指定版本
进入TestToken20.sol所在目录,编译合约:
C:>solcjs --bin --abi -o bin TestToken20.sol
运行后,输出TestToken20.abi及TestToken20.bin到bin目录下,TestToken20.abi里边放的就是abi的内容,TestToken20.bin里边放的就是bytecode的内容。
2.3 使用remix编译合约
Remix是一个开发Solidity智能合约的网络版开发软件。
登陆Remix 后,把中间的编辑框里的合约内容删除,然后把自己的合约代码复制到编辑框里。在右上角的菜单里Compile下面选中Auto Compile。
编译后会在右边区域显示是否有error、warning等信息。如果没有报错(Warning可以忽略),点击“Details”显示编译后详细信息。
可以得到跟2.2节一样的bytecode内容和abi内容。拷贝出来备用。
还有FUNCTIONHASHES,包含了本合约中的函数通过hash算法Keccak256得到前4个字节,调用合约时会用到。
3.部署智能合约
3.1 部署
部署智能合约文件deploy.js,内容如下:
var abiString = '[{"constant":true,"inputs":[],"name":"name",......"type":"event"}]'; //编译结果的abi
var bytecodeString = '606060405234801561001057600080fd5b5060......75fd0029'; //编译结果的bytecode
var account = {address:"0x745c57ca5318093115d61bbca368XXXXXXXXXXXX",secret:"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"};
var Chain3 = require('chain3');
var chain3 = new Chain3(new Chain3.providers.HttpProvider('http://localhost:8545'));
createContract(chain3, account, abiString, bytecodeString);
function createContract(chain3, account, abiString, bytecodeString){
var bytecode = "0x" + bytecodeString;
var abi = JSON.parse(abiString);
console.log('bytecode', bytecode);
console.log('abi', abiString);
var gasValue = chain3.mc.estimateGas({data: bytecode});
console.log("gas estimate on contract:", gasValue);
var address = account.address;
var secret = account.secret;
var txCount = chain3.mc.getTransactionCount(address);
console.log("get tx account", txCount)
var rawTx = {
from: address,
nonce: chain3.intToHex(txCount),
gasPrice: chain3.intToHex(25000000000),
gasLimit: chain3.intToHex(400000),
data: bytecode,
chainId: chain3.version.network,
};
var signedTx = chain3.signTransaction(rawTx, account.secret);
console.log("send signed tx:", signedTx);
console.log("len", signedTx.length);
chain3.mc.sendRawTransaction(signedTx, function(err, hash) {
if (!err){
console.log("succeed: ", hash);
}else{
console.log("error:", err.message);
}
});
}
代码中的account是本地节点的一个账号,需要付出本次部署智能合约的gas费。部署后的合约也归该账号所有。
合约部署完成后,如果没有报错,会返回一个hash值,通过这个hash值到浏览器可以查询到本次合约部署的详细信息。
或者通过节点命令得到相应信息。
>mc.getTransactionReceipt("transactionHash")
3.2 从keystore得到privateKey
本节中部署智能合约时,代码使用sendRawTransaction需要私钥签名,得到本地节点账号的私钥有两种方式:
var keythereum = require("keythereum");
var datadir = "C:\\Users\\lyq2018\\AppData\\Roaming\\MoacNode"; //moacnode目录,根据实际修改
//var datadir = "/Users/gm/Library/MoacNode"; //苹果mac系统moacnode目录,根据实际修改
var address= "0x68986c1bcd54ae5dae69310XXXXXXXXXXXXXXXXX"; //本地节点账号,根据实际修改
const password = "password"; //账号密码,根据实际修改
var keyObject = keythereum.importFromFile(address, datadir);
var privateKey = keythereum.recover(password, keyObject); //输出私钥
console.log(privateKey.toString('hex'));
代码运行需要nodejs和python2.7环境,同时需要导入keythereum。
c:\>npm install -g keythereum
3.3 从privateKey得到keystore
有时候用户会需要从私钥导出keystore用于节点,此时得到本地节点账号的keystore有两种方式:
var Wallet = require('ethereumjs-wallet');
var privateKey = 'bb673026deda3c3cd0c63f6ccddfb02a7ae320078aa8XXXXXXXXXXXXXXXXXXXX';
var key = Buffer.from(privateKey, 'hex');
var wallet = Wallet.fromPrivateKey(key);
wallet.toV3String('password');
console.log("Get keystore", wallet.toV3String('password'));
代码运行需要nodejs和python2.7环境,同时需要导入ethereumjs-wallet。
c:\>npm install -g ethereumjs-wallet
安装时如果报类似“npm ERR! Unexpected end of JSON input while parsing near......”的错,可以先清理缓存再安装。
c:\>npm cache clean --force
4.调用智能合约
以下为TestToken20.sol的调用代码,call_erc20.js。
var Chain3 = require('chain3');
var chain3 = new Chain3(new Chain3.providers.HttpProvider('http://localhost:8545'));
var contractAddress = "0xA2580D58A58998ca06e6f5b2A96AXXXXXXXXXXXX"; //智能合约地址
var address = "0x68986c1BCD54Ae5dAe69310fC64EXXXXXXXXXXXX";
var account = {address:"0x68986c1BCD54Ae5dAe69310fC64XXXXXXXXXXXX",secret:"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"};
var abiString = '[{"constant":true,"inputs":[],"name":"name",......"type":"event"}]'; //智能合约的abi
//调用erc20合约
//基本属性
callContract1(chain3, contractAddress, address, abiString);
function callContract1(chain3, contractAddress, address, abiString){
var abi = JSON.parse(abiString);
var contract = chain3.mc.contract(abi);
var token = contract.at(contractAddress);
console.log(JSON.stringify(token.totalSupply())); //总量
console.log(JSON.stringify(token.name())); //名称
console.log(JSON.stringify(token.decimals())); //位数
console.log(JSON.stringify(token.symbol())); //简称
}
//调用erc20合约
//通过abi生成智能合约对象, 直接通过对应合约方法进行call调用
//查询余额
callContract2(chain3, contractAddress, address, abiString);
function callContract2(chain3, contractAddress, address, abiString){
var abi = JSON.parse(abiString);
var contract = chain3.mc.contract(abi);
var token = contract.at(contractAddress);
token.balanceOf.call(address, function(err, result){
console.log(err, JSON.stringify(result));
});
}
//调用erc20合约
//通过对交易签名进行调用
//发送代币
var amount = 100; //发送数量
var anotherAddress = "0x745c57ca5318093115d61bbca3687ca02cxxxxxx"; //接收地址
callContract3(chain3, contractAddress, account, abiString, anotherAddress, amount);
function callContract3(chain3, contractAddress, account, abiString, anotherAddress, amount){
var address = account.address;
var abi = JSON.parse(abiString);
var contract = chain3.mc.contract(abi);
var token = contract.at(contractAddress);
var data = token.transfer.getData(anotherAddress, amount);
console.log('data', data);
var txCount = chain3.mc.getTransactionCount(account.address);
var rawTx = {
nonce: chain3.intToHex(txCount),
gasPrice: chain3.intToHex(25000000000),
gasLimit: chain3.intToHex(100000),
to: contractAddress,
data: data,
chainId: chain3.version.network
};
var signedTx = chain3.signTransaction(rawTx, account.secret);
chain3.mc.sendRawTransaction(signedTx, function(err, hash) {
if (!err){
console.log("succeed: ", hash);
var filter = chain3.mc.filter('latest');
filter.watch(function(error, result) {
var receipt = chain3.mc.getTransaction(hash);
if (!error && receipt && receipt.blockNumber != null) {
console.log("done.");
filter.stopWatching();
process.exit(0);
}
});
}else{
console.log("error:", err.message);
}
});
}
以上代码三个调用实例的函数分别为callContract1、callContract2、callContract3;可以根据需要单独或组合使用。
以下为TestToken721.sol的调用代码,call_erc721.js。
var Chain3 = require('chain3');
var chain3 = new Chain3(new Chain3.providers.HttpProvider('http://localhost:8545'));
var contractAddress = "0xA2580D58A58998ca06e6f5b2A96AXXXXXXXXXXXX"; //智能合约地址
var address = "0x68986c1BCD54Ae5dAe69310fC64EXXXXXXXXXXXX";
var account = {address:"0x68986c1BCD54Ae5dAe69310fC64XXXXXXXXXXXX",secret:"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"};
var abiString = '[{"constant":true,"inputs":[],"name":"name",......"type":"event"}]'; //智能合约的abi
//调用erc721合约
//读合约
callContract(chain3, contractAddress, address, abiString);
function callContract(chain3, contractAddress, address, abiString){
var abi = JSON.parse(abiString);
var contract = chain3.mc.contract(abi);
var token = contract.at(contractAddress);
var tokenID = 5; //从0开始的编号
//输出721 token的name
token.name.call(function(err, result){
console.log(err, JSON.stringify(result));
});
//输出balance
token.balanceOf.call(address, function(err, result){
console.log( "721 getBalanceof",err, JSON.stringify(result));
});
//输出该tokenID的属性
token.getProperty.call(tokenID, function(err, result){
console.log( "721 getTitle",err, JSON.stringify(result));
});
//输出tokenID的拥有者
token.ownerOf.call(tokenID, function(err, result){
console.log( "721 ownerOf",err, JSON.stringify(result));
});
//输出某个地址拥有的所有tokenID
token.tokensOfOwner.call(address, function(err, result){
console.log( "721 tokensOfOwner",err, JSON.stringify(result));
});
}
//调用erc721合约
//创建721token
callContract1(chain3, account, contractAddress, address, abiString)
function callContract1(chain3, account, contractAddress, address, abiString){
var abi = JSON.parse(abiString);
var contract = chain3.mc.contract(abi);
var token = contract.at(contractAddress);
console.log('data', token.createToken.getData("property",address)); //导入token的属性
var privateKey = new Buffer(account.secret, "hex");
var txCount = chain3.mc.getTransactionCount(account.address);
var rawTx = {
nonce: chain3.intToHex(txCount),
gasPrice: chain3.intToHex(25000000000),
gasLimit: chain3.intToHex(400000),
to: contractAddress,
data: token.createToken.getData("property",address),
chainId: chain3.version.network,
};
var signedTx = chain3.signTransaction(rawTx, account.secret);
chain3.mc.sendRawTransaction(signedTx, function(err, hash) {
if (!err){
console.log("succeed: ", hash);
}else{
console.log("error:", err.message);
}
});
}
//调用erc721合约
//发送721token
var tokenID = 100; //721token编号
var anotherAddress = "0x745c57ca5318093115d61bbca3687ca02cxxxxxx"; //接收地址
callContract2(chain3, account, contractAddress, anotherAddress, tokenID, abiString)
function callContract2(chain3, account, contractAddress, anotherAddress, tokenID, abiString){
var abi = JSON.parse(abiString);
var contract = chain3.mc.contract(abi);
var token = contract.at(contractAddress);
console.log('data', token.transfer.getData(anotherAddress,tokenID ));
var privateKey = new Buffer(account.secret, "hex");
var txCount = chain3.mc.getTransactionCount(account.address);
var rawTx = {
nonce: chain3.intToHex(txCount),
gasPrice: chain3.intToHex(25000000000),
gasLimit: chain3.intToHex(400000),
to: contractAddress,
data: token.transfer.getData(anotherAddress,tokenID ),
chainId: chain3.version.network,
};
var signedTx = chain3.signTransaction(rawTx, account.secret);
chain3.mc.sendRawTransaction(signedTx, function(err, hash) {
if (!err){
console.log("succeed: ", hash);
}else{
console.log("error:", err.message);
}
});
}
//调用721合约的时候
//得到所有拥有该721token的地址
var tokenNow = 0; //tokenID从该编号开始,必须 >= 0
var tokenMax = 8; //tokenID到该编号结束,必须已经create token到该编号
callContract3(tokenNow, chain3, contractAddress, address, abiString);
function callContract3(tokenNow, chain3, contractAddress, address, abiString){
var abi = JSON.parse(abiString);
var contract = chain3.mc.contract(abi);
var token = contract.at(contractAddress);
//console.log("以下输出721合约的owner:");
token.ownerOf.call(tokenNow, function(err, result){
console.log( "721 ownerOf",tokenNow,err, JSON.stringify(result));
});
//通过tokenID循环
if (tokenNow < tokenMax){
allContract3(tokenNow+1, chain3, contractAddress, address, abiString);
}
}