想看原汁原味的英文文档可移步至(https://kauri.io/#communities/Getting%20started%20with%20dapp%20development/understanding-smart-contract-compilation-and-depl),本文做了一些简单翻译,且只针对ubuntu系统
要开发dapp,有很多事情要做,如:
- 代码编译
- 生成abis
- 测试
- 部署
步骤
- 编译,生成 字节码 和 函数描述符 (Application Binary Interface,简称ABI)
- 将 字节码 和 参数 打包到一个 事务 中
- 部署合同的账户签署事务
- 将已签署的事务发送到链上,并挖矿
编译
编译智能合约需要solc编译器,通常情况下,我们都是使用框架开发,而一般的框架都是预先内置了solc编译器。
常用框架有:
这里我们使用原生的方式编译,所以需要手动安装solc。
安装solc编译器
add-apt-repository ppa:ethereum/ethereum
apt update
apt install solc
更多安装细节可去查看官方文档(http://solidity.readthedocs.io/en/latest/installing-solidity.html)
安装成功后,可通过下面命令查看solc版本
solc --version
安装jq
jq是linux系统下一个格式化输出json的工具,这里安装jq只是为了更好的查看json文件而已,并非必须
apt install jq
更多安装jq的细节移步至(https://stedolan.github.io/jq/download)
编译solidity文件
通过编译solidity文件,我们会得到:
- 字节码
- ABI(通过ABI可与已经部署的合约进行交互)
创建工作目录/文件
mkdir dapp-series-bounties
cd dapp-series-bounties
touch Bounties.sol
编写sol文件
将(https://github.com/kauri-io/kauri-fullstack-dapp-tutorial-series/blob/master/manual-compilation-and-deploy/Bounties.sol)中的文件内容拷贝下来写到自己的Bounties.sol文件中
这里要注意文件中的solidity编译器版本需要和你本地安装solidity编译器版本保持一致
执行编译
执行编译的命令如下:
solc --combined-json abi,bin Bounties.sol > Bounties.json
上述命令会将编译生成的 字节码 和 abi 写入到Bounties.json文件中
部署
所谓部署,就是上链,上哪个链?即 需要一个以太坊环境进行部署。
为此,我们将使用 Ganache-CLI 运行本地开发区块链环境。
安装ganache-cli
npm install -g ganache-cli
由于我使用的是ubuntu,会报没有npm命令,所以执行下述命令安装npm
安装npm
sudo apt install npm
配置npm源
npm config set registry http://registry.npm.taobao.org
更新npm
npm install npm -g
个人理解ganache-cli就是区块链的本地开发环境
安装geth(go-etherenum)
什么是geth
文档翻译过来的意思是:一个以太坊节点的Go实现
太难理解,下述来自(https://www.jianshu.com/p/9eb600f0e0e4)的通俗解释
- Geth可以当客户端来使用
打开Geth,用户可以创建自己的以太坊私有链、管理账户、挖矿、交易、部署执行智能合约等,用户还可以下载以太坊主链、解析主链上任意交易数据等。
- Geth可以当服务器来使用
Geth提供很多服务和丰富的API,用户可以开发程序通过调用Geth服务,实现自己想要的功能,比如获取一段时间内以太币的所有交易账户。
安装命令
apt install software-properties-common
add-apt-repository -y ppa:ethereum/ethereum
apt update
apt install ethereum
更多安装细节参考(https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum)
启动ganache-cli 并连接geth控制台
启动ganache-cli区块链本地开发环境
ganache-cli
启动后,会给到你一些东西
- 账号(Available Accounts)
- 私钥(Private Keys)
- 钱包(HD Wallet)
- Gas Price
- Gas Limit
- 启动服务(监听8545端口)
连接geth控制台到环境
geth attach http://localhost:8545
部署前的准备(获取字节码和abi)
要准备好 字节码 和 abi,在编译的时候,已经将这些东西写入到Bounties.json文件中了,所以需要将编译好json文件加载到geth控制台
准备js文件
因为(可能)geth控制台不能加载json文件,所以需要将json文件的内容写入到一个js文件(Bounties.js)中
在js文件中定义一个变量,其值等于sol文件编译的结果
echo "var bountiesOuput=" > Bounties.js
cat Bounties.json >> Bounties.js
geth控制台加载js文件
通过加载js文件,可以把变量加载到控制台环境中
在geth控制台中运行
> loadScript('Bounties.js')
true
> bountiesOutput
{
contracts: {
Bounties.sol:Bounties: {
abi: "[{\"constant\":false,\"inputs\":[{\"name\":\"_bountyId\",\"type\":\"uint256\..."}]"}],
bin: "608060405234801561001057600080fd5b506..."
}
},
version: "0.4.24+commit.e67f0147.Darwin.appleclang"
}
geth获取abi
因为abi是字符串格式,所以需要先解析
查看abi类型
> typeof bountiesOutput.contracts["Bounties.sol:Bounties"].abi
"string"
解析abi
> var bountiesContract = eth.contract(JSON.parse(bountiesOutput.contracts["Bounties.sol:Bounties"].abi));
undefined
查看解析后的abi
> bountiesContract
{
abi: [{
constant: false,
inputs: [{...}, {...}],
name: "fulfillBounty",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}, {
constant: false,
inputs: [{...}, {...}],
name: "issueBounty",
outputs: [{...}],
payable: true,
stateMutability: "payable",
type: "function"
}, {
....
at: function(address, callback),
getData: function(),
new: function()
geth控制台获取字节码
将bin(已编译的二进制字节码)准备为变量。 注意,为了获得期望的十六进制值,我们需要在开头添加“ 0x”。
> var bountiesBin = "0x" + bountiesOutput.contracts["Bounties.sol:Bounties"].bin
undefined
开始部署
构造交易对象
- 从coinbase账户发送
- 交易数据
- gas值
> var deployTransationObject = { from: eth.coinbase, data: bountiesBin, gas: 3000000 };
undefined
通过new构造交易实例(使用交易对象作为参数)
> var bountiesInstance = bountiesContract.new(deployTransationObject);
> bountiesInstance
{
abi: [{
constant: false,
inputs: [{...}, {...}],
name: "fulfillBounty",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}, {
constant: false,
inputs: [{...}, {...}],
name: "issueBounty",
outputs: [{...}],
payable: true,
stateMutability: "payable",
type: "function"
}, {
...}],
address: undefined,
transactionHash: "0xbeee5a5db59504c289e30a9843d8bf05bd0dcd66831993fde6a3986e2f022a52"
}
几段看不懂的翻译(对以太坊还不太熟)
因此,bountiesInstance是一个web3对象,代表部署到我们的开发环境中的bounties合同实例。
根据所用Geth版本的不同,合同实例中可能会或可能不会设置地址字段。 通常,这将在挖掘部署合同的事务时设置。
如果没有,因为上述地址字段设置为undefined,我们将必须手动设置合同地址
设置合同地址
如果合约中address为空,则需要手动设置合同地址
查看合同地址
我们可以通过查看部署合同交易的交易收据来找到合同地址。 我们的bountiesInstance对象将具有对transactionHash的引用。
eth.getTransactionReceipt(bountiesInstance.transactionHash);
{
blockHash: "0x565c4ebb83d9036518c33634d425f37b9ef3aa125e9b09386fbbdd44099892d9",
blockNumber: 1,
contractAddress: "0x1f81f1dd0de1670eac0bfa9e00a854733470d646",
cumulativeGasUsed: 911612,
gasUsed: 911612,
logs: [],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
status: "0x1",
transactionHash: "0xbeee5a5db59504c289e30a9843d8bf05bd0dcd66831993fde6a3986e2f022a52",
transactionIndex: 0
}
上述可知,合同地址是0x565c4ebb83d9036518c33634d425f37b9ef3aa125e9b09386fbbdd44099892d9
创建指向正确合约地址的合约对象
> var bountiesAddress = eth.getTransactionReceipt(bountiesInstance.transactionHash).contractAddress
> var bountiesInstance = bountiesContract.at(bountiesAddress)
undefined
> bountiesInstance
{
abi: [{
constant: false,
inputs: [{...}, {...}],
name: "fulfillBounty",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}, {
constant: false,
inputs: [{...}, {...}],
name: "issueBounty",
outputs: [{...}],
payable: true,
stateMutability: "payable",
type: "function"
}, {
...}],
address: "0x1f81f1dd0de1670eac0bfa9e00a854733470d646",
transactionHash: null,
BountyCancelled: function(),
BountyFulfilled: function(),
BountyIssued: function(),
FulfillmentAccepted: function(),
acceptFulfillment: function(),
allEvents: function(),
bounties: function(),
cancelBounty: function(),
fulfillBounty: function(),
issueBounty: function()
可见,合约地址已经设置成功
与合约交互
实则是合约部署好后,通过abi设置一个触发条件,用于自动执行合约
通过合约对象的 issueBounty 方法来设置触发条件,其参数有:
- string_data:
- unit64_deadline: 合约结束时间
- data
命令类似如下:
> bountiesInstance.issueBounty("some requirements","1691452800",{ from: eth.accounts[0], value: web3.toWei(1, "ether"), gas: 3000000 });
"0xb3a41fa36c09010abbfa9bf80c3cd11242e4476506d6bf8b363b8feeb3cf946d"
> eth.getTransactionReceipt("0xb3a41fa36c09010abbfa9bf80c3cd11242e4476506d6bf8b363b8feeb3cf946d")
{
blockHash: "0x8acd35b251c3f1c23a6276540e73b958de28686a89367ed108bef9f614771099",
blockNumber: 5,
contractAddress: null,
cumulativeGasUsed: 133594,
gasUsed: 133594,
logs: [{
address: "0x1f81f1dd0de1670eac0bfa9e00a854733470d646",
blockHash: "0x8acd35b251c3f1c23a6276540e73b958de28686a89367ed108bef9f614771099",
blockNumber: 5,
data: "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c74a4fba809c8f0e6b410b349f2908a4dbb881230000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000011736f6d6520726571756972656d656e7473000000000000000000000000000000",
logIndex: 0,
topics: ["0xba1576d8891bfe57a45ac4b986d4a4aa912c62f44771d4eec8ab2ce06e3be5b7"],
transactionHash: "0xb3a41fa36c09010abbfa9bf80c3cd11242e4476506d6bf8b363b8feeb3cf946d",
transactionIndex: 0,
type: "mined"
}],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000040000000000000000000000000000004000000000000010000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
status: "0x1",
transactionHash: "0xb3a41fa36c09010abbfa9bf80c3cd11242e4476506d6bf8b363b8feeb3cf946d",
transactionIndex: 0
}
结果解读
上面是发出赏金交易的日志输出和显示交易已成功开采的交易收据。
因为这是第一个颁发的赏金,所以我们从合同代码中知道issueBounty函数将创建bountyId为0的赏金。
检查数据是否正确
现在我们知道了bountyId,我们可以调用bountiesInstance web3对象的bounties函数来仔细检查issueBounty函数是否正确存储了我们的数据
> bountiesInstance.bounties.call(0)
["0xc74a4fba809c8f0e6b410b349f2908a4dbb88123", 1691452800, "some requirements", 0, 1000000000000000000]
完事!!!!
总结
- 编译出Bounties.sol智能合约
- 将其部署到本地开发区块链
- 通过发送交易来发放赏金
- 通过致电合同签出赏金数据
题外
经过一系列的苦逼操作后,终于完成了一个智能合约的编译部署。
挺麻烦的,写智能合约就够苦逼了,编译部署还这么麻烦。
所以我们才需要框架,框架向我们隐藏了此过程的复杂性,让我们在开发dApp时专注于编写智能合约即可。
框架再推: