用以太坊開發框架Truffle開發智能合約實踐攻略(代碼詳解)
1
TRUFFLE是什麼?
Truffle是一個世界級的開發環境,測試框架,以太坊的資源管理通道,致力於讓以太坊上的開發變得簡單,Truffle有以下:
- 內置的智能合約編譯,鏈接,部署和二進位文件的管理。
- 快速開發下的自動合約測試。
- 腳本化的,可擴展的部署與發布框架。
- 部署到不管多少的公網或私網的網路環境管理功能
- 使用EthPM&NPM提供的包管理,使用ERC190標準。
- 與合約直接通信的直接交互控制台(寫完合約就可以命令行里驗證了)。
- 可配的構建流程,支持緊密集成。
- 在Truffle環境里支持執行外部的腳本。
【說明】更多以太坊術語可參考此篇文章:
https://www.jianshu.com/p/03666198619d1.1 TRUFFLE的安裝
在Ubuntu命令上窗口輸入以下命令,完成安裝:
$ npm install -g truffle
如果安裝成功,可輸入truffle version名稱,正常情況下會有版本顯示:
truffle version
環境要求
NodeJS 5.0+
Windows,Linux(推薦Ubuntu),或Mac OS XTruffle客戶端
有許多的以太坊客戶端可以選擇。我們推薦在開發和部署時使用不同客戶端。
適用開發的客戶端- EtherumJS TestRPC
當開發基於Truffle的應用時,我們推薦使用EthereumJS TestRPC。它是一個完整的在內存中的區塊鏈僅僅存在於你開發的設備上。它在執行交易時是實時返回,而不等待默認的出塊時間,這樣你可以快速驗證你新寫的代碼,當出現錯誤時,也能即時反饋給你。它同時還是一個支持自動化測試的功能強大的客戶端。Truffle充分利用它的特性,能將測試運行時間提速近90%。
適用正式發布的客戶端
- Geth (go-ethereum)
- WebThree(cpp-ethereum)
- More
對此有許多官方和非官方的以太坊客戶端可供選擇。最好使用TestRPC客戶端充分測試後,再使用這些客戶端。這些是完整的客戶端實現,包括挖礦,網路,塊及交易的處理,Truffle可以在不需要額外配置的情況下發布到這些客戶端。
當發布到私有網路中
私人網路中使用了相同的技術,但卻有不同的配置。所以你可以將上面提及的客戶端來運行一個私有的網路,部署到這樣的網路也是使用同樣的方式。
【說明】作者使用TestRPC和 Geth (go-ethereum)這2種客戶端,他們的安裝方式參考文章:https://www.jianshu.com/p/683ea7d62a39
2
下載TRUFFLE MetaCoin樣例進行環境搭建實戰
2.1 MetaCoin初始化
我們假設前面的安裝和環境搭建已全部成功,此時應該可以直接使用命令truffle了,下面我們建立一個工作間truffle-workspace,然後在工作間執行:
mkdir MetaCoin
cd MetaCoin
truffle unbox metacoin
原來使用truffle init,但現在它存在於unbox。
執行截圖如下:
下載樣例
unbox
Truffle 的盒子Boxs裝有很多非常實用的項目樣板,可以讓你忽略一些環境配置問題,從而可以集中與開發你自己的DApp的業務唯一性。除此之外,Truffle Boxes能夠容納其他有用的組件、Solidity合約或者庫,前後端視圖等等。所有這些都是一個完整的實例Dapp程序。都可以下載下來逐一研究,尋找適合自己公司目前業務模型的組件。
Truffle的官方Boxes地址(http://truffleframework.com/boxes/)
可以看到,現在官方盒子還不多,總共7個,有三個是關於react的,兩個是truffle自己的項目,可以下載體驗,剩下兩個是我們比較關心的,一個是metacoin,非常好的入門示例,另一個是webpack,顧名思義,它是一套比起metacoin更加完整的模板的存在。既然我們是初學,下面我們就從metacoin入手學習。
2.2 目錄結構及文件解讀
進入metacoin目錄,當前目錄已經被初始化成一個新的空的以太坊工程,目錄結構如下:
- contractsConvertLib.solMetaCoin.solMigrations.solplaceholder
- migrations1_initial_migration.js
2_deploy_contracts.js
- testmetacoin.jsTestMetacoin.solplaceholder
- truffle-config.js
- truffle.js
初始化文件解釋1:Migrations.sol
pragma solidity ^0.4.2;
contract Migrations {
address public owner;
uint public last_completed_migration;
modifier restricted() {
if (msg.sender == owner) _;
}
function Migrations() public {
owner = msg.sender;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
上面我們學習了Solidity具體的類型語法,我們來分析一下這個文件:
- 它定義了一個名字為「遷移」的合約
- 有一個任意訪問的全局變數,存儲於storage的地址類型變數owner
- 有一個可任意訪問的全局變數,存儲於storage的無符號整型類型的變數last_completed_migration
- modifier下面細說,此處略過
- msg.sender下面細說,此處略過
- 構造函數,初始化將發送方賦值給owner保存
- 一個setCompleted賦值方法,賦值給last_completed_migration,其中該方法被聲明為restricted,下面細說,此處略過
- upgrade方法,調用當前合約自己的方法,得到合約的實例upgraded,然後通過該是咧調用setCompleted賦值方法。
Solidity語法補充說明1:function modifier
modifier的使用方法,就看上面的Migrations合約的例子即可,它可以自動改變函數的行為,例如你可以給他預設一個條件,他會不斷檢查,一旦符合條件即可走預設分支。它可以影響當前合約以及派生合約。
pragma solidity ^0.4.11;
contract owned {
function owned() public { owner = msg.sender; }
address owner;
// 這裡僅定義了一個modifier但是沒有使用,它將被子類使用,方法體在這裡「_;」,這意味著如果owner調用了這個函數,函數會被執行,其他人調用會拋出一個異常。
modifier onlyOwner {
require(msg.sender == owner);
_;
}
}
// 通過is關鍵字來繼承一個合約類,mortal是owned的子類,也叫派生類。
contract mortal is owned {
// 當前合約派生了owned,此方法使用了父類的onlyOwner的modifier
// public onlyOwner, 這種寫法挺讓人困惑,下面給出了我的思考,暫理解為派生類要使用基類的modifier。
function close() public onlyOwner {
selfdestruct(owner);
}
}
contract priced {
// Modifiers可以接收參數
modifier costs(uint price) {
// 這裡modifier方法體是通過條件判斷,是否滿足,滿足則執行「_;」分支。
if (msg.value >= price) {
_;
}
}
}
contract Register is priced, owned {
mapping (address => bool) registeredAddresses;
uint price;
// 構造函數給全局變數price賦值。
function Register(uint initialPrice) public { price = initialPrice; }
// payable關鍵字重申,如果不聲明的話,函數關於以太幣交易的操作都會被拒回。
function register() public payable costs(price) {
registeredAddresses[msg.sender] = true; }
// 此派生類也要使用基類的modifier。
function changePrice(uint _price) public onlyOwner {
price = _price;
}
}
contract Mutex {
bool locked;
modifier noReentrancy() {
require(!locked);
locked = true;
_;
locked = false;
}
function f() public noReentrancy returns (uint) {
require(msg.sender.call());
return 7;
}
}
又延伸出來一個盲點:require關鍵字,它是錯誤判斷,提到assert就懂了,官方文檔的解釋為:
require(bool condition):
throws if the condition is not met - to be used for errors in inputs or external components.
總結一下modifier:
- 聲明modifier時,特殊符號「_;」的意思有點像TODO,是一個「佔位符」,指出了你要寫的具體方法體內容的位置。
- function close() public onlyOwner,派生類某方法想「如虎添翼」加入基類的某個modifier功能,就可以這樣寫,這行的具體意思就是:close方法也必須是owner本人執行,否則報錯!
Solidity語法補充說明2:Restricting Access
限制訪問一種針對合約的常見模式。但其實你永遠不可能限制得了任何人或電腦讀取你的交易內容或者你的合同狀態。你可以使用加密增大困難,但你的合約就是用來讀取數據的,那麼其他人也會看到。所以,其實上面的modifier onlyOwner是一個特別好的可讀性極高的限制訪問的手段。
那麼restricted關鍵字如何使用呢?
好吧,我剛剛帶著modifier的知識重新看了上面的Migrations合約的內容發現,restricted並不是關鍵字,而是modifier的方法名,在其下的想增加該modifier功能的函數中,都使用了public restricted的方式來聲明。
說到這裡,我又明白了為什麼要使用public onlyOwner這種寫法,因為public是函數可見性修飾符,onlyOwner是自定義的限制訪問的modifier方法,他們都是關於函數使用限制方面的,所以會寫在一起,可以假想一個括弧將它倆括起來,他們佔一個位置,就是原來屬於public|private|internal|external的那個位置。
Solidity語法補充說明3:Special Variables and Functions
這一點很重要了,我們研究一下Solidity自身攜帶的特殊變數以及函數:
- block.blockhash(uint blockNumber) returns (bytes32): 返回參數區塊編號的hash值。(範圍僅限於最近256塊,還不包含當然塊)
- block.coinbase (address): 當前區塊礦工地址
- block.difficulty (uint): 當前區塊難度
- block.gaslimit (uint): 當前區塊的gaslimit
- block.number (uint): 當前區塊編號
- block.timestamp (uint): 當前區塊的timestamp,使用UNIX時間秒
- msg.data (bytes): 完整的calldata
- msg.gas (uint): 剩餘的gas
- msg.sender (address): 信息的發送方 (當前調用)
- msg.sig (bytes4): calldata的前四個位元組 (i.e. 函數標識符)
- msg.value (uint): 消息發送的wei的數量
- now (uint): 當前區塊的timestamp (block.timestamp別名)
- tx.gasprice (uint): 交易的gas單價
- tx.origin (address): 交易發送方地址(完全的鏈調用)
msg有兩個屬性,一個是msg.sender,另一個是msg.value,這兩個值可以被任何external函數調用,包含庫裡面的函數。
注意謹慎使用block.timestamp, now and block.blockhash,因為他們都是有可能被篡改的。
初始化文件解釋2:MetaCoin.sol
pragma solidity ^0.4.18;
import "./ConvertLib.sol";
// 這是一個簡單的仿幣合約的例子。它並不是標準的可兼容其他幣或token的合約,
// 如果你想創建一個標準兼容的token,請轉到 https://github.com/ConsenSys/Tokens(TODO:一會兒我們再過去轉)
contract MetaCoin {
mapping (address => uint) balances;// 定義了一個映射類型變數balances,key為address類型,值為無符整型,應該是用來存儲每個賬戶的餘額,可以存多個。
event Transfer(address indexed _from, address indexed _to, uint256 _value);// Solidity語法event,TODO:見下方詳解。
function MetaCoin() public {// 構造函數,tx.origin查查上面,找到它會返回交易發送方的地址,也就是說合約實例創建時會默認為當前交易發送方的餘額塞10000,單位應該是你的仿幣。
balances[tx.origin] = 10000;
}
function sendCoin(address receiver, uint amount) public returns(bool sufficient) {// 函數聲明部分沒有盲點,方法名,參數列表,函數可見性,返回值類型定義。
if (balances[msg.sender] < amount) return false;// 如果餘額不足,則返回發送幣失敗
balances[msg.sender] -= amount;// 否則從發送方餘額中減去發送值,注意Solidity也有 「-=」,「+=」 的運算符哦
balances[receiver] += amount;// 然後在接收方的餘額中加入發送值數量。
Transfer(msg.sender, receiver, amount);// 使用以上event關鍵字聲明的方法
return true;
}
function getBalanceInEth(address addr) public view returns(uint){// 獲取以太幣餘額
return ConvertLib.convert(getBalance(addr),2);// 調用了其他合約的方法,TODO:稍後介紹ConvertLib合約時說明。
}
function getBalance(address addr) public view returns(uint) {// 獲取當前賬戶的仿幣餘額
return balances[addr];
}
}
Solidity語法補充說明4:Events
Events allow the convenient usage of the EVM logging facilities, which in turn can be used to 「call」 JavaScript callbacks in the user interface of a dapp, which listen for these events.
Events提供了日誌支持,進而可用於在用戶界面上「調用」dapp JavaScript回調,監聽了這些事件。簡單來說,我們的DApp是基於web伺服器上的web3.js與EVM以太坊結點進行交互的,而智能合約是部署在EVM以太坊結點上的。舉一個例子:contract ExampleContract {
// some state variables ...
function foo(int256 _value) returns (int256) {
// manipulate state ...
return _value;
}
}
合約ExampleContract有個方法foo被部署在EVM的一個結點上運行了,此時用戶如果想在DApp上調用合約內部的這個foo方法,如何操作呢,有兩種辦法:
- var returnValue = exampleContract.foo.call(2);// 通過web3 的message的call來調用。
- 合約內部再聲明一個event ReturnValue(address indexed _from, int256 _value);並在foo方法內使用該event用來返回方法執行結果。
第一種辦法在方法本身比較耗時的情況下會阻塞,或者不會獲取到準確的返回值。所以採用第二種辦法:就是通過Solidity的關鍵字event。
event在這裡就是一個回調函數的概念,當函數運行結束以後(交易進塊),會通過event返回給web3,也就是DApp用戶界面相應的結果。這是以太坊一種客戶端非同步調用方法。關於這個回調,要在DApp使用web3時顯示編寫:
exampleEvent.watch(function(err, result) {
if (err) {
console.log(err)
return;
}
console.log(result.args._value)
// 檢查合約方法是否反返回結果,若有則將結果顯示在用戶界面並且調用exampleEvent.stopWatching()方法停止非同步回調監聽。
})
寫Solidity最大的不同在於,我們要隨時計算好我們的gas消耗,方法的複雜度,變數類型的存儲位置(memory,storage等等)都會決定gas的消耗量。
使用event可以獲得比storage更便宜的gas消耗。
總結一下event,就是如果你的Dapp客戶端web3.js想調用智能合約內部的函數,則使用event作為橋樑,它能方便執行非同步調用同時又節約gas消耗。
初始化文件解釋3:ConvertLib.sol
pragma solidity ^0.4.4;
library ConvertLib{
function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount)
{
return amount * conversionRate;
}
}
與MetaCoin智能合約不同的是,ConvertLib是由library聲明的一個庫,它只有一個方法,就是返回給定的兩個無符整數值相乘的結果。返回到上面的MetaCoin中該庫的使用位置去分析,即可知道,MetaCoin的仿幣的價格是以太幣的一倍,所以MetaCoin是以以太幣為標杆,通過智能合約發布的一個token,仿幣。
這似乎就可以很好地解決我在《以太坊RPC機制與API實例》文章中需要發布三倍以太幣的token的需求了,而我們完全不必更改以太坊源碼,但那篇文章通過這個需求的路線研究了以太坊的Go源碼也算功不可沒。
初始化文件解釋4:1_initial_migration.js
var Migrations = artifacts.require("./Migrations.sol");
module.exports = function(deployer) {
deployer.deploy(Migrations);
} ;
這個js文件是nodejs的寫法,看上去它的作用就是部署了上面的Migrations智能合約文件。
初始化文件解釋5:2_deploy_contracts.js
var ConvertLib = artifacts.require("./ConvertLib.sol");
var MetaCoin = artifacts.require("./MetaCoin.sol");
module.exports = function(deployer) {
deployer.deploy(ConvertLib);
deployer.link(ConvertLib, MetaCoin);
deployer.deploy(MetaCoin);
};
這個文件是meatcoin智能合約的部署文件,裡面約定了部署順序,依賴關係。這裡我們看到了MetaCoin智能合約是要依賴於庫ConvertLib的,所以要先部署ConvertLib,然後link他們,再部署MetaCoin,這部分js的寫法可以參照官方文檔DEPLOYER API,主要就是介紹了一下deploy、link以及then三個方法的詳細用法,不難這裡不再贅述。
初始化文件解釋6:truffle-config.js, truffle.js
module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration!};
module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration!
};
這兩個文件也都是nodejs,他們都是配置文件,可能作用域不同,目前它倆是完全相同的(因為啥也沒有)。我們去它推薦的網站看一看。給出了一個例子:
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 8545,
network_id: "*" // Match any network id
}
}
};
這個例子展示了該配置文件可以配置網路環境,暫先到這,以後遇上了針對該配置文件進行研究。
初始化文件解釋7:.placeholder
This is a placeholder file to ensure the parent directory in the git repository. Feel free to remove.
翻譯過來就是:placeholder文件是用來保證在git庫中父級目錄的,可以刪除。
初始化文件解釋8:metacoin.js
和下面的文件一樣,他們的功能都是用來做單元測試的,truffle在編譯期間會自動執行這些測試腳本。當前文件為js版本,模擬用戶在DApp客戶端用戶界面操作的情形。
var MetaCoin = artifacts.require("./MetaCoin.sol"); // 這與1_initial_migration.js文件的頭是一樣的,引入了一個智能合約文件。
contract(MetaCoin, function(accounts) {
it("should put 10000 MetaCoin in the first account", function() {
return MetaCoin.deployed().then(function(instance) {
return instance.getBalance.call(accounts[0]);
}).then(function(balance) {
assert.equal(balance.valueOf(), 10000, "10000 wasnt in the first account");
});
});
it("should call a function that depends on a linked library", function() {
var meta;
var metaCoinBalance;
var metaCoinEthBalance;
return MetaCoin.deployed().then(function(instance) {
meta = instance;
return meta.getBalance.call(accounts[0]);
}).then(function(outCoinBalance) {
metaCoinBalance = outCoinBalance.toNumber();
return meta.getBalanceInEth.call(accounts[0]);
}).then(function(outCoinBalanceEth) {
metaCoinEthBalance = outCoinBalanceEth.toNumber(); }).then(function() {
assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, "Library function returned unexpected function, linkage may be broken");
});
});
it("should send coin correctly", function() {
var meta;
// Get initial balances of first and second account.
var account_one = accounts[0];
var account_two = accounts[1];
var account_one_starting_balance;
var account_two_starting_balance;
var account_one_ending_balance;
var account_two_ending_balance;
var amount = 10;
return MetaCoin.deployed().then(function(instance) {
meta = instance;
return meta.getBalance.call(account_one);
}).then(function(balance) {
account_one_starting_balance = balance.toNumber();
return meta.getBalance.call(account_two);
}).then(function(balance) {
account_two_starting_balance = balance.toNumber();
return meta.sendCoin(account_two, amount, {from: account_one});
}).then(function() {
return meta.getBalance.call(account_one);
}).then(function(balance) {
account_one_ending_balance = balance.toNumber();
return meta.getBalance.call(account_two);
}).then(function(balance) {
account_two_ending_balance = balance.toNumber();
assert.equal(account_one_ending_balance,
account_one_starting_balance - amount, "Amount wasnt correctly taken from the sender");
assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasnt correctly sent to the receiver");
});
});
});
我們來分析一波這個truffle metacoin js版本的單元測試:
- 直接函數contract走起,第一個參數為智能合約名字,第二個參數為匿名內部函數
- 匿名函數傳入了當前賬戶地址,函數體是單元測試集
- 每個單元測試是由關鍵字it函數來做,第一個參數傳入單元測試的comments,第二個參數傳入一個無參匿名函數
- 進到無參匿名函數的函數體內,就是正式的單元測試內容,可以定義自己的成員屬性,通過調用truffle內部組件自動部署合約逐一測試,使用成員屬性接收返回值,最後使用關鍵字assert來判斷是否符合預期。具體業務不詳細展開,可根據自己業務內容隨意更改。
這是官方文檔,詳細說明如何使用JS來編寫智能合約的單元測試。
http://truffleframework.com/docs/getting_started/javascript-tests
初始化文件解釋9:TestMetacoin.sol
好下面來看看Solidity智能合約版本的單元測試。一般來講,這種文件的命名規則是Test加待測智能合約的名字拼串組成。
pragma solidity ^0.4.2;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/MetaCoin.sol";
contract TestMetacoin {
function testInitialBalanceUsingDeployedContract() public {
MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin());
uint expected = 10000;
Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
}
function testInitialBalanceWithNewMetaCoin() public {
MetaCoin meta = new MetaCoin();
uint expected = 10000;
Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
}
}
繼續分析:
- 首先import了truffle的幾個類庫,用來支持我們接下來的測試內容。然後import了待測智能合約。
- 建立單元測試智能合約,根據合約不同方法定義對應的test測試方法。
- 方法體內部去調用待測智能合約的方法,傳參接收返回值,然後使用關鍵字assert判斷是否符合預期。
這是官方文檔,詳細說明如何使用Solidity來編寫智能合約的單元測試。(http://truffleframework.com/docs/getting_started/solidity-tests)
2.3 編譯合約
鍵入
truffle compile
輸出情況:
輸出結果
根據編譯輸出的路徑地址./build/contracts,我們去查看一下
產生文件列表
可以看到原來所在在contracts目錄下的智能合約文件(有合約contract,有庫library)均被編譯成了json文件。
這些json文件就是truffle用來部署合約的編譯文件。
2.4 配置以太坊本地環境
truffle.js是truffle的配置文件,啟動好以太坊本地結點以後,我們需要讓truffle去識別它並使用它,這就需要在truffle.js中配置相關屬性:
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 8545,
network_id: "*" // Match any network id
}
}
};
【說明】如果不啟動TestRPC,直接執行部署合約的話,會有以下錯誤提示:
無網路
2.5 啟動本地以太坊客戶端結點
啟動適合開發的RPC客戶端
啟動之前安裝好的EthereumJS RPC客戶端。
testrpc
【說明】一定要啟動一個新的客戶端執行testrpc命令,可以觀察到默認賬戶和私鑰信息。
本地客戶端
2.6 部署合約
移植(migrate),對這裡叫移植,但下面我們仍使用「部署」這個詞,truffle中部署的命令為:
truffle migrate
輸出結果截圖如下:
部署錢包的輸出結果
查看testrpc的輸出窗口,可以看到這筆交易和花費的區塊:
2.7 測試合約
我們知道在執行編譯時會自動執行這些單元測試,如果有一個測試未通過則會中斷編譯過程。而在開發階段,我們也可以自己使用命令來測試。
truffle test
沒有報錯就說明通過了,綠條「5 passing(2s)」,有報錯就會列印在下方。
輸出截圖1
輸出截圖2
3
用Truffle框架運行一個「Hello World!」智能合約
3.1 創建工程目錄
返回父級目錄,創建一個文件夾HelloWorld,來做為你的工程根目錄。
mkdir HelloWorld
輸入結果:
創建並進入該目錄
3.2 初始化框架
在工作目錄HelloWorld目錄下,執行truffle初始化動作:
truffle init
輸出截圖:
初始化成功
採用SFTP下載文件到本地,可查看目錄結構:
│ truffle-config.js
│ truffle.js
│
├─contracts
│ Migrations.sol
│
├─migrations
│ 1_initial_migration.js
│
└─test
目錄結構簡單說明如下:
- contract/ - Truffle默認的合約文件存放地址。
- migrations/ - 存放發布腳本文件
- test/ - 用來測試應用和合約的測試文件
- truffle.js - Truffle的配置文件
3.3 新建新合約
在./contract目錄下創建一個自己的合約文件Greeter.sol。
pragma solidity ^0.4.17;
contract Greeter
{
address creator;
string greeting;
function Greeter(string _greeting) public
{
creator = msg.sender;
greeting = _greeting;
}
function greet() public constant returns (string)
{
return greeting;
}
function setGreeting(string _newgreeting) public
{
greeting = _newgreeting;
}
/**********
Standard kill() function to recover funds
**********/
function kill()public
{
if (msg.sender == creator)
suicide(creator); // kills this contract and sends remaining funds back to creator
}
}
3.4 新建發布腳本
在./migrations/目錄下新建一個文件:2_deploy_contracts.js,增加發布代碼。
var Greeter = artifacts.require("./Greeter.sol");
module.exports = function(deployer) {
deployer.deploy(Greeter,"Hello, World!");//"參數在第二個變數攜帶"
};
3.5 編譯
進入到工程根目錄./HelloWorld目錄下,進行編譯:
truffle compile
輸出截圖如下:
編譯成功截圖
3.6 啟動你的客戶端
如果之前沒有啟動RPC客戶端的話,則需要啟動之前安裝好的EthereumJS RPC客戶端。如果已啟動的則忽略此步。
$ testrpc
3.7 部署合約(migrate)
執行部署命令(truffle migrate)提示出錯。
truffle migrate
錯誤截圖輸出:
部署失敗,提示網路未配置
修改文件./HelloWorld/truffle.js文件,增加網路配置:
module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration!
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*" // 匹配任何network id
}
}
};
重新執行編譯命令,重新執行部署命令(truffle migrate),則運行正確。對應Greeter的錢包地址為「0x7d62724f397a99613b84923a1166d683de2db680」
部署成功
3.8 TRUFFLE測試環境運行合約
Truffle提供了一種更加簡單的方式,通過互動式控制台來與你的那些準備好的合約進行交互。
truffle console
一個基本的交互控制台,可以連接任何EVM客戶端。如果你已經有了自己的ganache或者geth等EVM的本地環境,那麼就可以使用truffle console來交互,所以如果你已經有一個現成的小組共享的開發用EVM,那麼使用這個沒錯。truffle develop一個交互控制台,啟動時會自動生成一個開發用區塊鏈環境(其實我認為它與ganache就是一個底層實現機制,都是默認生成10個賬戶)。如果你沒有自己的EVM環境的話,直接使用truffle develop非常方便。truffle console
輸入Greeter智能合約命令,顯示列印出一個json結構,展示了它的各種屬性內容。
查看Greeter結構
根據你的Greeter錢包地址,運行Greeter智能合約命令:
hello,world智能合約運行成功
3.8 GETH正式環境運行合約
啟動GETH環境
本節假設GETH環境已安裝好了。如果還沒有安裝的同學,可參考文章《第一課 如何在WINDOWS環境下搭建以太坊開發環境》(https://www.jianshu.com/p/683ea7d62a39)的描述步驟。
然後在IDE內部打開一個terminal,啟動GETH的EVM環境。
geth --datadir testNet3 --dev --rpc console
截圖1
截圖2
GETH 中是通過abi來註冊合約對象的。
首先我們找到./build/contracts/Greeter.json中的abi的value:
"abi": [
{
"inputs": [
{
"name": "_greeting",
"type": "string"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"constant": true,
"inputs": [],
"name": "greet",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_newgreeting",
"type": "string"
}
],
"name": "setGreeting",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "kill",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
],
通過json壓縮成一行得到:
var abi = [{"inputs": [{"name": "_greeting","type": "string"}],"payable": false,"stateMutability": "nonpayable","type": "constructor"},{"constant": true,"inputs": [],"name": "greet","outputs": [{"name": "","type": "string"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": false,"inputs": [{"name": "_newgreeting","type": "string"}],"name": "setGreeting","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": false,"inputs": [],"name": "kill","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"}];重新部署智能合約到Geth環境
啟動一個新的命令窗口,到
cd /usr/work/HelloWorld
truffle migrate
成功部署輸出截圖:
智能合約部署成功
獲得Greeter的地址為 0xb52bb3ce336f71a14345c78e5b2f8e63685e3f92
切換到GETH環境下,利用api和錢包地址(你自己Greeter智能合約的錢包地址哦)註冊合約對象。
var abi = [{"inputs": [{"name": "_greeting","type": "string"}],"payable": false,"stateMutability": "nonpayable","type": "constructor"},{"constant": true,"inputs": [],"name": "greet","outputs": [{"name": "","type": "string"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": false,"inputs": [{"name": "_newgreeting","type": "string"}],"name": "setGreeting","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": false,"inputs": [],"name": "kill","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"}];
var HelloWorld = eth.contract(abi).at(0xb52bb3ce336f71a14345c78e5b2f8e63685e3f92)
HelloWorld.greet()
輸出截圖顯示成功:
4
總結及參考
本文站在巨人的肩膀上,完成了以太坊開發框架Truffle從入門到實戰的演示。對巨人的文章表示感謝:
1,Solidity的Truffle框架實戰(手把手)(http://truffle.tryblockchain.org/Solidity-truffle-%E5%AE%9E%E6%88%98.html)
2, 【精解】開發一個智能合約(http://www.cnblogs.com/Evsward/p/contract.html)
3,官網參考:http://truffleframework.com/docs/
如需獲取源碼,請私信作者獲取。(點擊:「閱讀原文」即可查看作者原文)
本文來源:簡書
作者:筆名輝哥
以下是我們的社區介紹,歡迎各種合作、交流、學習:)
閱讀原文
推薦閱讀:
TAG:以太坊 | 科技 | 區塊鏈(Blockchain) |