翻譯:Toya

宣告:轉載須附本文連結,違者必究

原文連結:

https://

medium。com/bitcorps-blo

g/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05#。bc8qat6lf

我已經智慧合約領域工作了4年,主要在比特幣區塊鏈上。我參與的一些專案包括存在證明,bitcore(位元核心)以及Streamium。 過去這個月,我探索了在以太坊平臺上進行開發。

我決定製作一個簡短的指南服務未來想要學習以太坊開發的程式設計師。手冊分為兩個部分:如何開始以太坊智慧合約開發,智慧合約安全簡述。

如何開始學習以太坊智慧合約

0.基礎概念

這個指南假設你已經有了一些密碼學貨幣和區塊鏈的基礎技術背景。 如果你沒有,我建議快速過一遍Andreas Antonopoulos的《

完全掌握比特幣

》(Mastering Bitcoin),Consensys的《

用剛剛夠的比特幣來搞懂以太坊

》(Just Enough Bitcoin for Ethereum),或者至少看看Scott Driscoll的短片。 為了繼續讀下去你得了解公鑰和私鑰,為什麼區塊鏈需要礦工,如何達成去中心化的共識,以及交易指令碼和智慧合約的概念。

另外兩個在你開始進行以太坊開發之前需要了解的重要的,相關的概念是以太坊虛擬機器和汽油(gas)。以太坊的目的在於成為一個智慧合約平臺。它的起源可以被追溯到Vitalik Buterin對比特幣做為智慧合約平臺具有的侷限性的評論。以太坊虛擬機器(EVM)是以太坊智慧合約執行之處。與比特幣相比,它為撰寫合約提供了更具表現力和完整性的語言。事實上,它是一個圖靈完備的程式語言。一個比較好的比喻是,EVM是一個執行智慧合約的分散式的世界電腦。由於智慧合約由EVM執行, 必須存在一種限制每個合約佔用資源的機制。EVM內執行的每一步操作實際上同時在被所有節點所執行。這是為什麼需要有汽油(gas)存在。一個以太坊合約程式碼交易可以引發資料讀寫,密碼學原語,調動(傳送資訊給)其他合約等等昂貴的運算。每個此類運算都有用汽油計量的價格,每筆交易所耗費的汽油單元需要用以太幣來支付,根據隨時變化的汽油和以太幣的匯率計算。相應的價格會從提交交易請求的以太坊賬戶中扣除。同時每筆交易對可使用的汽油會設定上限引數,用以防止程式設計錯誤導致耗幹賬戶中資金。點選這裡閱讀更多關於汽油。

1.設定你的環境

好了,你已經知道了那些基礎的,讓我們趕緊把環境搞起來寫程式碼吧。為了開始開發以太坊app(或者dapp,去中心化應用的簡稱,許多人喜歡這樣叫),你需要安裝一個客戶端來接入主網。它會成為你進入這個分散式網路的視窗,提供一個觀察區塊鏈的方法,那裡所有EVM(以太坊虛擬機器)狀態被顯示出來。有很多與條款相容的客戶端,最受歡迎的是geth,用Go語言實現。但它並不是最開發者友好的客戶端。我目前找到最好的選擇是testrpc節點(是的,名字起得很糟糕)。相信我,它會節省你很多時間。安裝它,執行它:

$ sudo npm install -g ethereumjs-testrpc

$ testrpc

你應該在一個新的終端中執行‘testrpc’,並且在你開發的過程中一直讓它執行。每次你執行testrpc,它會生成10個包涵模擬測試資金的新地址供你使用。這個不是真錢,你可以安全得用這些進行任何實驗,不會有損失資金的風險。在以太坊中撰寫智慧合約最受歡迎的語言是Solidity,因此我們會使用這個語言。我們也會用Truffle開發框架,它會幫助創造智慧合約,編譯,部署以及測試。讓我們開始吧

# First, let‘s install truffle

首先,讓我們安裝truffle

$ sudo npm install -g truffle# let’s setup our project

$ mkdir solidity-experiments

$ cd solidity-experiments/

$ truffle init

Truffle 會生成一個示範專案所需要的檔案,包括MetaCoin,一個token合約的例子。你應該能夠透過執行truffle compile指令來編譯示範合約。然後,你需要透過我們在執行的testrpc節點用‘truffle migrate’指令來在模擬網路部署合約。

Compiling ConvertLib。sol。。。

Compiling MetaCoin。sol。。。

Compiling Migrations。sol。。。

Writing artifacts to 。/build/contracts$ truffle migrate

Running migration: 1_initial_migration。js

Deploying Migrations。。。

Migrations: 0x78102b69114dbb846200a6a55c2fce8b16f61a5d

Saving successful migration to network。。。

Saving artifacts。。。

Running migration: 2_deploy_contracts。js

Deploying ConvertLib。。。

ConvertLib: 0xaa708272521f972b9ceced7e4b0dae92c77a49ad

Linking ConvertLib to MetaCoin

Deploying MetaCoin。。。

MetaCoin: 0xdd14d0691ca607d9a38f303501c5b0cf6c843fa1

Saving successful migration to network。。。

Saving artifacts。。。Note to Mac OS X users: Truffle is sometimes confused by 。DS_Store files。 If you get an error mentioning one of those files, just delete it。

我們剛剛往測試節點上部署了我們的示範合約。哇!很簡單,對吧?是時候寫我們自己的合約了!

2.撰寫你的第一個以太坊只能合約

在這個指南里面,我們會寫一個存在證明只能合約。就是創造一個存有用於證明存在的檔案雜湊的電子公正機關。用‘truffle create:contract’來開始:

$ truffle create:contract ProofOfExistence1

從你的編譯器裡面開啟合約/ProofOfExistnece1。sol(我用的是帶Soilidity語法高亮顯示的vim)

// Proof of Existence contract, version 1

contract ProofOfExistence1 {

// state

bytes32 public proof; // calculate and store the proof for a document

// *transactional function*

function notarize(string document) {

proof = calculateProof(document);

}// helper function to get a document‘s sha256

// *read-only function*

function calculateProof(string document) constant returns (bytes32) {

return sha256(document);

}

}

我們將從一段簡單但是有錯誤的程式碼開始向一個更好的解決方案靠近。這是一份Solidity合約定義,有點像其他語言中的類別(class)。合約中有狀態(state)和函式(functions)。區分合約中可能出現的兩種函式非常重要。

只讀(常數)函式:這些函式不對任何狀態(state)進行改變。他們只讀取狀態,進行計算,並且返回數值。因為這些函式可以在每一個節點內本地解決,他們不回花費任何的汽油(gas)。他們被用‘contant’關鍵詞標出。

交易函式:這些函式對狀態進行改變,轉移資金。因為這些變化需要在區塊鏈中被反應出來,執行交易函式需要向網路提交交易,這會消耗汽油(gas)。

我們的合約中兩種函式各有一個,已在註釋中標註。下一段我們將會看到我們使用函式的型別會如何改變我們與智慧合約互動。這個簡單的版本每次只儲存一個證明,用資料型別bytes32或者32bytes,跟sha256雜湊的大小一樣。交易函式‘notarize’允許我們在合約的狀態變數‘proof’裡儲存一個檔案的雜湊。這個變數是個公開變數,是我們合約的使用者認證一個檔案是否被公正的唯一途徑。我們一會就會自己做一下,但是首先。。。

讓我們把ProofOfExistence1部署到網路上!這次,你需要透過編輯移動文件(migration file)(migrations/2_deploy_contracts。js)讓Truffle部署我們的新合約。用以下的來代替內容:

/*

* migrations/2_deploy_contracts。js:

*/

module。exports = function(deployer) {

deployer。deploy(ConvertLib);

deployer。autolink();

deployer。deploy(MetaCoin);

// add this line

deployer。deploy(ProofOfExistence1);

};

你也可以選擇性的刪除有關ConvertLib和MetaCoin的語句,這些我們不會再用了。為了再次執行這個移動,你需要使用重啟標籤確保它再次執行。

truffle migrate ——reset

更多的關於Truffle移動如何工作的內容可以看這裡。

3. 與你的智慧合約互動

現在我們已經將智慧合約部署好了,讓我們擺弄擺弄它!我們可以透過函式呼叫來給它發信息或者讀取它的公開狀態。我們透過Truffle操縱檯來完成:

$ truffle console

// get the deployed version of our contract

truffle(default)> var poe = ProofOfExistence1。deployed()// and print its address

truffle(default)> console。log(poe。address)

0x3d3bce79cccc331e9e095e8985def13651a86004// let’s register our first “document”

truffle(default)> poe。notarize(‘An amazing idea’)

Promise { }// let‘s now get the proof for that document

truffle(default)> poe。calculateProof(’An amazing idea‘)。then(console。log)

Promise { }

0xa3287ff8d1abde95498962c4e1dd2f50a9f75bd8810bd591a64a387b93580ee7// To check if the contract’s state was correctly changed:

truffle(default)> poe。proof()。then(console。log)

0xa3287ff8d1abde95498962c4e1dd2f50a9f75bd8810bd591a64a387b93580ee7

// The hash matches the one we previously calculated

注意所有函式呼叫都會返回一個Promise,當Promise被解決如果我們想要檢驗它我們可以透過‘。then(console。log)’來輸出。

我們要做的第一件事是獲得一個我們部署合約的表達,並把它儲存在一個叫做‘poe’的變數之中。

然後我們呼叫交易方程‘notarize’,這會涉及一個狀態改變。當我們呼叫一個交易方程,我們得到的是一個被轉化為交易id的Promise,而不是函式返回的值。記住為了改變EVM狀態我們需要消耗汽油(gas)並且向網路提交一個交易。這是為什麼我們會得到交易id做為Promise的結果,從改變狀態的那項交易那裡得到。在這裡,我們對交易id不感興趣,所以我們可以把Promise丟掉。不過當我們真正寫app時,我們會想要把它存起來用以檢查相應的交易,捕捉錯誤。

接下來,我們呼叫只讀(常數)函式‘calculateProof‘。 記得用’constant‘關鍵詞來標記你的只讀函式,否則Truffle會試著創造一個交易來執行這個函式。這個是我們告訴Truffle,我們並沒有跟區塊鏈互動而只是在讀取。透過這個只讀函式,我們會得到’An amazing idea‘檔案的sha256。

我們現在需要把這個和我們智慧合約的狀態進行對比。為了檢查狀態的改變是否正確,我們需要讀取‘Proof’這個公開狀態變數。要獲得一個公開狀態變數的值,我們得呼叫具有同樣名字的一個函式,它會返回一個Promise。我們這次,輸出的雜湊值是一致的,所以一切都如我們所料得進行了 :)

像你從上面的片段看到的,我們第一版存在證明智慧合約似乎可以工作!幹得好!但是它每次只可以註冊一個檔案。讓我們做一版更好的。

4. 合約程式碼迭代

讓我們修改合約來支援多個檔案驗證。把原檔案複製到名為contracts/ProofOfExistence2。sol的新檔案中,並且採取以下改變。主要的變化包括:我們把‘proof’變數變成了bytes32的陣列,並且命名為‘proofs’,我們把它變成私有,然後加入一個透過迴圈訪問陣列來檢查一個檔案是否被公正的函式。

// Proof of Existence contract, version 2

contract ProofOfExistence2 {

// state

bytes32[] private proofs; // store a proof of existence in the contract state

// *transactional function*

function storeProof(bytes32 proof) {

proofs。push(proof);

} // calculate and store the proof for a document

// *transactional function*

function notarize(string document) {

var proof = calculateProof(document);

storeProof(proof);

} // helper function to get a document‘s sha256

// *read-only function*

function calculateProof(string document) constant returns (bytes32) {

return sha256(document);

} // check if a document has been notarized

// *read-only function*

function checkDocument(string document) constant returns (bool) {

var proof = calculateProof(document);

return hasProof(proof);

} // returns true if proof is stored

// *read-only function*

function hasProof(bytes32 proof) constant returns (bool) {

for (var i = 0; i < proofs。length; i++) {

if (proofs[i] == proof) {

return true;

}

}

return false;

}

}

讓我們與新的函式互動一下:(不要忘了更新migrations/2_deploy_contracts。js來加入新的合約並且執行‘truffle mirgrate——reset’)

// deploy contracts

truffle(default)> migrate ——reset// Get the new version of the contract

truffle(default)> var poe = ProofOfExistence2。deployed()// let’s check for some new document, and it shouldn‘t be there。

truffle(default)> poe。checkDocument(’hello‘)。then(console。log)Promise { }

false// let’s now add that document to the proof store

truffle(default)> poe。notarize(‘hello’)Promise { }// let‘s now check again if the document has been notarized!

truffle(default)> poe。checkDocument(’hello‘)。then(console。log)Promise { }

true

// success!// we can also store other documents and they are recorded too

truffle(default)> poe。notarize(’some other document‘);

truffle(default)> poe。checkDocument(’some other document‘)。then(console。log)Promise { }

true

這一版比第一版強,但是仍然有些問題。注意每一次我們想要檢查一個檔案是否有被公正過時都需要迴圈訪問所有存在的‘proofs’。儲存proofs更好的結構會是用對映(map)。走運的是,Solidity支援對映結構,在這個語言裡稱此結構為mappings。另外一個我們會在這一版程式碼做出的改進是我們會去掉那些多餘的標識只讀(read-only)或交易(transactional)函式的那些註釋。我想現在你已經都知道這些了:)下面是最終版本,我想應該不難理解,因為是從之前的版本一點點變過來的:

// Proof of Existence contract, version 3

contract ProofOfExistence3 { mapping (bytes32 => bool) private proofs; // store a proof of existence in the contract state

function storeProof(bytes32 proof) {

proofs[proof] = true;

} // calculate and store the proof for a document

function notarize(string document) {

var proof = calculateProof(document);

storeProof(proof);

} // helper function to get a document’s sha256

function calculateProof(string document) constant returns (bytes32) {

return sha256(document);

} // check if a document has been notarized

function checkDocument(string document) constant returns (bool) {

var proof = calculateProof(document);

return hasProof(proof);

} // returns true if proof is stored

function hasProof(bytes32 proof) constant returns(bool) {

return proofs[proof];

}

}

這下看起來已經足夠好了。它跟第二版執行起來沒有差別。記得更新移動文件(migration file)同時再次執行‘truffle migrate —— reset’來測試一下它。這個教程中的所有程式碼都可以在這裡找到。

5.在真正的測試網路上部署

在你用testrpc在模擬網路上大量測試你的合約之後,你就可以在真正的網路上測試你的合約啦!這就需要你有一個真正的testnet/livenet以太坊客戶端。點選這裡看如何安裝geth的說明。

開發的過程中,你應該在testnet模式中執行你的節點,這樣你就可以在沒有損失真金白銀的風險下進行所有的測試。Testnet模式(在以太坊也叫Morden)基本上與真正的以太坊一模一樣,但是這裡的以太幣token沒有任何金錢價值。不要發懶,記得永遠要在testnet模式下開發,如果你因為程式設計錯誤而損失以太幣,你會非常後悔的。

在testnet模式下執行geth, 開啟RPC伺服器:

geth ——testnet ——rpc console 2>> geth。log

這會開啟一個你可以輸入基本口令來控制你的節點/客戶端的控制器。你的節點會開始下載testnet區塊鏈,你可以在eth。blockNumber上檢視下載進度。區塊鏈下載的同時,你仍然可以執行口令。比如,讓我們設定一個賬戶:(千萬要記住密碼!)

> personal。newAccount()

Passphrase:

Repeat passphrase:

“0xa88614166227d83c93f4c50be37150b9500d51fc”

讓我們傳送一些以太幣過去並且查詢餘額。你可以從這裡獲得免費testnet以太幣:

https://

zerogox。com/ethereum/we

i_faucet

。 只需複製粘帖你剛剛生成的那個地址,這個水龍頭就是給你傳送一個以太幣。想要查詢餘額,執行以下程式碼:

> eth。getBalance(eth。accounts[0])

0

它會告訴你沒有餘額因為你還沒有與全網路同步。在你等待的同時,去testnet block explorer去查詢一下餘額。那裡,你也可以看到testnet目前最高的塊數(寫這個的時候是#1355293),你可以將這個資訊與eth。blockNumber的資訊結合去判斷你的節點是否已經完成同步。

一旦你的節點同步好,你就可以開始透過Truffle在testnet上部署你的合約了。首先,解鎖你的主geth賬戶,這樣Truffle就可以使用它。確認裡面有一些餘額,否則你將不能夠把新的合約推向網路。

> personal。unlockAccount(eth。accounts[0], “mypassword”, 24*3600)

true

> eth。getBalance(eth。accounts[0])

1000000000000000000

準備好了吧!如果這兩個的某一個無法執行,檢查之前的步驟以確保你正確的完成了它們。現在,執行:

$ truffle migrate ——reset

注意這次會需要更長的時間來完成,因為我們是在連線到真正的網路而不是一個用testrpc模擬出來的網路。一旦完成,你就可以用之前同樣的方法跟智慧合約互動。

在testnet上部署的版本ProofOfExistence3可以在這個地址找到:0xcaf216d1975f75ab3fed520e1e3325dac3e79e05。

我想把如何在以太坊現場網路部署合約的細節留給讀者。你只應該在模擬網路和testnet大量測試你的合約之後再做這個。千萬記得,任何程式設計錯誤都可能導致在livenet上的金錢損失!

以太坊中智慧合約的安全性問題很具有挑戰性。參見 Emin Gun Sirer的 “智慧合約挺難弄對的”。

考慮到智慧合約是定義金錢如何移動的電腦程式碼的性質,我不得不在安全問題上稍做提示。我會在以後的文章裡深度的討論合約安全性問題(像這裡),但是這裡我會先簡單的提幾點。

一些你應該知道(並且避免)的問題:

重入攻擊(reentrancy):不要在合約裡使用外部呼叫。如果迫不得已,確保它是你做得最後一件事。

傳送失敗(send can fail):傳送資金時,你的程式碼應該為傳送失敗的情況做好準備。

迴圈可能引發汽油限制(Loops can trigger gas limit):當你在狀態變數上做迴圈的時候千萬當心,變數的大小會增長這可能導致汽油消耗到達極限。

呼叫棧深度限制(Call stack depth limit):不要使用遞迴,記住任何呼叫都可能因為呼叫棧到達極限而失敗。

時間戳依賴性(Timestamp dependency):不用在程式碼的關鍵部分使用時間戳,因為礦工可以操縱它們。

這些是智慧合約中可能導致資金盜竊以及毀壞的一些意外行為的例子。中心思想是:如果你在撰寫智慧合約,你就在寫真正處理金錢的程式碼。你應該加一萬個當心!寫測試,反覆檢查程式碼,並且做程式碼稽核。

避免明顯安全問題的最好方法就是對語言有紮紮實實的理解。我建議熟讀Solidity文件,如果你有時間。我們將會需要更多更好的工具來完善智慧合約安全。