以太坊智能合約入門(編寫、編譯、建立、部署、互動、測試、交易)
來源:互聯網
上載者:User
## 什麼是以太坊智能合約?以太坊智能合約是存放在以太坊區塊鏈具有特定地址的代碼(它的功能)和資料(它的狀態)集合。智能合約賬戶之間可以相互傳遞訊息以實現圖靈完備運算。 智能合約以以太坊特定的二進位位元組碼通過以太坊虛擬機器(EVM)運行於區塊鏈上。[以太坊智能合約](http://xc.hubwiz.com/course/5a952991adb3847553d205d1)通常是以名為 [Solidity](https://solidity.readthedocs.org/en/latest/) 的進階語言編寫,並被編譯為位元組碼上傳到區塊鏈上。### SoliditySolidity是一種類似JavaScript的語言,允許你開發智能合約並可以被編譯成EVM位元組碼,現在已經是以太坊的旗艦語言並且是最流行的。## 編寫合約沒有實現Hello World程式的語言是不完整的,在以太坊的環境中,Solidity沒有一個明確的方式可以”輸出”一個字串。 最接近的方式就是實用*日誌事件*將一個字串放入區塊鏈中:```contract HelloWorld { event Print(string out); function() { Print("Hello, World!"); }}```這條合約每次執行後,會通過Print並帶有”Hello World”參數,將一條日誌放入區塊鏈中。## 編譯合約可以通過多種形式的機制對solidity開發的[以太坊 智能合約](http://xc.hubwiz.com/course/5a952991adb3847553d205d1)的編譯。* 通過命令列使用 `solc` 編譯器。* 通過 `geth` 或 `eth``(仍需安裝 ``solc` 編譯器) 提供的javascript控制台使用 `web3.eth.compile.solidity` 。* 通過 [即時線上編譯器](https://ethereum.github.io/browser-solidity/).* 通過 [Ethereum Wallet](https://github.com/ethereum/mist/releases).### 在geth中設定solidity編譯器如果你啟動了 `geth` 節點,你可以通過如下命令來檢查哪些編譯器可以使用。```> web3.eth.getCompilers();["lll", "solidity", "serpent"]```這個命令返回當前可用的編譯器的字串數組。**Note**`solc` 編譯器同 `cpp-ethereum` 一起被安裝,作為替代方案,你可以[自己編譯](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum) 。如果你的 `solc` 執行檔不在指定的標準路徑下,你可以通過 `--solc` 參數指定 `solc` 的執行路徑。```$ geth --solc /usr/local/bin/solc```同樣的,你可以通過命令列在運行時執行這個操作:```> admin.setSolc("/usr/local/bin/solc")solc, the solidity compiler commandline interfaceVersion: 0.2.2-02bb315d/.-Darwin/appleclang/JIT linked to libethereum-1.2.0-8007cef0/.-Darwin/appleclang/JITpath: /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()](https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethcompilesolidity):```> 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](https://github.com/ethereum/wiki/wiki/JavaScriptAPI#web3ethcompilesolidity) 並通過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代碼來向一個空地址 [發起一筆交易](https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethsendtransaction) 。**Note**這個可以通過更容易的方式完成,也就是通過 [即時線上Solidity編譯器](https://ethereum.github.io/browser-solidity/) 或者 [Mix IDE](https://github.com/ethereum/wiki/wiki/Mix:-The-DApp-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()](https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethcontract) 來完成與合約的互動,該函數返回一個JavaScript對象,該對象包含了所有可以被JavaScript調用的合約函數。描述合約可用函數的標準方法是 [ABI定義](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-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](https://www.etherchain.org/contracts) 。API `admin` 為註冊了這類資訊的合約提供了便利的方法來查看。```// get the contract info for contract address to do manual verificationvar info = admin.getContractInfo(address) // lookup, fetch, decodevar source = info.source;var abiDef = info.abiDefinition```這項工作生效的基本機制是:* 合約資訊被上傳到一個公用可訪問的位置地址 *URI* 上* 知道合約地址任何人都可以找到相關的 *URI*這些合約資訊通過兩步區塊鏈註冊被打包: * 第一步:稱為 `HashReg` 的合約通過內容雜湊來註冊合約代碼。 * 第二步:稱為 `UrlHint` 的合約通過內容雜湊來註冊url。 這些 [註冊合約](https://github.com/ethereum/go-ethereum/blob/develop/common/registrar/contracts.go) 被作為前沿(Frontier)版本的一部分,同時被帶入到家園(Homestead)版本中。使用這個結構,只需要知道合約的地址,然後擷取到url,進而擷取合約相關的所有中繼資料。如果你是一個稱職的合約建立者,你需要遵循如下步驟:1. 將合約本身部署到區塊鏈上2. 擷取合約資訊的json檔案3. 部署合約資訊的json檔案到你選擇的url上4. 註冊代碼雜湊 -> 內容雜湊 -> urlJS 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 passwordpersonal.newAccount();// name your primary account, will often use itprimary = eth.accounts[0];// check your balance (denominated in ether)balance = web3.fromWei(eth.getBalance(primary), "ether");// assume an existing unlocked primary accountprimary = eth.accounts[0];// mine 10 blocks to generate ether// starting minerminer.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 pooltxpool.status// number of pending txseth.getBlockTransactionCount("pending");// print all pending txseth.getBlock("pending", true).transactions```如果提交了交易建立合約,你可以檢查所需代碼是否已經插入到當前的區塊中:```txhash = eth.sendTansaction({from:primary, data: code})//... miningcontractaddress = eth.getTransactionReceipt(txhash);eth.getCode(contractaddress)```本文章內容來源於以太坊社區,螃蟹翻譯。順便分享一個適合新手的[以太坊教程](http://xc.hubwiz.com/course/5a952991adb3847553d205d1),這個教程把上面的內容講的更清楚更透徹。497 次點擊