以太坊学习路线——(三)Solidity常用IDE搭建、合约编译、部署、调用

这篇博客演示的基本操作系统环境是CentOS 7,参考书籍:以太坊开发实战——以太坊关键技术与案例分析 第七、九章(吴寿鹤、冯翔、刘涛、周广益   著)。


一、常用IDE

1.remix:是一个浏览器版的solidity开发 IDE,你可以使用在线版的https://remix.ethereum.org/,Remix使用方法,也可以下载下来 https://github.com/ethereum/remix-ide 安装到本地。

2.IntelliJ IDEA也提供了solidity的插件如下:(点击安装即可)

以太坊学习路线——(三)Solidity常用IDE搭建、合约编译、部署、调用_第1张图片

但是安装完毕后,是不能直接手动建立solidity项目的,需要建立一个java项目或者其他项目,然后右键项目名,在弹出的选项中选择New,然后点击New Solidity File选项,会弹出下面的对话框,输入你的合约文件名,并选择Smart contract。

以太坊学习路线——(三)Solidity常用IDE搭建、合约编译、部署、调用_第2张图片

此时可以编写合约文件了,但还不能编译合约,所以需要先下载solidity的编译器:solc ,命令行可执行文件名为solcjs。

然后将编译器连接到IntelliJ idea中,在设置中找到External Tools,可以看到如下图右侧区域,这是我已经创建的外部工具

以太坊学习路线——(三)Solidity常用IDE搭建、合约编译、部署、调用_第3张图片

如果你没有创建过则为空,点击“+”添加外部工具:

以太坊学习路线——(三)Solidity常用IDE搭建、合约编译、部署、调用_第4张图片

设置相关参数如下:

  • Name:solidity
  • Program:/usr/local/node/lib/node_modules/solc/solcjs(注意:这是你安装的solc可执行文件路径,注意核实)
  • Arguments:--abi --bin $FileName$ -o $OutputPath$(--abi表示生成相关应用二进制接口、--bin表示生成二进制文件、$OutputPath$表示这些生成的文件的存储路径)
  • Working directory:$FileDir$(代表在当前路径)

然后编写你的合约文件,要编译时,右键你的合约文件,选择External Tools -> solidity就开始编译你的合约文件了。如下图,左侧为编译输出文件目录,右侧为合约代码,下面是编译输出结果。

以太坊学习路线——(三)Solidity常用IDE搭建、合约编译、部署、调用_第5张图片

二、编辑并编译合约

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实现底层同样是以方法对待了。其中相关关键字解释如下:

  • type:方法类型,包括function、constructor、fallback,默认为function。
  • name:方法名。
  • inputs:方法参数,它是一个对应数组,数组里的每个对象都是一个参数说明。name(参数名)、type(参数类型)。
  • outputs:outputs是一个数组,数组内的参数可以参考上面的inputs,这两个数组的格式是一样的。
  • constant:布尔值,如果为true指明方法,则不会修改合约的状态变量。
  • payable:布尔值,标明是否可以接收ether。

 三、部署合约

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()

 

你可能感兴趣的:(区块链,以太坊,Solidity)