在学习了solidity的基本语法后,我们可以尝试来编译和部署一个solidity智能合约,部署流程如下
1)使用solc编译智能合约
2)启动一个以太坊节点(geth或testrpc)
3)将编译好的合约发布到以太坊的网络上
4)用web3.js api调用部署好的合约
如果没有安装过solc,需要先安装,在ubuntu执行以下命令
sudo apt-get install -y software-properties-common
sudo add-apt-respository -y ppa:ethereum/ehtereum
sudo apt-get update
sudo ape-get install -y solc
solc --version//有版本则成功
新建/geth/solc目录,文件Storage.sol,使用的ide是atom,自动补全
pragma solidity ^0.4.25;
contract Storage {
uint256 storedData;
function set(uint256 data) {
storedData = data;
}
function Get() constant returns (uint256) {
return storedData;
}
}
注意下这里不是单引号而是反引号,使用solc命令编译Storage.sol合约,把输出的结果赋值给storageOutput变量,同时输出到storage.js文件中
parallels@parallels-vm:~/geth/solc$ echo "var storageOutput=`solc --optimize --combined-json abi,bin,interface Storage.sol`" > storage.js
Storage.sol:5:3: Warning: No visibility specified. Defaulting to "public".
function set(uint256 data) {
^ (Relevant source part starts here and spans across multiple lines).
Storage.sol:8:3: Warning: No visibility specified. Defaulting to "public".
function Get() constant returns (uint256) {
^ (Relevant source part starts here and spans across multiple lines).
parallels@parallels-vm:~/geth/solc$ cat storage.js var storageOutput={"contracts":{"Storage.sol:Storage":{"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\"}]","bin":"608060405234801561001057600080fd5b5060bf8061001f6000396000f30060806040526004361060485763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166360fe47b18114604d578063b1976a02146064575b600080fd5b348015605857600080fd5b5060626004356088565b005b348015606f57600080fd5b506076608d565b60408051918252519081900360200190f35b600055565b600054905600a165627a7a723058207329b6c82bb730452e864cf684fe48ef2f9e88498eb90bfcdb2c3cbe2d8811830029"}},"version":"0.4.25+commit.59dbf8f1.Linux.g++"}
输出的内容有两部分组成
1)ABI:Application Binary Interface的缩写,字面意思为“应用的二进制接口”,可以通俗理解为合约的借口说明,当合约被编译后,它的abi也就确定了
[
{
"constant":false,//是否会修改合约的状态变量
"inputs":[//方法参数,对应一个数组
{
"name":"data",
"type":"uint256"
}
],
"name":"set",
"outputs":[],
"payable":false,//标明方法是否可以接受ether
"stateMutability":"nonpayable",
"type":"function"//方法类型,包括function,constructor,fallback
},
{
"constant":true,
"inputs":[],
"name":"Get",
"outputs":[
{
"name":"",
"type":"uint256"
}
]
可以看到abi解析后是一个数组,这里包含两个对象,每个对象对应一个合约方法,所以这个合约实际包含两个方法
2)bin:合约被编译后的二进制内容
启动我们之前搭建好的私有链
geth --datadir "./db" --rpc --rpcaddr=0.0.0.0 --rpcport 8545 --rpccorsdomain "*" --rpcapi "eth,net,web3,personl,admin,shh,txpool,debug,miner" --nodiscover --maxpeers 30 --networkid 1981 --port 30303 console
通过attach进入geth控制台
geth --datadir './db' attach ipc:./db/geth.ipc
加载之前生成的storage.js文件
> loadScript('solc/storage.js')
true
> storageOutput
{
contracts: {
Storage.sol:Storage: {
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\"}]",
bin: "608060405234801561001057600080fd5b5060bf8061001f6000396000f30060806040526004361060485763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166360fe47b18114604d578063b1976a02146064575b600080fd5b348015605857600080fd5b5060626004356088565b005b348015606f57600080fd5b506076608d565b60408051918252519081900360200190f35b600055565b600054905600a165627a7a723058207329b6c82bb730452e864cf684fe48ef2f9e88498eb90bfcdb2c3cbe2d8811830029"
}
},
version: "0.4.25+commit.59dbf8f1.Linux.g++"
}
分别获取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],"123456",600)
true
然后发送
> var deployTransationObject = { from:eth.accounts[0],data:storageBinCode,gas:1000000 };
undefined
> var storageInstance = storageContract.new(deployTransationObject)
Error: insufficient funds for gas * price + value
at web3.js:3143:20
at web3.js:6347:15
at web3.js:5081:36
at web3.js:3021:24
at <anonymous>:1:23
错误是因为这个账户里没有以太币,挖矿后重新运行
> var storageInstance = storageContract.new(deployTransationObject)
undefined
现在网络中待处理的交易
> txpool.status
{
pending: 1,
queued: 0
}
> txpool.inspect.pending
{
0xAe5B8710f1a506bac5f6F4F6aC79D796B6Bf2C7d: {
0: "contract creation: 0 wei + 1000000 gas × 1000000000 wei"
}
}
开始挖矿,使这笔交易写入区块
> miner.start(1);admin.sleepBlocks(1);miner.stop();
null
INFO [10-12|13:31:10.896] Successfully sealed new block number=16 sealhash=d59e90…dd3cea hash=8271b3…e14d75 elapsed=4.027s
INFO [10-12|13:31:10.896] ? block reached canonical chain number=9 hash=e06dc1…ab82ee
INFO [10-12|13:31:10.896] ? mined potential block number=16 hash=8271b3…e14d75
INFO [10-12|13:31:10.896] Commit new mining work number=17 sealhash=e14361…feeca6 uncles=0 txs=0 gas=0 fees=0 elapsed=156.404µs
通过storageInstance对象可以看到部署成功后的合约地址
> storageInstance
{
abi: [{
constant: false,
inputs: [{...}],
name: "set",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}, {
constant: true,
inputs: [],
name: "Get",
outputs: [{...}],
payable: false,
stateMutability: "view",
type: "function"
}],
address: "0x25c2bf1b06e9feedb2e3866a309bddd735f5eed0",
transactionHash: "0x5127129f147d8ff5fe1f2ae11fea215ca132fea4d8d5cbd8ef0a6716604a254e",
Get: function(),
allEvents: function(),
set: function()
}
根据部署合约的交易hash查看详情
> eth.getTransactionReceipt(storageInstance.transactionHash);
{
blockHash: "0x8271b3671a0bb1b614489c2d3977c04fa080b9ad3e5d6387df9f3058a7e14d75",
blockNumber: 16,
contractAddress: "0x25c2bf1b06e9feedb2e3866a309bddd735f5eed0",
cumulativeGasUsed: 103631,
from: "0xae5b8710f1a506bac5f6f4f6ac79d796b6bf2c7d",
gasUsed: 103631,
logs: [],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
root: "0xf24b23c59413cdf30f5b52b191b90e0b85c4ca3efa55443f1165199ffd3ee7fb",
to: null,
transactionHash: "0x5127129f147d8ff5fe1f2ae11fea215ca132fea4d8d5cbd8ef0a6716604a254e",
transactionIndex: 0
}
获取合约地址
> var storageAddress =eth.getTransactionReceipt(storageInstance.transactionHash).contractAddress
undefined
> storageAddress
"0x25c2bf1b06e9feedb2e3866a309bddd735f5eed0"
通过获取合约地址和合约进行交互
> var storage = storageContract.at(storageAddress);
undefined
> storage
{
abi: [{
constant: false,
inputs: [{...}],
name: "set",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}, {
constant: true,
inputs: [],
name: "Get",
outputs: [{...}],
payable: false,
stateMutability: "view",
type: "function"
}],
address: "0x25c2bf1b06e9feedb2e3866a309bddd735f5eed0",
transactionHash: null,
Get: function(),
allEvents: function(),
set: function()
}
call表示直接在本地evm虚拟机中调用合约方法get()
> storage.Get.call()//在以太坊虚拟机本地执行功能,不会记录在区块链上,不需要花费以太币
0
调用合约set方法,先解锁
> storage.set.sendTransaction(42,{from:eth.accounts[0],gas:1000000})//通过发送交易来执行功能调用,永久记录在区块链上
"0x3c6b089a991066a8dae0c2fa938b70f016279d330107d30ba1246abe8d7c94ac"
> txpool.inspect.pending
{
0xAe5B8710f1a506bac5f6F4F6aC79D796B6Bf2C7d: {
1: "0x25C2Bf1b06e9FeedB2E3866A309bDDd735F5eED0: 0 wei + 1000000 gas × 1000000000 wei"
}
}
开启挖矿
miner.start(1);admin.sleepBlocks(1);miner.stop();
再调用合约的get方法
> storage.Get.call()
42
到这里就是成功通过控制台部署和调用了智能合约,可以看出手动实现是一件比较麻烦的事,之后会学习使用truffle完成编译和部署