【区块链开发入门】(三) Solidity合约编译、部署

目录


这里将讲解如何在控制台中编译、部署Solidity智能合约。智能合约部署流程如下:

  1. 使用solc编译智能合约。
  2. 启动一个以太坊节点(geth或testrpc)。
  3. 将编译好的合约发布到以太坊的网络上。
  4. 用web3.js api调用部署好的合约。

以下是即将编译、部署的智能合约:文件名为Storage.sol,路径为/home/geth/solc。

pragma solidity ^0.7.5;
contract Storage {
    uint256 public storedData;
    function set(uint256 data) public {
        storedData = data;
    }
    function get() public returns (uint256) {
        return storedData;
    }
}

一、编译智能合约

1.安装solc编译工具

在Ubuntu下安装solc:

$ sudo apt-get install -y software-properties-common
$ sudo add-apt-repository -y ppa:ethereum/ethereum
$ sudo apt-get update
$ sudo apt-get install -y solc

安装完成后,查看solc是否安装完毕:

$ solc --version
solc, the solidity compiler commandline interface
Version: 0.7.5+commit.eb77ed08.Linux.g++

2.开始编译合约

进入控制台执行:

$ echo "var storageOutput=`solc --optimize --combined-json abi,bin,interface Storage.sol`" > storage.js
$ cat storage.js
var storageOutput={"contracts":{"Storage.sol:Storage":{"abi":"[{\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"data\",\"type\":\"uint256\"}],\"name\":\"set\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"storedData\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]","bin":"608060405234801561001057600080fd5b5060c28061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80632a1afcd914604157806360fe47b11460595780636d4ce63c146075575b600080fd5b6047607b565b60408051918252519081900360200190f35b607360048036036020811015606d57600080fd5b50356081565b005b60476086565b60005481565b600055565b6000549056fea2646970667358221220b8a43d477b990de26dbe99f3bce799d045ed22483cd83701c916a07f5119dded64736f6c63430007050033"}},"version":"0.7.5+commit.eb77ed08.Linux.g++"}

【区块链开发入门】(三) Solidity合约编译、部署
注意这里不是单引号而是反引号,使用solc命令编译Storage.sol合约,把输出的结果赋值给storageOutput变量,同时输出到storage.js文件中。输出的内容有两部分组成
1)ABI:Application Binary Interface的缩写,字面意思为“应用的二进制接口”,可以通俗理解为合约的借口说明,当合约被编译后,它的abi也就确定了。以下是Storage.sol的ABI:

[
    {
        "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解析后是一个数组,这里包含两个对象,每个对象对应一个合约方法,所以这个合约实际包含两个方法。
有几个关键字:

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

2)bin:合约被编译后的二进制内容。

二.部署合约

1.启动以太坊geth节点

启动之前搭建好的私有链:

$ geth --datadir "./db" --rpc --rpcaddr=0.0.0.0 --rpcport 8545 --rpccorsdomain "*" --rpcapi "eth,net,web3,personal,admin,shh,txtool,debug,miner" --nodiscover --maxpeers 30 --networkid 2020 --port 30303 --mine --minerthreads 1 --etherbase "0xeb680f30715f347d4eb5cd03ac5eced297ac5046" console --allow-insecure-unlock
WARN [11-28|12:56:08.493] Sanitizing cache to Go's GC limits       provided=1024 updated=656
INFO [11-28|12:56:08.520] Maximum peer count                       ETH=30 LES=0 total=30
WARN [11-28|12:56:08.527] The flag --rpc is deprecated and will be removed in the future, please use --http 
WARN [11-28|12:56:08.530] The flag --rpcaddr is deprecated and will be removed in the future, please use --http.addr 
WARN [11-28|12:56:08.569] The flag --rpcport is deprecated and will be removed in the future, please use --http.port 
WARN [11-28|12:56:08.570] The flag --rpccorsdomain is deprecated and will be removed in the future, please use --http.corsdomain 
WARN [11-28|12:56:08.570] The flag --rpcapi is deprecated and will be removed in the future, please use --http.api 
INFO [11-28|12:56:08.570] Smartcard socket not found, disabling    err="stat /run/pcscd/pcscd.comm: no such file or directory"
WARN [11-28|12:56:08.642] The flag --etherbase is deprecated and will be removed in the future, please use --miner.etherbase 
INFO [11-28|12:56:08.643] Set global gas cap                       cap=25000000
INFO [11-28|12:56:08.644] Allocated trie memory caches             clean=163.00MiB dirty=164.00MiB
INFO [11-28|12:56:08.644] Allocated cache and file handles         database=/home/yulin/geth/db/geth/chaindata cache=328.00MiB handles=2048
INFO [11-28|12:56:08.955] Opened ancient database                  database=/home/yulin/geth/db/geth/chaindata/ancient
INFO [11-28|12:56:08.997] Initialised chain configuration          config="{ChainID: 666 Homestead: 0 DAO: <nil> DAOSupport: false EIP150: 0 EIP155: 0 EIP158: 0 Byzantium: <nil> Constantinople: <nil> Petersburg: <nil> Istanbul: <nil>, Muir Glacier: <nil>, YOLO v2: <nil>, Engine: unknown}"
INFO [11-28|12:56:09.006] Disk storage enabled for ethash caches   dir=/home/yulin/geth/db/geth/ethash count=3
INFO [11-28|12:56:09.026] Disk storage enabled for ethash DAGs     dir=/home/yulin/.ethash             count=2
INFO [11-28|12:56:09.027] Initialising Ethereum protocol           versions="[65 64 63]" network=2020 dbversion=<nil>
WARN [11-28|12:56:09.027] Upgrade blockchain database version      from=<nil> to=8
INFO [11-28|12:56:09.054] Loaded most recent local header          number=5 hash="92e8f5…2bdfb9" td=655361 age=1w2d19h
INFO [11-28|12:56:09.059] Loaded most recent local full block      number=5 hash="92e8f5…2bdfb9" td=655361 age=1w2d19h
INFO [11-28|12:56:09.059] Loaded most recent local fast block      number=5 hash="92e8f5…2bdfb9" td=655361 age=1w2d19h
INFO [11-28|12:56:09.059] Loaded local transaction journal         transactions=0 dropped=0
INFO [11-28|12:56:09.062] Regenerated local transaction journal    transactions=0 accounts=0
WARN [11-28|12:56:09.062] Switch sync mode from fast sync to full sync 
INFO [11-28|12:56:09.085] Starting peer-to-peer node               instance=Geth/v1.9.24-stable-cc05b050/linux-amd64/go1.15.5
INFO [11-28|12:56:09.498] IPC endpoint opened                      url=/home/yulin/geth/db/geth.ipc
ERROR[11-28|12:56:09.498] Unavailable modules in HTTP API list     unavailable="[shh txtool]" available="[admin debug web3 eth txpool personal ethash miner net]"
INFO [11-28|12:56:09.500] HTTP server started                      endpoint=[::]:8545 cors=* vhosts=localhost
INFO [11-28|12:56:09.500] Transaction pool price threshold updated price=1000000000
WARN [11-28|12:56:09.501] The flag --minerthreads is deprecated and will be removed in the future, please use --miner.threads 
INFO [11-28|12:56:09.501] Updated mining threads                   threads=1
INFO [11-28|12:56:09.512] Transaction pool price threshold updated price=1000000000
INFO [11-28|12:56:09.509] New local node record                    seq=11 id=a5ba809fab82885b ip=127.0.0.1 udp=0 tcp=30303
INFO [11-28|12:56:09.526] Started P2P networking                   self="enode://57ed0b7b9dfc0ac65e6e6ed8bbf8364484e64855c58c0ba74cb9c13dac5528df4eea51268d37dc4a74d532d2277e463055fa2e38f31356113ab5c2f9ec3e93d7@127.0.0.1:30303?discport=0"
INFO [11-28|12:56:09.527] Commit new mining work                   number=6 sealhash="14d58a…230691" uncles=0 txs=0 gas=0 fees=0 elapsed="183.601µs"
Welcome to the Geth JavaScript console!

instance: Geth/v1.9.24-stable-cc05b050/linux-amd64/go1.15.5
coinbase: 0xeb680f30715f347d4eb5cd03ac5eced297ac5046
at block: 5 (Wed Nov 18 2020 17:32:44 GMT+0800 (CST))
 datadir: /home/yulin/geth/db
 modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

To exit, press ctrl-d
>

加载之前生成的storage.js文件如下,接着就可以使用编译好的合约了。

> loadScript('/home/yulin/geth/solc/storage.js' )
undefined
> storageOutput
{
  contracts: {
    Storage.sol:Storage: {
      abi: "[{\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"data\",\"type\":\"uint256\"}],\"name\":\"set\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"storedData\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]",
      bin: "608060405234801561001057600080fd5b5060c28061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80632a1afcd914604157806360fe47b11460595780636d4ce63c146075575b600080fd5b6047607b565b60408051918252519081900360200190f35b607360048036036020811015606d57600080fd5b50356081565b005b60476086565b60005481565b600055565b6000549056fea2646970667358221220b8a43d477b990de26dbe99f3bce799d045ed22483cd83701c916a07f5119dded64736f6c63430007050033"
    }
  },
  version: "0.7.5+commit.eb77ed08.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

2.部署智能合约

在部署合约之前,需要先解锁账户

> personal.unlockAccount(eth.accounts[0],"6666")
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
{
  0x69E3683b008505FaC66149EE69B01F6EACcD88FE: {
    1: "contract creation: 0 wei + 1000000 gas × 1000000000 wei"
  }
}

开始挖矿,使这笔交易成功写入区块中。

miner.start(1);admin.sleepBlocks(1);miner.stop();

交易被确认后,通过storageInstance对象可以看到部署成功后的合约地址
为"0x0642c9ec44a5d6ecfeb19a370e87ee2e90eaf04e"。

> storageInstance
{
  abi: [{
      inputs: [],
      name: "get",
      outputs: [{...}],
      stateMutability: "nonpayable",
      type: "function"
  }, {
      inputs: [{...}],
      name: "set",
      outputs: [],
      stateMutability: "nonpayable",
      type: "function"
  }, {
      inputs: [],
      name: "storedData",
      outputs: [{...}],
      stateMutability: "view",
      type: "function"
  }],
  address: "0x0642c9ec44a5d6ecfeb19a370e87ee2e90eaf04e",
  transactionHash: "0x82e8e6ac2ecc10359302834236bc806d176cfc52519f43907461038abd1ca0ca"
}

合约地址是独一无二的,它是根据发送者的地址和交易的nonce的Hash计算得出的。在后续操作中通过这个合约地址与合约进行交互,部署合约的交易地址也可以看到为:0x82e8e6ac2ecc10359302834236bc806d176cfc52519f43907461038abd1ca0ca

根据部署合约的交易hash查看交易详情:

eth.getTransactionReceipt(storageInstance.transactionHash);
{
  blockHash: "0xc541afc9f0b8f8239b736d177d57ff15c0877607a1d5d5c19cd018b85d9375af",
  blockNumber: 6,
  contractAddress: "0x0642c9ec44a5d6ecfeb19a370e87ee2e90eaf04e",
  cumulativeGasUsed: 106233,
  from: "0x69e3683b008505fac66149ee69b01f6eaccd88fe",
  gasUsed: 106233,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  root: "0x5a0d54f66bc60c167f5e190878438dfa585d0ae1bd4387791593056f74d5c30a",
  to: null,
  transactionHash: "0x82e8e6ac2ecc10359302834236bc806d176cfc52519f43907461038abd1ca0ca",
  transactionIndex: 0
}

通过eth.getTransactionReceipt方法获取合约地址:

> var storageAddress = eth.getTransactionReceipt(storageInstance.transactionHash).contractAddress
undefined

> storageAddress
"0x0642c9ec44a5d6ecfeb19a370e87ee2e90eaf04e"

通过获取的合约地址与合约交互:

> var storage = storageContract.at(storageAddress);
undefined

> storage
{
  abi: [{
      inputs: [],
      name: "get",
      outputs: [{...}],
      stateMutability: "nonpayable",
      type: "function"
  }, {
      inputs: [{...}],
      name: "set",
      outputs: [],
      stateMutability: "nonpayable",
      type: "function"
  }, {
      inputs: [],
      name: "storedData",
      outputs: [{...}],
      stateMutability: "view",
      type: "function"
  }],
  address: "0x0642c9ec44a5d6ecfeb19a370e87ee2e90eaf04e",
  transactionHash: null,
  allEvents: function(),
  get: function(),
  set: function(),
  storedData: function()
}

call表示直接在本地EVM虚拟机调用合约的get()方法,并且call方式调用合约不会修改区块链中的数据。

> storage.get.call()
0

调用合约set()方法,向以太坊中发送一条调用交易:

> storage.set.sendTransaction(42,{from: eth.accounts[0], gas: 1000000})
"0x13f21fc911ebcb9dbef71549e61b8aba7a97660a287872cac182c2f5223101db"

网络中有一个待处理的交易:

> txpool.status
{
  pending: 1,
  queued: 0
}
> txpool.inspect.pending
{
  0x69E3683b008505FaC66149EE69B01F6EACcD88FE: {
    2: "0x0642C9eC44A5d6ECFeb19a370e87eE2e90eAf04e: 0 wei + 1000000 gas × 1000000000 wei"
  }
}

开始挖矿

> miner.start(1);admin.sleepBlocks(1);miner.stop();
null

交易成功打包后,再调用合约的get()方法,发现合约的storedData变量值已经变成了刚刚设的值42.

> storage.get.call()
42
上一篇:Solidity中super的调用和继承


下一篇:Solidity函数修饰符