“一個智能合約是一套以數字形式定義的承諾(promises) ,包括合約參與方可以在上面執行這些承諾的協議。”協議是技術實現(technical implementation),在這個基礎上,合約承諾被實現,或者合約承諾實現被記錄下來。選擇哪個協議取決於許多因素,最重要的因素是在合約履行期間,被交易資產的本質。 再次以銷售合約為例。假設,參與方同意貨款以比特幣支付。選擇的協議很明顯將會是比特幣協議,在此協議上,智能合約被實施。因此,合約必須要用到的“數字形式”就是比特幣指令碼語言。比特幣指令碼語言是一種非圖靈完備的、命令式的、基於棧的程式設計語言,類似於Forth。下面將以一個簡單的合約指令碼以及與之對應的合約功能測試指令碼來進行相應的解釋。
下面是一個簡單的合約指令碼:
import "ConvertLib.sol";// This is just a simple example of a coin-like contract.// It is not standards compatible and cannot be expected to talk to other// coin/token contracts. If you want to create a standards-compliant// token, see: https://github.com/ConsenSys/Tokens. Cheers!contract MetaCoin {mapping (address => uint) balances;function MetaCoin() {balances[tx.origin] = 10000;}function sendCoin(address receiver, uint amount) returns(bool sufficient) {if (balances[msg.sender] < amount) return false;balances[msg.sender] -= amount;balances[receiver] += amount;return true;}function getBalanceInEth(address addr) returns(uint){return ConvertLib.convert(getBalance(addr),2);} function getBalance(address addr) returns(uint) { return balances[addr]; }}
指令碼合約規定了賬戶初始值的設定以及進行轉賬時的一些規定,基本上所有的合約指令碼都遵循這樣的一種程式碼群組織形式,下面進行一些關鍵字的簡單說明:
Address:地址類型,這個地址會在合約的建構函式functionConference()中被賦值。很多時候也稱呼這種地址為'owner'(所有人)。
uint.:無符號整型,區塊鏈上的儲存空間很緊張,保持資料儘可能的小。
Public:這個關鍵字表明變數可以被合約之外的對象使用。private修飾符則表示變數只能被本合約(或者衍生合約)內的對象使用。
Mapping或數組:在Solidity加入數群組類型之前,大家都使用類似mapping (address => uint)的Mapping類型。這個聲明也可以寫作address registrantsPaid[],不過Mapping的儲存佔用更小(smaller footprint)。這個Mapping變數會用來儲存參與者(用他們的錢包地址表示)的付款數量以便在退款時使用。
在寫完合約指令碼後,我們需要將其部署在我們的區塊鏈網路上面去(truffle開源架構已經做得比較好了,感興趣的可以看看相關資料),部署完成後,我們可以寫一些簡單的test case來驗證我們的合約指令碼是否可以被區塊鏈中的對等節點正確的調用,比如:
contract('MetaCoin', function(accounts) { it("should put 10000 MetaCoin in the first account", function(done) { //it should be executed every time to any testcase var meta = MetaCoin.deployed(); /*代碼中的那些then和return就是Promise。它們的作用寫成一個深深的嵌套調用鏈的話會是這樣conference.numRegistrants.call().then( function(num) { assert.equal(num, 0, "Registrants should be zero!"); conference.organizer.call().then( function(organizer) { assert.equal(organizer, accounts[0], "Owner doesn't match!"); }).then( function(...)) }).then( function(...)) // Because this would get hairy...*/ meta.getBalance.call(accounts[0]).then(function(balance) { assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account"); //stops tests at this point }).then(done).catch(done); }); it("should call a function that depends on a linked library ", function(done){ var meta = MetaCoin.deployed(); var metaCoinBalance; var metaCoinEthBalance; 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 unexpeced function, linkage may be broken"); }).then(done).catch(done); }); it("should send coin correctly", function(done) { var meta = MetaCoin.deployed(); // 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; 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 wasn't correctly taken from the sender"); assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver"); }).then(done).catch(done); }); });
其中關鍵字段已經給出了一些簡單的解釋,重點是其中的then語句的使用,對於then語句,其實就是我們合約中預先規定的合約辦法,即一個事件來了後該做怎麼樣的處理。