上一章的結尾說這一次要講編寫一個智能合約部署到測試網絡集群中,並進行交易,但我自己越看越覺得內容挺多的.先講下truffle的項目創建,編譯和部署的問題,然后再做上面說的事情吧.
truffle是一套以太坊的開發測試框架, 使用solidity開發語言,類似於javascript.
truffle的安裝在第一篇文章中已經講過.下面直接開始進入truffle的使用.
Truffle創建項目
使用truffle可以很簡單的創建項目:
mkdir truffle-project
truffle init
這個命令只會創建一個簡單的項目,用於編寫,編譯,部署基於Solidity的只能合約,
如果需要創建基於Web應用的以太坊智能合約,則需要使用:
mkdir truffle-project
truffle init webpack
命令完成以后, 文件夾的目錄結構應該是這樣:
▾ truffle-project-web-orignal/
▾ app/
▸ javascripts/
▸ stylesheets/
index.html
▾ contracts/
ConvertLib.sol
MetaCoin.sol
Migrations.sol
▾ migrations/
1_initial_migration.js
2_deploy_contracts.js
▸ node_modules/
▾ test/
metacoin.js
TestMetacoin.sol
package.json
README.md
truffle.js
webpack.config.js
目錄結構
所有的智能合約都在./contracts目錄中,默認情況下,目錄中都有一個solidity編寫的示例智能合約代碼文件和一個示例solidity庫文件.它們的擴展名都是".sol".雖然solidity庫和智能合約不同,為了更好的說明,我們還是把它們統稱為"合約".
編譯命令:
編譯智能合約只需要運行下面的簡單命令:
truffle默認只會編譯最后一次編譯成功之后被修改過的合約文件, 這是為了減少比必要的編譯.設置"--all"選項,可以強制編譯所有文件.
編譯結果(ARTIFACTS)
編譯輸出的位置為相對於項目目錄的"./build/contracts"目錄.如果目錄不存在,將會自動被創建.這些編譯后的文件完全組成了truffle的內部工作.它們為成功部署你的應用扮演了相當重要的角色.你不應該手動修改這些文件,因為即使修改了, 隨着合約的編譯和部署,又會被覆蓋掉.
依賴項
使用Solidity的"import"命令來申明合約的依賴項. truffle將會自動按照正確的順序來編譯,並保證所有的依賴項都會發送給編譯器,依賴可以通過兩種方法來指定.
通過依賴項的文件名:
為了從一個獨立的文件中導入合約,只需要編寫下面的命令, 其中"AnotherContact.sol"文件的路徑是相對於當前正在編寫的合約的.這將使得"AnotherContact.sol"文件中的所有合約(類)對於當前的源代碼都可用.
import "./AnotherContact.sol";
通過導出的包引入合約(類)
truffle支持通過NPM和EthPM安裝的依賴庫. 要從依賴庫中導入合約, 使用下面的語法, "sompackage"表示通過NPM或者EthPM安裝的包, "/SomeContract.sol"表示包中提供的solidity源文件的路徑(包含文件名).
import "somepackage/SomeContract.sol"
注意:truffle將會先搜索EthPM,然后搜索NPM,所以在低概率情況下,出現了名字的沖突,使用的是EthPM中的庫.
部署/遷移
這條命令將會執行在項目migrations目錄中的所有"migrations".最簡單的情況是所有的"migrations"只是一個被管理的部署腳本集合.如果執行過"migrations", "truffle migrate"只會啟動那些在先前沒有執行過的,新創建的"migrations".加入沒有新的"migrations"存在,"truffle migrate"不會執行任何任務.可以使用"--reset"選項強制所有"migrations"重新執行.當用於本地測試時,請確保TestRPC已經安裝並在執行"migrate"之前運行起來.
遷移文件
一個簡單的migration文件看起來是這樣:
var MyContract = artifacts.require("MyContract");
module.exports = function(deployer) {
// deployment steps
deployer.deploy(MyContract);
};
請注意文件名有一個數字前綴和一個用於描述的后綴. 編碼的前綴是必須的,它需要用來記錄"migration"是否執行成功.后綴純粹是為了便於閱讀和理解.
ARTIFACTS.REQUIRE()
在migration文件的開始, 我們通過"artifacts.require()"方法告訴truffle我們將要與那個合約交互.這個方法類似於Node中的"require",但在我們這里,它特殊的返回一個合約抽象,我們可以在后面的部署腳本中使用它這個合約. 這個指定的變量名不是必須與合約源文件的名字相同,但它應該與在源代碼中定義的合約類名稱相同.考慮下面的示例, 在同一個源文件中定義了兩個合約類.
文件名: ./contracts/Contracts.sol
contract ContractOne {
// ...
}
contract ContractTwo {
// ...
}
如果需要使用"ContractTwo", "artifacts.require()"語句應該是這樣:
var ContractTwo = artifacts.require("ContractTwo");
MODULE.EXPORTS(模塊導出)
所有"migrations"必須通過"module.exports"語法導出一個函數.每個被"migration"導出的函數都必須有一個"deployer"對象作為函數的第一個參數.這個對象輔助部署時, 為部署智能合約提供了簡潔的語法,並擔當了一些普通的部署職責,如:保存部署的生成文件為之后的需要使用."deployer"對象是部署階段的主要接口.其API在本文后面有講解.
"migration"函數還能接受其它的參數.請參考之后的示例.
INITIAL MIGRATION(migration初始化)
truffle為了使用遷移的特性, 需要一個遷移合約.這個合約必須實現指定的接口, 但你可以根據意願自由的編輯此合約.對於大多數項目來說,這個合約會在第一次遷移的時候就初始部署,並且之后不會再更新.當在使用"truffle init"創建新項目的時候,你默認會得到這個合約.
文件名:contracts/Migrations.sol
pragma solidity ^0.4.8;
contract Migrations {
address public owner;
// A function with the signature `last_completed_migration()`, returning a uint, is required.
uint public last_completed_migration;
modifier restricted() {
if (msg.sender == owner) _;
}
function Migrations() {
owner = msg.sender;
}
// A function with the signature `setCompleted(uint)` is required.
function setCompleted(uint completed) restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
為了能夠充分發揮Migrations的特性, 你必須在第一個migration中就部署這份合約.為達此目的,創建下面的"migration":
文件名:migration/1_initial_migration.js
var Migrations = artifacts.require("Migrations");
module.exports = function(deployer) {
// Deploy the Migrations contract as our only task
deployer.deploy(Migrations);
};
從現在開始,你就可以使用增量的數字作為前綴來創建新的migration,用於部署其它的合約, 進行后面的部署步驟了.
DEPLOYER(部署)
"migration"文件將會使用"deployer"來進行部署階段的任務.因此,你可以按照同步順序編寫部署任務的代碼,它們會被按照編寫時出現的先后順序正確執行:
"migration"文件將會使用"deployer"來進行部署階段的任務.因從,你可以按同步順序編寫部署代碼,它們會被按照正確的順序執行:
// Stage deploying A before B
deployer.deploy(A);
deployer.deploy(B);
或者, deployer上的每個函數可以被用作授權許可, 以按照隊列方式進行部署任務, 每個任務都依賴前一個任務的執行結果:
// Deploy A, then deploy B, passing in A's newly deployed address
deployer.deploy(A).then(function() {
return deployer.deploy(B, A.address);
});
你可以把合約的部署編寫成一個許可鏈, 如果你覺得這樣的寫法會更加清晰的話.
(NETWORK CONSIDERATIONS)網絡方面的考慮
可以根據部署的目標網絡運行部署方案. 這是一個高級的特性, 所以請先看看"NETWORK(http://truffleframework.com/docs/advanced/networks)"章節,再回來繼續.
為了根據條件執行部署步驟, 要求編寫的"migrations"接受第二個參數"network".例如:
module.exports = function(deployer, network) {
if (network != "live") {
// Do something specific to the network named "live".
} else {
// Perform a different step otherwise.
}
}
AVAILABLE ACCOUNTS(有效的賬戶)
在進行部署時, Migrations(遷移)會傳遞以太坊客戶端和web3提供者 提供的賬戶列表給你使用. 這個列表與"web3.eth.getAccounts()"返回的賬戶列表完全一樣.
DEPLOYER API(部署API)
為了簡化"migrations", "deployer"包含許多可用的函數.
DEPLOYER.DEPLOY(CONTRACT, ARGS..., OPTIONS)
這個API,部署一個特定的合約,這個合約由contract對象指定, 具有可選構造參數. 這對單例模式的合約非常有用. 這會在部署后,設置合約的地址(i.e., Contract.address等於新部署的地址), 並會覆蓋之前存儲的任何地址.
為了快速進行部署多個合約,你可以傳合約的數據,或者陣列數組.另外,最后一個參數是一個可選對象"overwrite", 它會觀察單個鍵.如果"overwrite"被設置為 false, 當一個合約已經被部署的情況下, 當前合約就不會被部署. 這對合約地址由外部依賴提供, 這種特定場景是有用的.
需要注意在第一次調用"deploy"之前,你需要部署連接你即將部署的合約的所有依賴庫.更多細節, 參考"link"函數.
示例:
// Deploy a single contract without constructor arguments
// 部署單個合約,不帶任何構造參數
deployer.deploy(A);
// Deploy a single contract with constructor arguments
// 部署單個合約帶有構造參數
deployer.deploy(A, arg1, arg2, ...);
// Deploy multiple contracts, some with arguments and some without.
// This is quicker than writing three `deployer.deploy()` statements as the deployer
// can perform the deployment as a single batched request.
// 部署多個合約,一些有參數,一些沒有參數
// 這比一條"deployer.deploy()"語句部署一個合約快很多.
// 因為deployer可以把所有的合約部署都一次性打包提交.
deployer.deploy([
[A, arg1, arg2, ...],
B,
[C, arg1]
]);
// External dependency example:
//
// For this example, our dependency provides an address when we're deploying to the
// live network, but not for any other networks like testing and development.
// When we're deploying to the live network we want it to use that address, but in
// testing and development we need to deploy a version of our own. Instead of writing
// a bunch of conditionals, we can simply use the `overwrite` key.
// 在本示例中, 依賴項提供了一個部署到真實網絡中的地址,但沒有為相應的測試網絡和開發網絡提供.
// 當我們部署到真實網絡中時,使用這個地址, 但在測試和開發網絡的情況下,我們需要部署自己的版本.
// 我們可以用'overwrite'關鍵字, 代替編寫一堆判斷條件來執行部署.
deployer.deploy(SomeDependency, {overwrite: false});
DEPLOYER.LINK(LIBRARY, DESTINATIONS)
將一個已經部署好的庫鏈接到一個或多個合約.'destinations'可以是單個合約或者由多個合約構成的數組.如果destinations中的任何合約並沒有依賴被鏈接的庫, deployer會忽略這個合約.
例如:
// Deploy library LibA, then link LibA to contract B, then deploy B.
// 部署LibA庫, 然后將LibA鏈接到合約B, 然后再部署B
deployer.deploy(LibA);
deployer.link(LibA, B);
deployer.deploy(B);
// Link LibA to many contracts
// 將LibA鏈接到許多合約
deployer.link(LibA, [B, C, D]);
DEPLOYER.THEN(FUNCTION() {...})
這一段不知道怎么翻譯才好, 大家看英語原版吧. 再不明白看示例.
Just like a promise, run an arbitrary deployment step. Use this to call specific contract functions during your migration to add, edit and reorganize contract data.
Example:
var a, b;
deployer.then(function() {
// Create a new version of A
return A.new();
}).then(function(instance) {
a = instance;
// Get the deployed instance of B
return B.deployed():
}).then(function(instance) {
b = instance;
// Set the new instance of A's address on B via B's setA() function.
return b.setA(a.address);
});
到這里我們就應該自己能夠編寫合約的部署代碼了.下一次講什么,等我捋一捋....有點亂.