以太坊智能合約入門(編寫、編譯、建立、部署、交互、測試、交易)

什麼是以太坊智能合約?

以太坊智能合約是存放在以太坊區塊鏈具備特定地址的代碼(它的功能)和數據(它的狀態)集合。智能合約帳戶之間能夠相互傳遞消息以實現圖靈完備運算。 智能合約以以太坊特定的二進制字節碼經過以太坊虛擬機(EVM)運行於區塊鏈上。javascript

以太坊智能合約一般是以名爲 Solidity 的高級語言編寫,並被編譯爲字節碼上傳到區塊鏈上。java

Solidity

Solidity是一種相似JavaScript的語言,容許你開發智能合約並能夠被編譯成EVM字節碼,如今已是以太坊的旗艦語言而且是最流行的。git

編寫合約

沒有實現Hello World程序的語言是不完整的,在以太坊的環境中,Solidity沒有一個明確的方式能夠」輸出」一個字符串。 最接近的方式就是實用日誌事件將一個字符串放入區塊鏈中:github

contract HelloWorld {
        event Print(string out);
        function() { Print("Hello, World!"); }
}

這條合約每次執行後,會經過Print並帶有」Hello World」參數,將一條日誌放入區塊鏈中。web

編譯合約

能夠經過多種形式的機制對solidity開發的以太坊 智能合約的編譯。編程

  • 經過命令行使用 solc 編譯器。
  • 經過 geth 或 eth`(仍需安裝 `solc 編譯器) 提供的javascript控制檯使用 web3.eth.compile.solidity 。
  • 經過 實時在線編譯器.
  • 經過 Ethereum Wallet.

在geth中設置solidity編譯器

若是你啓動了 geth 節點,你能夠經過以下命令來檢查哪些編譯器可使用。json

> web3.eth.getCompilers();
["lll", "solidity", "serpent"]

這個命令返回當前可用的編譯器的字符串數組。數組

Note網絡

solc 編譯器同 cpp-ethereum 一塊兒被安裝,做爲替代方案,你能夠本身編譯 。app

若是你的 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

編譯一個簡單的合約

咱們來編譯一個簡單的合約代碼:

> source = "contract test {
 function multiply(uint a)
 returns(uint d)
 {
 return a * 7;
 } 
}"

這個合約提供了一個名爲 multiply 的函數,輸入一個正整數 a 返回結果 a * 7 。

你已經準備好了編譯solidity代碼的環境,使用 geth 的JS命令臺 eth.compile.solidity():

> 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; } }'
  }
}

Note

編譯器支持RPC <[https://github.com/ethereum/wiki/wiki/JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC)>__ ,所以你可使用web3.js 並經過RPC/IPC鏈接到 geth 。

下面的例子顯示瞭如何經過使用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 { function multiply(uint a) returns(uint d) { return a * 7; } }"],"id":1}' http://127.0.0.1:8100

編譯器爲源代碼中的每一個單獨的合約生成一個合約對象,命令 eth.compile.solidity 會返回合約名和合約對象的映射。這個例子中咱們的合約名爲 test ,因此命令 eth.compile.solidity(source).test 會返回名爲test的合約對象,幷包含以下相關域:
code:編譯生成的以太坊虛擬機字節碼
info:編譯器輸出的額外元數據
source:源代碼
language:合約使用的編程語言(Solidity, Serpent, LLL)
languageVersion:合約語言的版本號
compilerVersion:編譯合約代碼所使用編譯器的版本號
abiDefinition:應用程序二進制接口定義 
userDoc:提供給用戶的 [NatSpec Doc]
developerDoc:提供給開發者的 [NatSpec Doc]

編譯器最直觀的輸出結構(code 和 info)反應出兩個徹底不一樣的 部署路徑 ,編譯出的EVMcode會給發給區塊鏈上特定交易,剩下的(info)會存放在去中心化的區塊鏈雲端做爲完善代碼的元數據。

若是你的源代碼包含多個合約,那麼輸出會包含每個合約的入口信息,合約的展開信息能夠經過名字來獲取,你能夠經過查看當前的GlobalRegistrar合約來嘗試一下效果:

contracts = eth.compile.solidity(globalRegistrarSrc)

建立並部署一個合約

在開始這個章節前,請確保你有一個解鎖的帳戶而且裏面有一些資金。

你如今能夠經過前面章節的EVM代碼來向一個空地址 發起一筆交易 。

Note

這個能夠經過更容易的方式完成,也就是經過 實時在線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, ..., {from: primaryAddress, data: evmByteCodeFromPreviousSection})

全部的二進制數據都會被序列化爲十六進制格式,十六進制的字符串老是以 0x 做爲前綴。

Note

請注意 arg1, arg2, ... 是合約的構造參數,能夠接受任何輸入,若是合約不須要任何構造參數那麼這些參數能夠被忽略。

值得指出的是執行這些步驟你須要支付一些費用,一旦的交易被打包進區塊,你帳戶的餘額會根據以太坊虛擬機的瓦斯費用規則進行扣除,通過一些時間,你的交易會出如今一個狀態被確認是一致的區塊中,你的合約如今已經存在於區塊鏈中。

異步執行這些步驟的方法以下:

MyContract.new([arg1, arg2, ...,]{from: primaryAccount, data: evmCode}, function(err, contract) {
  if (!err && contract.address)
    console.log(contract.address);
});

合約的交互

一般使用抽象層 eth.contract() 來完成與合約的交互,該函數返回一個JavaScript對象,該對象包含了全部能夠被JavaScript調用的合約函數。

描述合約可用函數的標準方法是 ABI定義,這個對象是一個數組,該數組包含了每個可用合約函數的調用簽名和返回值。

var Multiply7 = eth.contract(contract.info.abiDefinition);
var myMultiply7 = Multiply7.at(address);

如今全部ABI中定義的函數均可以在合約實例中使用了,你能夠經過以下兩種方法之一來進行調用:

> myMultiply7.multiply.sendTransaction(3, {from: address})
"0x12345"
> myMultiply7.multiply.call(3)
21

當使用 sendTransaction 時,經過發送一個交易來調用函數。這種方式會消耗以太幣,同時調用會永久被紀錄在區塊鏈中,這種方式的返回值就是交易的哈希值。

當使用 call 時,函數會在本地的虛擬機(EVM)上執行,調用的返回值就是函數的返回值。這種方式的調用不會被紀錄在區塊鏈中,所以也不會改變合約的內部狀態,這種方式被稱爲常量函數調用。這種調用方式不會消耗以太幣。

只關心返回值的狀況下你應該使用 call ,若是你關心合約的狀態變化那麼就使用 sendTransaction 。

在上面的例子中,不涉及改變合約狀態,所以 sendTransaction 調用只會白白燃燒燃料(gas)增長宇宙的熵。

合約元數據

在上個章節咱們解釋瞭如何在區塊鏈上建立合約,接下來咱們處理編譯器輸出的內容,合約元數據或者合約信息。

當與一個你尚未建立的合約進行交互時,你可能想要說明文檔或者查看其源代碼。合約做者被鼓勵經過區塊鏈或者第三方機構的服務來註冊此類信息,例如: EtherChain 。API admin 爲註冊了這類信息的合約提供了便利的方法來查看。

// 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

這些合約信息經過兩步區塊鏈註冊被打包: 第一步:稱爲 HashReg 的合約經過內容哈希來註冊合約代碼。 第二步:稱爲 UrlHint 的合約經過內容哈希來註冊url。 這些 註冊合約 被做爲前沿(Frontier)版本的一部分,同時被帶入到家園(Homestead)版本中。

使用這個結構,只須要知道合約的地址,而後獲取到url,進而獲取合約相關的全部元數據。

若是你是一個稱職的合約建立者,你須要遵循以下步驟:

  1. 將合約自己部署到區塊鏈上
  2. 獲取合約信息的json文件
  3. 部署合約信息的json文件到你選擇的url上
  4. 註冊代碼哈希 -> 內容哈希 -> url

JS API提供幫助讓這些步驟變的很是簡單,調用 admin.register 來獲得合約摘要,將摘要序列化存儲到指定的json文件中,計算文件的內容哈希,並最終將這些內容哈希註冊爲代碼哈希。一單你將這些文件部署到任何url,你能夠經過使用 admin.registerUrl 在區塊鏈上註冊你的內容哈希url(若是使用固定內容尋址模型做爲文檔存儲那麼rul-hint就不是必需的了)。

source = "contract test { function multiply(uint a) returns(uint d) { return a * 7; } }"
// 使用solc來編譯
contract = eth.compile.solidity(source).test
// 建立合約對象
var MyContract = eth.contract(contract.info.abiDefinition)
// 合約的摘要信息,序列化到指定的json文件中
contenthash = admin.saveInfo(contract.info, "~/dapps/shared/contracts/test/info.json")
// 合約發送到區塊鏈上
MyContract.new({from: primaryAccount, data: contract.code}, function(error, contract){
  if(!error && contract.address) {
    // 計算內容哈希而且將其經過 `HashReg` 註冊爲代碼哈希
    // 使用地址來發送交易
    // 返回咱們用來註冊url的內容哈希
    admin.register(primaryAccount, contract.address, contenthash)
    // 將 ~/dapps/shared/contracts/test/info.json 部署到一個url上
    admin.registerUrl(primaryAccount, hash, url)
  }
});

測試合約和交易

一般要對合約和交易進行測試和調試,這個章節咱們來介紹幾種調試工具和實踐方法。爲了測試合約和交易,而且避免產生真實的影響,你最好在一條私有區塊鏈上進行操做,這能夠經過配置網絡ID(選擇一個獨一無二的整數)來實現,而且不須要其餘對等節點。推薦的作法是爲測試設置其餘的數據目錄和端口,以免可能的來自其餘節點的影響(假設使用默認的參數運行)。經過調試模式來運行 geth ,並設置最高級別的日誌:

geth --datadir ~/dapps/testing/00/ --port 30310 --rpcport 8110 --networkid 4567890 --nodiscover --maxpeers 0 --vmdebug --verbosity 6 --pprof --pprofport 6110 console 2>> ~/dapp/testint/00/00.log

提交任何交易前,你須要設置好你的測試鏈,詳細內容請查看: <cite style="box-sizing: border-box;">test-networks</cite>

// 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({from:primary, data: code})
//... mining
contractaddress = eth.getTransactionReceipt(txhash);
eth.getCode(contractaddress)

本文章內容來源於以太坊社區,螃蟹翻譯。順便分享一個適合新手的以太坊教程,這個教程把上面的內容講的更清楚更透徹。

  • web3j教程,java和Android App開發以太坊區塊鏈的教程,web3j開發詳解。
相關文章
相關標籤/搜索