目录
这里将讲解如何在控制台中编译、部署Solidity智能合约。智能合约部署流程如下:
- 使用solc编译智能合约。
- 启动一个以太坊节点(geth或testrpc)。
- 将编译好的合约发布到以太坊的网络上。
- 用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++"}
注意这里不是单引号而是反引号,使用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