當前位置:
首頁 > 最新 > 深入淺出談以太坊智能合約

深入淺出談以太坊智能合約

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)

本文摘自圖書《區塊鏈開發指南》。《區塊鏈開發指南》由 申屠青春 主編,朱波、張鵬、汪曉明、季宙棟、左川民 聯合編著,中國三大區塊鏈聯盟的大伽聯袂推薦。


點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 區塊鏈大本營 的精彩文章:

TAG:區塊鏈大本營 |