【以太坊傻瓜教程】在私链上发布第一个合约

版权声明:本文为博主原创文章,转载请注明出处与作者! https://blog.csdn.net/weixin_40401264/article/details/78135785

【以太坊傻瓜教程】在私链上发布第一个合约


教程简介:

本教程将介绍如何编写合约,编译合约以及如何将合约发布到自己的私链上并调用。


开发环境:

本教程开发环境:


编写合约:

新建一个文本文件,在此文件中键入如下代码:

pragma solidity ^0.4.0;

contract SimpleStorage {
    uint storedData;

    function set(uint x) {
        storedData = x;
    }

    function get() constant returns (uint) {
        return storedData;
    }

}

代码第一行是用来向编译器声明当前合约是采用0.4版本Solidity语法编写。这个声明的目的是保证合约针对编译器的向后兼容性。后面的代码是合约主体。声明了一个“状态变量”(State Variable)叫做storedData类型为uint(此处的uint是256位值),此变量值记录在区块链数据库中。合约中的“状态变量”有点类似于c++中的静态成员变量。然后声明了两个函数set和get,分别用来设置与获取上面声明的“状态变量”值。

将此文本文件保存,并重命名为 SimpleStorage.sol。
这里写图片描述


编译合约:

打开Windows命令行输入如下命令

echo var contractContent = > SimpleStorage.js
solc --combined-json abi,bin,interface SimpleStorage.sol >> SimpleStorage.js

此步骤的主要目的是生成一个javascript脚本,脚本的目的是初始化一个叫contractContent变量,此变量以字符串形式记录了编译好的合约脚本。第二个命令是使用solidity的编译器,将我们的合约SimpleStorage进行编译,并生成ABI,合约字节码,和接口描述。将这些信息打包为json格式。

运行完这一步我们得到了一个javascript脚本,SimpleStorage.js
这里写图片描述

此脚本后面会在geth中执行,这样就可以将编译好的合约脚本巧妙地传到了geth可控制台内。此步骤主要是因为在geth1.6后内部取消了eth.compile.Solidity的编译函数


发布合约:

启动geth控制台,在Windows命令行中输入如下命令

geth --datadir "E:\EthDBSpace\PrivChain" --jspath "D:\workspace\solidity\" console      

–datadir 选项是用来指定eth数据路径的,这里指定是的我们之前建好的私链数据路径。关于如何构建私链,可参见上一课“如何创建私链”(http://blog.csdn.net/weixin_40401264/article/details/78095222)。
–jspath 选项是用来指定javascript存放目录的。这里我们指定为我们刚才编译好的SimpleStorage.js所在目录。
console 命令用来指示geth打开命令行

经过一阵初始化,进入geth命令行。显示结果如下:
这里写图片描述

载入编译好的合约,在geth命令行中输入:

loadScript("SimpleStorage.js")

此命令会载入并执行SimpleStorage.js。结果就是多了一个新变量contractContent,此变量记录了编译好的合约。

查看编译好的合约是否成功,可直接在geth命令行键入变量名查看变量内容

contractContent

结果如下,载入成功。

这里写图片描述

创建contract对象,在geth命令行中键入:

var contractTemplate=web3.eth.contract(JSON.parse(contractContent.contracts["SimpleStorage.sol:SimpleStorage"].abi))

上面代码的含义为:采用contractContent的”SimpleStorage.sol:SimpleStorage”合约的ABI来创建一个contract对象。所谓contract的对象可以理解为一个合约模板,后面会根据此模板来创建合约实例。

发布合约前需要获得发布合约的gas预估,在geth命令行中键入

var gasValue = eth.estimateGas({data:"0x"+contractContent.contracts["SimpleStorage.sol:SimpleStorage"].bin})

在发布合约前需要解锁账户,因为会用到里面的钱。在geth命令行中键入

personal.unlockAccount(eth.accounts[0])

发布合约,在geth命令行中键入

var contractInst = contractTemplate.new({ from: eth.accounts[0], data: "0x" + contractContent.contracts["SimpleStorage.sol:SimpleStorage"].bin,gas:gasValue},
      function (e, contract) {
            console.log(e, contract);
           if (typeof contract.address !== 'undefined') {
              console.log('Contract mined! address: ' + contract.address + 'transactionHash: ' + contract.transactionHash);
           }
      }
);

上面代码的含义为:调用contract.new函数来发布合约,返回一个合约实例。
contract.new函数的签名为:contract.new([arg0][,arg1]…[,transactionObject][,callback])
前面若干形参是构造函数参数。第二个参数填交易描述。对于发布合约,from填写要发布合约的账户,to参数无需填写,data参数填写编译好的合约的字节码文本, gas填写发布合约的gas值,此处我们填写的上面系统评估个gas值gasValue。第三个参数填异步回调。这个回调函数会在发布合约成功后调用。我们这里的回掉是在合约发布后在控制台上打印合约发布地址及交易Has。在以太坊中js回调用了类Node.js的error-first callback形式的回调.
关于Error-First Callback参见
http://fredkschott.com/post/2014/03/understanding-error-first-callbacks-in-node-js/

合约发布命令执行成功后,控制台会显示交易已被提交
这里写图片描述

挖矿来让合约发布到私链区块中。

miner.start()

合约提交成功后,结果如下:
这里写图片描述

可以看到我们的回调被调用,打印出了合约的地址:0x171d64d78f753d2c95f4efba6799a0cc6dc8c5a7

结束挖矿:

miner.stop()

调用合约函数:

下面我们调用set来为状态变量storedData赋值。由于我们的set函数是要改写链上数据的,所以需要使用sendTransaction来调用。为了使用sendTransaction来调用链上合约,首先我们需要获得函数的签名的函数选择器(Function Selector)。将函数签名传入sha3函数生成hash256,hash的前4个字节即函数选择器的值。
在geth命令行输入如下命令,获取set函数哈希:

web3.sha3("set(uint256)")

其中函数签名的参数列表只需写参数类型,注意参数列表不要有空格。
其中参数类型必须准确到字长,比如当前合约的storedData变量为uint,在solidity中uint类型等同于uint256,所以这里要填uint256

算set函数哈希结果如下:

这里写图片描述

哈希值为0x60fe47b16ed402aae66ca03d2bfc51478ee897c26a1158669c7058d5f24898f4
所以set函数的函数选择器为0x60fe47b1

在geth命令行输入如下命令:

eth.sendTransaction({from:eth.accounts[0], to:contractInst.address, value:0, data:"0x60fe47b10000000000000000000000000000000000000000000000000000000000001234"})

from:为调用者的账户。
to:为合约地址。
value:由于此调用的目的不是转账,所以此值为0。
data: 描述了调用那个函数及使用什么参数。格式为:前4个字节为函数选择器。这里是要调用set函数所以为0x60fe47b1,后面紧跟着参数。由于set的参数只有一个uint(uint256),字长为32字节。这里设置的值为0x1234。

调用成功后会显示:

这里写图片描述

挖矿来使函数调用得以执行。在geth命令行输入

miner.start()

执行成功后停止挖矿:

miner.stop()

查询set函数执行结果

eth.getStorageAt(contractInst.address,0)

第一个参数是合约地址,第二个参数是状态变量索引,由于我们的合约目前只有一个状态变量所以这里填0

查询结果如下:

这里写图片描述

返回值为0x1234,说明调用set函数成功!

查询调用结果:

我们也可以通过调用合约中的get函数来查询状态变量storedData。由于get函数为constant所以可以本地同步调用。在geth命令行输入:

contractInst.get.call()

调用结果如下:

这里写图片描述

猜你喜欢

转载自blog.csdn.net/weixin_40401264/article/details/78135785