第八篇 墨客区块链(MOAC BlockChain) 程序猿怎么部署和调用智能合约

标签: moac  墨客  区块链  智能合约

在本博客《第四篇 在墨客区块链(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 [email protected]     //安装指定版本

进入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需要私钥签名,得到本地节点账号的私钥有两种方式:

  • 方式一:使用“keystore + password”的方式将账号导入手机版钱包TokenPocket,然后导出私钥。
  • 方式二:使用如下代码getPrivateKey.js得到本地节点账号的私钥。
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有两种方式:

  • 方式一:使用“私钥”的方式将账号导入手机版钱包TokenPocket,然后导出为keystore。
  • 方式二:使用如下代码getKeystore.js得到本地节点账号的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);
    }	
}

 

原文链接:加载失败,请重新获取