深入淺出談以太坊智能合約
1
什麼是合約?
合約是代碼(它的功能)和數據(它的狀態)的集合,存在於以太坊區塊鏈的特定地址。 合約賬戶能夠在彼此之間傳遞信息,進行圖靈完備的運算。合約依靠被稱作以太坊虛擬機(EVM) 位元組代碼(以太坊特有的二進位格式)上的區塊鏈運行。
合約很典型地用諸如Solidity等高級語言寫成,然後編譯成位元組代碼上傳到區塊鏈上。
也有其他語言可以用於編寫智能合約如Serpent和LLL,在下一節會進一步闡述。去中心化應用開發資源列出了綜合的開發環境,幫助你用這些語言開發的開發者工具,提供測試和部署支持等功能。
2
以太坊高級語言
合約依靠被稱作以太坊虛擬機(EVM) 位元組代碼(以太坊特有的二進位格式)上的區塊鏈運行。然而,合約是很典型地用諸如Solidity等高級語言寫成的,它會用以太坊虛擬機編譯器編譯成位元組代碼上傳到區塊鏈。
下面是開發者可以用來為以太坊寫智能合約的高級語言。
1. Solidity
Solidity是和JavaScript相似的語言,你可以用它來開發合約並編譯成以太坊虛擬機位元組代碼。它目前是以太坊最受歡迎的語言。
2. Serpent
Serpent是和Python類似的語言,可以用於開發合約編譯成以太坊虛擬機位元組代碼。它力求簡潔, 將低級語言在效率方面的優點和編程風格的操作簡易相結合,同時合約編程增加了獨特的領域特定功能。Serpent用LLL編譯。
3. LLL
Lisp Like Language (LLL)是和Assembly類似的低級語言。它追求極簡;本質上只是直接對以太坊虛擬機的一點包裝。
4. Mutan (棄用)
Mutan是個靜態類型,由Jeffrey Wilcke 開發設計的C類語言。它已經不再受到維護。
3
寫合約
沒有Hello World程序,語言就不完整。Solidity在以太坊環境內操作,沒有明顯的「輸出」字元串的方式。我們能做的最接近的事就是用日誌記錄事件來把字元串放進區塊鏈,示例如下:
contract HelloWorld {
event Print(string out);
function() { Print("Hello, World!"); }
}
每次執行時,這個合約都會在區塊鏈創建一個日誌入口,印著「Hello,World!」參數。
另請參閱:Solidity docs里有更多寫Solidity代碼的示例和指導。
4
編譯合約
solidity合約的編譯可以通過很多機制完成。
通過命令行使用solc編譯器實現。
在geth或eth提供的javascript控制台使用web3.eth.compile.solidity (這仍然需要安裝solc 編譯器)實現。
通過在線Solidity實時編譯器實現。
通過建立solidity合約的Meteor dapp Cosmo實現。
通過Mix IDE實現。
通過以太坊錢包實現。
注意:關於solc和編譯Solidity合約代碼的更多信息可在此查看。
1. 在geth設置solidity編譯器
如果你啟動了geth節點,就可以查看哪個編譯器可用。示例如下:
> web3.eth.getCompilers();
["lll", "solidity", "serpent"]
這一指令會返回到顯示當前哪個編譯器可用的字元串。
注意:solc編譯器和cpp- ethereum一起安裝。或者,你可以自己創建。
如果你的solc可執行文件不在標準位置,可以用—solc標誌為solc可執行文件指定一個定製路線。示例如下:
$ geth --solc /usr/local/bin/solc
或者你可以通過控制台在執行期間設置這個選項:
> admin.setSolc("/usr/local/bin/solc")
solc, the solidity compiler commandline interface
Version: 0.2.2-02bb315d/.-Darwin/appleclang/JIT linked to libethereum-1.2.0-8007cef0/.-Darwin/appleclang/JIT
path: /usr/local/bin/solc
2. 編譯一個簡單合約
讓我們來編譯一個簡單的合約源,示例如下:
> source = "contract test { function multiply(uint a) returns(uint d) { return a * 7; } }"
這個合約提供了一個單一方法multiply,它和一個正整數a調用並返回到a*7。
> contract = eth.compile.solidity(source).test
{
code: 605280600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b60376004356041565b8060005260206000f35b6000600782029050604d565b91905056 ,
info: {
language: Solidity ,
languageVersion: 0 ,
compilerVersion: 0.9.13 ,
abiDefinition: [{
constant: false,
inputs: [{
name: a ,
type: uint256
} ],
name: multiply ,
outputs: [{
name: d ,
type: uint256
} ],
type: function
} ],
userDoc: {
methods: {
}
},
developerDoc: {
methods: {
}
},
source: contract test { function multiply(uint a) returns(uint d) { return a
*
7; } }
}
}
注意:編譯器通過RPC因此也能通過web3.js,對瀏覽器內任何通過RPC/IPC連接到geth的Dapp可用。
下面的例子會向你展示如何通過JSON-RPC接合geth來使用編譯器。
$ geth --datadir ~/eth/ --loglevel 6 --logtostderr=true --rpc --rpcport 8100 --rpccorsdomain * --mine console 2>> ~/eth/eth.log
$ curl -X POST --data {"jsonrpc":"2.0","method":"eth_compileSolidity","params":["contract test {
Code:編譯的以太坊虛擬機位元組代碼。
Info:從編譯器輸出的額外元數據。
Source:源代碼。
Language:合約語言 (Solidity,Serpent,LLL)。
LanguageVersion:合約語言版本。
compilerVersion:用於編譯這個合約的solidity編譯器版本。
abiDefinition:應用的二進位界面定義。
userDoc:用戶的NatSpec Doc。
developerDoc:開發者的NatSpec Doc。
編譯器輸出的直接結構化(到code和info)反映了兩種非常不同的部署路徑。編譯的以太坊虛擬機代碼和一個合約創建交易被發送到區塊,剩下的(info)在理想狀態下會存活在去中心化雲上,公開驗證的元數據則執行區塊鏈上的代碼。
如果你的源包含多個合約,輸出會包括每個合約一個入口,對應的合約信息對象可以用作為屬性名稱的合約名字檢索到。你可以通過檢測當前的GlobalRegistrar代碼來試一下:
5
創建和部署合約
開始閱讀這一節之前,確保你有解鎖的賬戶和一些資金。
現在在區塊鏈上創建一個合約,方法是用上一章節的以太坊虛擬機代碼作為數據給空地址發送交易。示例如下:
注意:用在線Solidity實時編譯器或Mix IDE程序會更容易完成。
var primaryAddress = eth.accounts[0]
var abi = [{ constant: false, inputs: [{ name: a , type: uint256 } ]
var MyContract = eth.contract(abi)
var contract = MyContract.new(arg1, arg2, ..., , function(err, contract) {
if (!err && contract.address)
console.log(contract.address);
});
6
與合約互動
與合約互動典型的做法是用諸如eth.contract()功能的抽象層,它會返回到javascript對象,和所有可用的合約功能一起,作為可調用的javascript功能。
描述合約可用功能的標準方式是ABI定義。這個對象是一個字元串,它描述了調用簽名和每個可用合約功能的返回值。示例如下:
現在ABI中具體說明的所有功能調用都在合約實例中可用。你可以用兩種方法中的一種來調用這些合約實例上的方法。
> myMultiply7.multiply.sendTransaction(3, )
"0x12345"
> myMultiply7.multiply.call(3)
21
當用sendTransaction被調用的時候,功能調用通過發送交易來執行。需要花費以太幣來發送,調用會永久記錄在區塊鏈上。用這種方式進行的調用返回值是交易散表。
當用call被調用的時候,功能在以太坊虛擬機被本地執行,功能返回值和功能一起返回。用這種方式進行的調用不會記錄在區塊鏈上,因此也不會改變合約內部狀態。這種調用方式被稱為恆定功能調用。以這種方式進行的調用不花費以太幣。
如果你只對返回值感興趣,那麼你應該用call。如果你只關心合約狀態的副作用,就應該用sendTransaction。
在上面的例子中,不會產生副作用,因此sendTransaction只會燒gas,增加宇宙的熵。
7
合約元數據
在之前的章節中,揭示了怎樣在區塊鏈上創建合約。現在來處理剩下的編譯器輸出,合約元數據或者說合約信息。
在與不是你創建的合約互動時,你可能會想要文檔或者查看源代碼。合約作者被鼓勵提供這樣的可見信息,他們可以在區塊鏈上登記或者藉助第三方服務,比如說EtherChain。管理員API為所有選擇登記的合約提供便利的方法來獲取這個捆綁。示例如下:
// get the contract info for contract address to do manual verification
var info = admin.getContractInfo(address) // lookup, fetch, decode
var source = info.source;
var abiDef = info.abiDefinition
這項工作的潛在機制是:
合約信息被可以公開訪問的URI上傳到可辨認的地方。
任何人都可以只知道合約地址就找到是什麼URI。
僅通過2個步驟的區塊鏈註冊就可以實現這些要求。第一步是在被稱作HashReg的合約中用內容散表註冊合約代碼(散表)。第二步是在UrlHint合約用內容散表註冊一個url。這些註冊合約是Frontier版本的一部分,已經參與到Homestead中。
要知道合約地址來查詢url,獲取實際合約元數據信息包,使用這一機制就足夠了。
如果你是個盡職的合約創建者,請遵循以下步驟:
將合約本身部署到區塊鏈
獲取合約信息json文件
將合約信息json文件部署到你選擇的任意url
註冊代碼散表 ->內容散表 -> url
JS API通過提供助手把這個過程變得非常容易。 調用admin.register從合約中提取信息,在指定文件中寫出json序列,運算文件的內容散表,最終將這個內容散表註冊到合約代碼散表。一旦將那個文件部署到任意url,你就能用admin.registerUrl來註冊url 和你區塊鏈上的內容散表(注意,一旦固定的內容選址模式被用作文件商店,url-hint不再必要了) 。
source = "contract test { function multiply(uint a) returns(uint d) { return a
*
7; } }"
// compile with solc
contract = eth.compile.solidity(source).test
// create contract object
var MyContract = eth.contract(contract.info.abiDefinition)
// extracts info from contract, save the json serialisation in the given file,
contenthash = admin.saveInfo(contract.info, "~/dapps/shared/contracts/test/info.json")// send off the contract to the blockchain
MyContract.new(, function(error, contract){
if(!error && contract.address) {
// calculates the content hash and registers it with the code hash in `HashReg`
// it uses address to send the transaction.
// returns the content hash that we use to register a url
admin.register(primaryAccount, contract.address, contenthash)
// here you deploy ~/dapps/shared/contracts/test/info.json to a url
admin.registerUrl(primaryAccount, hash, url)
}
});
8
測試合約和交易
在為交易和合約排除故障時,你通常會需要一些低級的測試策略。這一章節將介紹一些你可以用到的排錯工作和做法。為了測試合約和交易而不產生實際的後果,最好在私有區塊鏈上測試。這可以通過配置一個替代網路ID (選擇一個特別的數字)和/或不能用的端點來實現。推薦做法是,為了測試你用一個替代數據目錄和埠,這樣就不會意外地和實時運行的節點衝突(假定用默認運行。在虛擬機排錯模式開啟geth,推薦性能分析和最高的日誌冗餘級別):
geth --datadir ~/dapps/testing/00/ --port 30310 --rpcport 8110 --networkid 4567890 --nodiscover -
提交交易之前,你需要創建私有測試鏈(參閱測試網路相關章節),示例如下:
// create account. will prompt for password
personal.newAccount();
// name your primary account, will often use it
primary = eth.accounts[0];
// check your balance (denominated in ether)
balance = web3.fromWei(eth.getBalance(primary), "ether");
// assume an existing unlocked primary account
primary = eth.accounts[0];
// mine 10 blocks to generate ether
// starting miner
miner.start(4);
// sleep for 10 blocks (this can take quite some time).
admin.sleepBlocks(10);
// then stop mining (just not to burn heat in vain)
miner.stop();
balance = web3.fromWei(eth.getBalance(primary), "ether");
創建交易之後,你可以用下面的命令來強制運行:
miner.start(1);
admin.sleepBlocks(1);
miner.stop();
你也可以用以下命令查看即將發生的交易:
// shows transaction pool
txpool.status
// number of pending txs
eth.getBlockTransactionCount("pending");
// print all pending txs
eth.getBlock("pending", true).transactions
如果你提交合約創建交易,可以檢查想要的代碼是否實際上嵌入到當前的區塊鏈:
txhash = eth.sendTansaction()
//... mining
contractaddress = eth.getTransactionReceipt(txhash);
eth.getCode(contractaddress)
本文摘自圖書《區塊鏈開發指南》。《區塊鏈開發指南》由 申屠青春 主編,朱波、張鵬、汪曉明、季宙棟、左川民 聯合編著,中國三大區塊鏈聯盟的大伽聯袂推薦。
點擊展開全文
![](https://pic.pimg.tw/zzuyanan/1488615166-1259157397.png)
![](https://pic.pimg.tw/zzuyanan/1482887990-2595557020.jpg)
TAG:區塊鏈大本營 |