cfxtruffle 使用完全指南
Truffle 是以太坊生態著名的智慧合約開發工具,提供編譯,連結,測試,部署等實用功能,為廣大 Solidity 開發者所喜愛。
Conflux 作為新一代高效能公鏈,不僅在完全去中心化的前提下實現了兩個量級的效能提升,還實現了跟 EVM 相容的虛擬機器,
意味著 Dapp 開發者不用學習新開發語言即可以在 Conflux 網路上開發應用。
為了提升 Conflux 的合約開發體驗,官方最近也對 Truffle 進行了遷移改造,打造了 Conflux-Truffle,使之能夠支援 Conflux 的合約開發。
本文會詳細介紹如何使用 Conflux Truffle 開發 Conflux 智慧合約,從環境搭建,建立專案,開發,編譯,測試,部署,一一介紹。
- 基本概念介紹
- Dependencies 準備
- 使用 cfxtruffle 開發智慧合約
- 參考文件
基本概念介紹
區塊鏈世界是去中心化的,所有參與的節點具有相同的資料,人人平等。而資料在區塊鏈上的組織形式是:首先多筆交易被一塊打包形成一個區塊(block),
然後區塊之間根據先後順序連結起來,形成一條鏈,因此叫區塊鏈(blockchain)。最初的區塊鏈(bitcoin chain)只支援轉賬,因此只有一個應用(bitcoin)。
以太坊開創性的新增了 EVM 的功能,具備了圖靈完整性,因此在其上可以自由開發各種去中心化應用(Dapp)。
Epoch & storageLimit
傳統的區塊鏈賬本是一條單鏈,從前往後每個區塊都有一個編號,叫做區塊號( block number),conflux 開發了一種全新的賬本結構: 樹圖,實現了高吞吐,低延遲。
在樹圖賬本結構中,如果只看父邊他是一個 Tree,如果父邊引用邊都看則是一個 Graph。正是這種結構使得 conflux 網路可以併發出塊,即多個區塊可以
都在某個區塊之後生成。因此在 Conflux 是沒有 block number 的概念。
但為了實現全序,Conflux 通過 GHAST 規則從創世區塊開始,在其所有子區塊中選擇最重子樹 block 為 pivot block,所有的 pivot block 鏈到一塊也形成一條鏈
定義為 pivot chain,如果只看 pivot chain 其跟普通的區塊鏈結構一致,在這條鏈上基於每個 pivot block 定義一個Epoch,因此你可以把 conflux 中的
Epoch 理解為跟 block number 對應的概念,只不過 conflux 中的每個 epoch 中可能會有多個 block。
在現實世界中,傳送轉賬交易需要給銀行付手續費,在比特幣中傳送交易需要給礦工付手續費,在以太坊中同樣如此。具體來講,以太坊網路的交易最終是由礦工
執行的 EVM 執行的,gas 是用來衡量一筆交易執行的工作量(可以理解為工作的工時),交易傳送者,傳送交易時可以指定願意給每個工作量付的價格即 gasPrice。
因此最終一筆交易的手續費為 gas * gasPrice。
在傳送一筆交易時指定的 gas 則是一個限制值,即傳送方最大願意為一筆交易支付 gas 這麼多的工時,如果交易需要的工作量超過 gas,則不會再付錢,交易不會被執行。
在 Dapp 系統中,交易執行除了需要礦工進行計算付出計算資源外,還需要礦工儲存合約的狀態,因此需要付出儲存資源。在 Conflux 系統中傳送交易時,還需要為狀態儲存
抵押一部分費用,因此在 conflux 中傳送交易時會比以太坊多一個 storageLimit 引數,用於設定願意為某筆交易儲存所抵押的費用上限。在合約釋放掉使用的儲存空間之後,抵押的費用
也會得到返還。
Dapp
傳統 App 的所有狀態都儲存於中心化伺服器的資料庫上,其狀態的查詢和修改,直接通過 API 到資料庫中執行 SQL 操作即可,雖然速度快,但有資料隨便篡改,隱私洩露等問題。
Dapp 是執行於區塊鏈系統之上的應用,被稱為去中心化應用。應用狀態儲存於區塊鏈系統之上,狀態查詢需要通過網路,狀態更改只能通過傳送交易的形式
Wallet
在傳統網際網路應用中,是通過賬號訪問服務的,但資料本質儲存於服務提供商的伺服器上。在區塊鏈世界中一切的交易傳送都需要通過錢包來實現,比如以太坊的 MetaMask,Conflux 的 Portal。
在錢包中你可以檢視餘額,傳送交易,與 Dapp 互動,領取測試網代幣等。
其實本質的是,錢包中存有你賬戶的私鑰,在區塊鏈世界中只有私鑰可以簽名傳送交易。
私鑰只應該被你自己所保有,不能被別人知道。
Dependencies 準備
在進行智慧合約開發前,需要先準備一些開發環境.
NPM
npm 是 node.js 的包管理工具,因為 cfxtruffle 是使用 node 開發,所以需要使用 npm 安裝。
npm 包含在 node 的安裝包中,只要 node 安裝成功,npm 也就安裝成功了。
安裝 node 可以直接下載官方的安裝包,或者使用 nvm,
安裝好之後,可以使用如下命令檢視版本:
$ node -v
$ npm -v
cfxtruffle
npm 安裝成功之後,就能安裝 cfxtruffle 了。
$ npm install -g conflux-truffle
# 安裝完成之後, 可使用如下命令測試
$ cfxtruffle -v
有兩點需要注意的地方:
- npm install 的時候需要通過 -g 指定全域性安裝
- npm install 包的名字為
conflux-truffle
, 安裝後的命令列程式為cfxtruffle
conflux-rust docker
開發合約除錯需要執行本地節點,執行 conflux 最簡單的方法是使用 docker 執行 conflux-rust 映象,此方式不需要自己編譯程式,準備配置檔案。只需要本地安裝有 docker 環境即可。
# 下載 conflux-rust docker 映象
$ docker pull confluxchain/conflux-rust
# 啟動節點
$ docker run -p 12537:12537 --name cfx-node confluxchain/conflux-rust
簡單一個命令即啟動了一個本地 Conflux 節點,並且該節點會同時建立 10 個本地賬號,每個賬號分配 1000 CFX 用於合約部署和互動,且這些賬號會自動 unlock。
conflux-rust 映象啟動之後,cfxtruffle 可以直接使用它來部署合約,以及同合約互動。
當然也可以自己編譯或下載節點程式,自定義配置檔案,然後執行本地節點,具體可參看這篇介紹。
當然節點執行起來之後需要手動建立本地賬號,並轉賬給他們使之有餘額,以及手動進行解鎖。
說明:
- 本地賬號可以使用節點命令列(conflux account new)或 local rpc 建立,本地賬號可以被節點程式訪問,並直接用於交易簽名。
- 本地賬號可以直接通過
cfx_sendTransaction
傳送交易,交易的簽名操作由節點完成,不需要事先簽名,本地開發 Dapp 非常方便。 - 本地賬號為了安全,需要解鎖才能直接傳送交易,可以在呼叫
cfx_sendTransaction
時通過第二個引數傳遞密碼, 也可以事先呼叫 unlock rpc 解鎖賬號。 - 目前的 cfxtruffle 需要 0.6.0 以上版本的 conflux-rust 映象搭配使用。
conflux portal
Conflux portal 是一款瀏覽器外掛錢包,支援 Chrome 和 Firefox 等瀏覽器。在官網點選連結即可下載安裝(可能需要翻牆)。
安裝後該錢包可切換不同 conflux 網路, 當然也可以連線本地網路,並且可以申領測試網路代幣,如果合約本地測試完之後,可部署到測試網路測試。
使用 cfxtruffle 開發智慧合約
環境準備好之後,可以使用 cfxtruffle 開發智慧合約了,我們會使用 cfxtruffle 開發一個具有鑄幣和轉賬功能的 coin 合約。
首先來看下 cfxtruffle 所有命令:
$ cfxtruffle -h
Usage: cfxtruffle <command> [options]
Commands:
compile Compile contract source files
config Set user-level configuration options
console Run a console with contract abstractions and commands available
create Helper to create new contracts, migrations and tests
debug Interactively debug any transaction on the blockchain
deploy (alias for migrate)
exec Execute a JS module within this Conflux-Truffle environment
help List all commands or provide information about a specific command
init Initialize new and empty Conflux project
install Install a package from the Conflux Package Registry
migrate Run migrations to deploy contracts
networks Show addresses for deployed contracts on each network
obtain Fetch and cache a specified compiler
opcode Print the compiled opcodes for a given contract
publish Publish a package to the Conflux Package Registry
run Run a third-party command
test Run JavaScript and Solidity tests
unbox Download a Conflux-Truffle Box, a pre-built Conflux-Truffle project
version Show version number and exit
watch Watch filesystem for changes and rebuild the project automatically
See more at http://truffleframework.com/docs
我們也可以檢視某個具體命令的使用幫助:
$ cfxtruffle help create
建立專案
第一步我們需要建立一個 cfxtruffle 基礎專案:
# 建立一個空專案
$ cfxtruffle init project-name
# 檢視專案目錄結構
$ cd project-name && tree
.
├── contracts
│ └── Migrations.sol
├── migrations
│ └── 1_initial_migration.js
├── test
└── truffle-config.js
3 directories, 3 files
新建立的專案有以下資料夾和檔案:
contracts
solidity 合約程式碼目錄migrations
合約部署遷移指令碼目錄test
單元測試目錄truffle-config.js
cfxtruffle 配置檔案(js 檔案)
另外 cfxtruffle 也提供了許多功能更完善的專案模板(box)或者叫腳手架,使用模板,會節省許多初始化工作:
$ mkdir project-name && cd project-name
$ cfxtruffle unbox metacoin
你可以到官方的 box) 列表看看是否有滿足你需求的模板.
注:這兩個命令,在國內可能需要翻牆。
新增合約
init 命令只會建立一個空專案,裡面包含一個基礎的 Migrations 合約及它的遷移指令碼。
我們需要新增新的檔案,來用於開發新的合約,create
命令可以用於建立一個合約相關的所有檔案,包括合約原始碼(contract),部署指令碼(migration),測試指令碼(test)。
其使用方式為: cfxtruffle create <artifact_type> <ArtifactName>
現在我們來建立 Coin 合約的原始碼檔案
$ cfxtruffle create contract Coin
以上命令執行完之後, 會在 contracts 目錄建立一個 Coin.sol 檔案,我們可以在其中編寫 solidity 程式碼。
如下是 Coin 合約的 solidity,其實現了鑄幣,轉賬,餘額查詢的功能。
pragma solidity ^0.5.10;
contract Coin {
// An `address` is comparable to an email address - it's used to identify an account on Ethereum.
// Addresses can represent a smart contract or an external (user) accounts.
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#address
address public owner;
// A `mapping` is essentially a hash table data structure.
// This `mapping` assigns an unsigned integer (the token balance) to an address (the token holder).
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#mapping-types
mapping (address => uint) public balances;
// Events allow for logging of activity on the blockchain.
// Ethereum clients can listen for events in order to react to contract state changes.
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#events
event Transfer(address from, address to, uint amount);
// Initializes the contract's data, setting the `owner`
// to the address of the contract creator.
constructor() public {
// All smart contracts rely on external transactions to trigger its functions.
// `msg` is a global variable that includes relevant data on the given transaction,
// such as the address of the sender and the ETH value included in the transaction.
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties
owner = msg.sender;
}
// Creates an amount of new tokens and sends them to an address.
function mint(address receiver, uint amount) public {
// `require` is a control structure used to enforce certain conditions.
// If a `require` statement evaluates to `false`, an exception is triggered,
// which reverts all changes made to the state during the current call.
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions
// Only the contract owner can call this function
require(msg.sender == owner, "You are not the owner.");
// Ensures a maximum amount of tokens
require(amount < 1e60, "Maximum issuance succeeded");
// Increases the balance of `receiver` by `amount`
balances[receiver] += amount;
}
// Sends an amount of existing tokens from any caller to an address.
function transfer(address receiver, uint amount) public {
// The sender must have enough tokens to send
require(amount <= balances[msg.sender], "Insufficient balance.");
// Adjusts token balances of the two addresses
balances[msg.sender] -= amount;
balances[receiver] += amount;
// Emits the event defined earlier
emit Transfer(msg.sender, receiver, amount);
}
}
該合約具體屬性和方法如下:
- owner屬性:合約所有者,只有合約所有者可以執行鑄幣操作,owner 具有 public 屬性,因此也會自動建立一個對應的方法,用於查詢合約的所有者
- balances屬性: 餘額,map 型別,用於儲存賬戶的餘額資訊,
- constructor方法:該方法僅會在合約建立的時候執行一次,之後不會再執行,主要用於設定合約初始狀態
- mint方法:鑄幣,可以給某個地址生成指定數額的 coin
- transfer方法:轉賬,給另外賬戶轉賬
- Transfer事件:每當轉賬操作發生,會觸發一個此事件,
compile
solidity 是一種高階語言,其語法參考了C++, python, js, 但 solidity 不能直接部署,需要先進行編譯。
這其實跟 C++ 程式比較類似,需要編譯為 binary 程式才能執行。
solidity 的編譯器是 solc
可以單獨安裝和使用,不過 cfxtruffle 中整合了編譯 compile
的功能可以直接使用。
# 編譯合約
$ cfxtruffle compile
# 首次執行,會建立一個 build 目錄,編譯後的內容會儲存在該目錄於合約對應的 json 檔案中。
solidity 編譯後,會生成 bytecode 和 abi(Application Binary Interface 合約對外介面的描述),這兩者在部署的時候都會用到。
cfxtruffle 編譯後生成的 json 檔案中,該檔案包含有合約的 bytecode,abi,ast,name 等資訊,該檔案在之後的部署,除錯等步驟中都會被用到。
truffle-config.js
truffle-config.js
是專案的配置檔案,非常重要,cfxtruffle 許多命令執行時都會使用取這裡的配置。
module.exports = {
networks: {
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 12537, // Standard Conflux port (default: none)
network_id: "*", // Any network (default: none)
},
},
}
其中最重要的配置項為 networks,用於配置 cfxtruffle 部署互動的網路,預設使用 development 網路。
除此之外,還可以配置測試,compiler 等資訊,具體參看官方文件
本地部署
合約開發的時候,會先在本地節點部署和測試,前邊我們介紹瞭如何使用 docker 啟動一個本地節點。啟動的節點可以用來做本地部署和測試。
cfxtruffle 中的 migrate
命令主要用於合約部署(deploy 是 migrate 的別名)。migrations
資料夾用於存放 migration 指令碼,這些指令碼管理著合約的部署,通常每個合約對應一個 migration 指令碼。
cfxtruffle 專案建立後會有一個 Migrations.sol
合約,及其對應的遷移指令碼。
該合約主要用於在鏈上記錄合約的部署序號(整數序號)。
每次 migrate 命令執行時會到鏈上查詢上次部署的位置,然後判斷是否有新合約需要部署。
Migrations.sol
合約程式碼如下:
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.21 <0.7.0;
contract Migrations {
address public owner;
uint public last_completed_migration;
constructor() public {
owner = msg.sender;
}
modifier restricted() {
if (msg.sender == owner) _;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
}
現在讓我們來使用 create 命令,新增一個 Coin 合約的部署指令碼
$ cfxtruffle create migration Coin
# 生成的 migration 指令碼檔名中,會包含一個時間戳數字,我們需要手動改為遞增的序號比如 2_coin.js
然後可以在裡面加入如下程式碼
// require the Coin artifact
const Coin = artifacts.require("Coin")
module.exports = function(deployer) {
// when `deploy` command run, will execute code here
deployer.deploy(Coin);
};
設定完成之後可以執行部署命令了
$ cfxtruffle deploy
...
...
2_coin.js
=========
Deploying 'Coin'
----------------
> transaction hash: 0xd001fb34df8e634e21d7d225bfd0da6128237cd74f170fbc97ad820098ceaeff
> Blocks: 0 Seconds: 0
> contract address: 0x8DCe85c454d401318C03956529674b9E2B8E8680
> block number: 1608
> block timestamp: 1595475207
> account: 0x1357DA1577f40EE27aE8870C7f582bD345C65A1c
> balance: 997.71313608
> gas used: 437390 (0x6ac8e)
> gas price: 20 GDrip
> value sent: 0 CFX
> total cost: 0.0087478 CFX
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.0087478 CFX
Summary
=======
> Total deployments: 2
> Final cost: 0.01221746 CFX
deploy 命令執行完成之後,會輸出部署的結果,比如交易hash,合約地址,花費的費用等。
合約互動
合約部署之後,通常我們會需要跟合約進行互動,比如查詢狀態,發起轉賬等等。cfxtruffle 內含 console
命令,可以開啟一個互動式的命令列環境,
該環境提供了合約的 JS 物件,賬號列表,以及 js-conflux-sdk 例項及 util 等非常方便。
你可以在裡面例項化合約物件,然後通過例項物件與合約互動,比如:查詢狀態,修改餘額等。
以下是 Coin 合約互動的例子:
$ cfxtruffle console
cfxtruffle(development)> .help # 檢視幫助
cfxtruffle(development)> Object.keys(global) # 檢視console環境,可用的全域性物件: accounts, cfx, cfxutil, Coin, Migrations
# 例項一個 Coin 合約, 呼叫合約物件的 deployed() 方法
cfxtruffle(development)> let coin = await Coin.deployed()
# 檢視合約的 owner 是誰
cfxtruffle(development)> let owner = await coin.owner()
cfxtruffle(development)> owner
'0x1357DA1577f40EE27aE8870C7f582bD345C65A1c'
# 檢視所有可用 account
cfxtruffle(development)> accounts
[
'0x1357DA1577f40EE27aE8870C7f582bD345C65A1c',
'0x148A9696F8DCf4d6cB01eC80F1047a3476bA5C56',
'0x1f69a930B6A4F2BC5Ac03B79A88af9f6bBa0d137'
]
# 查詢餘額
cfxtruffle(development)> let balance = await coin.balances('0x1357DA1577f40EE27aE8870C7f582bD345C65A1c')
cfxtruffle(development)> balance.toString()
# 鑄造新幣
cfxtruffle(development)> await coin.mint('0x1357DA1577f40EE27aE8870C7f582bD345C65A1c', 10000)
cfxtruffle(development)> balance = await coin.balances('0x1357DA1577f40EE27aE8870C7f582bD345C65A1c')
cfxtruffle(development)> balance.toString()
'10000'
# 轉賬
cfxtruffle(development)> await coin.transfer('0x148A9696F8DCf4d6cB01eC80F1047a3476bA5C56', 100)
cfxtruffle(development)> balance = await coin.balances('0x1357DA1577f40EE27aE8870C7f582bD345C65A1c')
cfxtruffle(development)> balance.toString()
'9900'
# 指定交易的 gasPrice
cfxtruffle(development)> await coin.transfer('0x148A9696F8DCf4d6cB01eC80F1047a3476bA5C56', 100, {gasPrice: '0x100'})
# 整合的 cfx 物件是一個 js-conlfux-sdk 例項
cfxtruffle(development)> await cfx.getBalance('0x148A9696F8DCf4d6cB01eC80F1047a3476bA5C56')
cfxtruffle(development)> await cfx.getNextNonce("0xbbd9e9be525ab967e633bcdaeac8bd5723ed4d6b")
# cfxutil
cfxtruffle(development)> let drip = cfxutil.unit.fromCFXToGDrip(0.1)
cfxtruffle(development)> let randomKey = cfxutil.sign.randomPrivateKey()
#
cfxtruffle(development)> .exit
- 除此之外還可以在 console 部署新合約,例項化指定地址的合約,estimateGas 等, 關於合約的詳細互動方式,可以參看這裡文件
- 另外在 console 中,可以直接執行 cfxtruffle 的命令: networks, compile 等
- 注:目前 cfxtruffle 暫不支援 develop 命令
合約測試
cfxtruffle 內建了測試框架(Mocha & Chai),可以編寫 js 和 solidity 測試,所有的測試程式碼位於 test 資料夾中。
接下來使用 create 命令建立一個測試檔案。
$ cfxtruffle create test Coin
在裡面可以新增以下程式碼
const Coin = artifacts.require("Coin");
// 注意:這裡使用 contract 而不是 describe
contract("Coin", function(accounts) {
it("should assert true", async function() {
let instance = await Coin.deployed();
await instance.mint(accounts[0], 10000)
let balance = await instance.balances(accounts[0]);
assert.equal(balance.toString(), '10000', "10000 wasn't in the first account");
});
});
然後使用 test 命令即可執行測試
$ cfxtruffle test
cfxtruffle 每次執行測試,都會完全新部署一個合約(clean-room), 關於具體如何編寫測試程式碼,可以參看 js 和 solidity 的測試編寫說明。
部署到遠端節點
合約在本地開發,測試完成之後,可以嘗試部署到測試網路,或者到主網釋出合約,cfxtruffle 也支援遠端部署。
首先需要在專案配置檔案中新增一個新的網路配置比如 testnet
, 裡面設定好 host 和 port。
然後需要設定 privateKeys 欄位,該欄位是一個私鑰陣列(遠端部署只能在本地進行簽名)。
testnet: {
host: "wallet-testnet-jsonrpc.conflux-chain.org",
port: 12537,
network_id: "*",
// 注意:從 portal 獲取的私鑰需要新增 0x 字首,privateKeys 也可以指定單個 key,若配置了私鑰,請注意不要將程式碼上傳到公開程式碼倉儲中。
privateKeys: ['your-private-key']
},
Conflux portal 的測試環境 存入
按鈕裡面提供了 CFX 水龍頭,可申領一些測試環境的代幣用於合約部署。
然後可以通過執行 deploy 命令的時候指定 --network,就可以將合約部署到指定的網路上
$ cfxtruffle deploy --network testnet
執行外部指令碼或命令
cfxtruffle 還提供了兩個命令 exec
, run
可用於執行外部指令碼,指令碼里可以編寫一些與合約互動的邏輯,許多時候這會非常有用。
你可以在專案根目錄下新增一個指令碼 get-balance.js 內容如下
const Coin = artifacts.require("Coin");
module.exports = async function(callback) {
try {
let instance = await Coin.deployed();
let accounts = await cfx.getAccounts();
let balance = await instance.balances(accounts[0]);
console.log(`balance of ${accounts[0]} is: ${balance.toString()}`);
} catch(e) {
console.error(e);
}
callback();
}
然後就可以使用 exec 命令執行它了
$ cfxtruffle exec ./get-balance.js
run
命令則可用於執行 truffle 的 plugin:
$ npm install --save-dev truffle-plugin-hello
並在 truffle-config.js 中新增對外掛的宣告
module.exports = {
/* ... rest of truffle-config */
plugins: [
"truffle-plugin-hello"
]
}
然後就可以用 run 命令執行了
$ cfxtruffle run hello
現在在 npm 上面有一些 合約 verify,lint,coverage 等外掛可以直接使用, 當然你也可以編寫自己的外掛,具體方法參看這裡
總結
至此我們從頭到尾使用 cfxtruffle 開發了一個 Coin 智慧合約,其各個功能的使用方法及其他高階命令沒有一一詳細介紹,可以參看 truffle 的官方文件。
Conflux 作為新一代公鏈,主網即將完全上線,希望智慧合約開發小夥伴都來體驗一下它飛一般的效能。也希望越來越多的應用,能誕生於 Conflux 網路。
實現信任的真正數字化。
參考
- truffle 文件
- conflux-rust docker
- conflux rpc
- 如何執行一個 conflux 本地節點
- Solidity 文件
- js-conflux-sdk
- conflux portal
- conflux scan
相關資料庫: