这篇博客演示的基本操作系统环境是CentOS 7,参考书籍:以太坊开发实战——以太坊关键技术与案例分析 第七、九章(吴寿鹤、冯翔、刘涛、周广益 著)。
1.remix:是一个浏览器版的solidity开发 IDE,你可以使用在线版的https://remix.ethereum.org/,Remix使用方法,也可以下载下来 https://github.com/ethereum/remix-ide 安装到本地。
2.IntelliJ IDEA也提供了solidity的插件如下:(点击安装即可)
但是安装完毕后,是不能直接手动建立solidity项目的,需要建立一个java项目或者其他项目,然后右键项目名,在弹出的选项中选择New,然后点击New Solidity File选项,会弹出下面的对话框,输入你的合约文件名,并选择Smart contract。
此时可以编写合约文件了,但还不能编译合约,所以需要先下载solidity的编译器:solc ,命令行可执行文件名为solcjs。
然后将编译器连接到IntelliJ idea中,在设置中找到External Tools,可以看到如下图右侧区域,这是我已经创建的外部工具
如果你没有创建过则为空,点击“+”添加外部工具:
设置相关参数如下:
然后编写你的合约文件,要编译时,右键你的合约文件,选择External Tools -> solidity就开始编译你的合约文件了。如下图,左侧为编译输出文件目录,右侧为合约代码,下面是编译输出结果。
1.编写合约
编写Storage.sol合约文件如下:
pragma solidity >=0.4.21 <0.6.0;
contract Storage {
uint256 public storedData;
function set(uint256 data) public {
storedData = data;
}
function get() public returns (uint256) {
return storedData;
}
}
2.命令行编译合约
由于solcjs的comandline选项与solc不兼容,而工具(如geth)希望solc的行为不适用于solcjs。我直接下载了solc的可执行文件solc-static-linux,然后执行下面的命令:
//把文件所属改为root,也可以不改,因为我是以root用户使用
$ chown root solc-static-linux
//把文件名改为solc
$ mv solc-static-linux solc
//把你的solc可执行文件添加到/usr/bin/,就可以在任何目录运行该程序
$ cp ./solc /usr/bin/
//查看命令行参数说明文档
$ solc --help
//然后就可以开始运行编译solidity文件了
[root@localhost code]# solc --optimize --abi --bin Storage.sol -o ./out
Compiler run successful. Artifact(s) can be found in directory ./out.
//编译成功后可以看到生成的两个目标文件
out/
├── Storage.abi
└── Storage.bin
其中bin文件是合约被编译后的二进制内容,abi(Application Binary Interface,“应用二进制接口”)文件,可理解为合约接口说明。当合约被编译后,那么它的abi也就确定了。编译后的abi文件内容如下:
[
{
"constant":true,
"inputs":[],
"name":"storedData",
"outputs":[{"name":"","type":"uint256"}],
"payable":false,
"stateMutability":"view",
"type":"function"
},
{
"constant":false,
"inputs":[{"name":"data","type":"uint256"}],
"name":"set","outputs":[],
"payable":false,
"stateMutability":"nonpayable",
"type":"function"
},
{
"constant":true,
"inputs":[],
"name":"get",
"outputs":[{"name":"","type":"uint256"}],
"payable":false,
"stateMutability":"view",
"type":"function"
}
]
可以看到这个合约abi解析后是一个数组,里面包含了三个对象,后两个对象对应了合约中的set方法和get方法,而第一个则对应了状态变量storedData,在solidity实现底层同样是以方法对待了。其中相关关键字解释如下:
1.启动以太坊私有链geth节点
2.部署
(1).Remix编译部署
(2).命令行方式部署
打开终端到solidity文件同一路径下执行:
$ echo "var storageOutput=`solc --optimize --combined-json abi,bin,interface Storage.sol`" > storage.js
在geth控制台加载生成的storage.js:
> loadScript('/opt/idea-IU-191.6183.87/code/storage.js')
true
> storageOutput
{
contracts: {
Storage.sol:Storage: {
abi: "[{\"constant\":true,\"inputs\":[],\"name\":\"storedData\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"data\",\"type\":\"uint256\"}],\"name\":\"set\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]",
bin: "608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80632a1afcd914604157806360fe47b11460595780636d4ce63c146075575b600080fd5b6047607b565b60408051918252519081900360200190f35b607360048036036020811015606d57600080fd5b50356081565b005b60476086565b60005481565b600055565b6000549056fea165627a7a723058202ba55d0448970a9d00978257eeaf760416330480cd5868a50d431999319480b30029"
}
},
version: "0.5.7+commit.6da8b019.Linux.g++"
在storageOutput对象中存储了一个map对象,在storageOutput.contracts["Storage.sol:Storage"]中有两个key,分别定义了合约地ABI和编译后的二进制代码。下面分别获取这两个对象并分别赋值给两个变量,将使用ABI、bin来部署和调用智能合约。
> var storageContractAbi = storageOutput.contracts['Storage.sol:Storage'].abi
undefined
> var storageContract = eth.contract(JSON.parse(storageContractAbi))
undefined
> var storageBinCode = "0x" + storageOutput.contracts['Storage.sol:Storage'].bin
undefined
在正式开始部署之前,需要先解锁账户:
> personal.unlockAccount(eth.accounts[0])
Unlock account 0x8c7ae59ab7e5d510ae3f09a9544978f50315b5f5
Passphrase:
true
然后开始部署,使用web3.eth.contract的new方法向网络中发送部署合约的交易,返回一个web3js合约实例地址storageInstance.
> var deployTransationObject = {from: eth.accounts[0], data: storageBinCode, gas: 1000000};
undefined
> var storageInstance = storageContract.new(deployTransationObject)
undefined
//此时网络中有一个待处理的交易
> txpool.status
{
pending: 1,
queued: 0
}
//该交易信息
> txpool.inspect.pending
{
0x8c7Ae59Ab7E5D510AE3f09A9544978f50315b5F5: {
1: "contract creation: 0 wei + 1000000 gas × 1000000000 wei"
}
}
//开启单线程挖矿、挖到第一个区块后,停止挖矿
> miner.start(1);admin.sleepBlocks(1);miner.stop();
null
//日志文件中的挖矿记录
INFO [04-05|21:00:07.880] Successfully sealed new block number=29 sealhash=5dd54d…a8e99e hash=a4a582…0a5990 elapsed=650.523ms
INFO [04-05|21:00:07.880] mined potential block number=29 hash=a4a582…0a5990
INFO [04-05|21:00:07.880] Commit new mining work number=30 sealhash=4bdbbc…714cad uncles=0 txs=0 gas=0 fees=0 elapsed=168.607µs
交易被确认后,通过storageInstance对象可以看到部署成功后的合约地址,(命令:> storageInstance)
为"0x6cbab38836ef107315599b7ebbf682515b3e4f64",合约地址是独一无二的,它是根据发送者的地址和交易的nonce的Hash计算得出的。在后续操作中通过这个合约地址与合约进行交互,部署合约的交易地址也可以看到为:
"0x5fab980e0c0196c6b5c0b6fcb0a6f6f29ee951e7cb8cbcca67bbb87c64a63a01"。
//根据部署合约的交易Hash查看交易详情
> eth.getTransactionReceipt(storageInstance.transactionHash);
//通过eth.getTransactionReceipt方法获取合约地址:
> var storageAddress = eth.getTransactionReceipt(storageInstance.transactionHash).contractAddress
undefined
> storageAddress
"0x6cbab38836ef107315599b7ebbf682515b3e4f64"
通过获取的合约地址与合约交互:
> var storage = storageContract.at(storageAddress);
undefined
> storage
call表示直接在本地EVM虚拟机调用合约的get()方法,并且call方式调用合约不会修改区块链中的数据。
> storage.get.call()
0
调用合约set()方法,向以太坊中发送一条调用交易:
> storage.set.sendTransaction(42,{from: eth.accounts[0], gas: 1000000})
"0x3f8f8edbdef4e643f1e1313fc0d8caa7d5c114c3af1a1db9a272a557c3dd3cc8"
//网络中有一个待处理的交易:
> txpool.status
{
pending: 1,
queued: 0
}
//交易详情
> txpool.inspect.pending
{
0x8c7Ae59Ab7E5D510AE3f09A9544978f50315b5F5: {
2: "0x6cbAb38836eF107315599b7eBBF682515B3E4F64: 0 wei + 1000000 gas × 1000000000 wei"
}
}
//开始挖矿
> miner.start(1);admin.sleepBlocks(1);miner.stop();
null
//测试合约执行结果
> storage.get.call()