1. 程式人生 > >一起學:以太坊智慧合約開發

一起學:以太坊智慧合約開發

課程介紹

無論在科技圈還是金融圈,“區塊鏈”儼然成了最熱的詞彙。2016年,區塊鏈寫入了國家的十三五規劃中;2017年,央行基於區塊鏈技術的數字票據交易平臺測試成功;同年,工信部發布了首個區塊鏈參考架構標準。
經過2017年如火如荼的投資盛宴,區塊鏈開始逐漸走向應用落地,業內都在積極探索使用場景。如金融領域的資產貿易、支付系統、金融服務,其他領域的電子商務、社交通訊、遊戲娛樂等等。同時,BAT、迅雷、小米、網易等網際網路龍頭企業紛紛加入研發隊伍中。
正如移動網際網路興起之時一樣,區塊鏈 DApp 應用市場初期肯定會有一波紅利期。對此有興趣的開發者,不可錯過。


以太坊是現階段業內生態最好的公鏈,本達人課的內容將基於以太坊平臺,共包含四大部分:


第一部分(第 01-02 課),介紹以太坊相關概念及開發環境的準備,包括以太坊節點、EVM 虛擬機器、Gas 機制及開發測試用的私有鏈 Ganache。


第二部分(第 03-05 課),從編寫簡單的 Hello world 合約入手,學習智慧合約的開發、編譯、部署及網頁與智慧合約的互動,並學習通過 Truffle 開發框架快速構建 DApp 應用,及如何將合約部署到 Ropsten 測試網和主網。


第三部分(第 06-07 課),基於對 Solidity 語法的深入學習,來實現目前市場上最流行的空投合約,加深對智慧合約開發的理解與應用,全方位提升您的技術實力與思維方式。

第四部分(第 08 課),開發實現以太坊錢包轉入、轉出功能,初步瞭解以太坊錢包的基礎功能。

作者簡介

曠文傑,資深 PHP 開發,曾就職於阿里巴巴旗下美妝電商平臺。現在區塊鏈領域深耕,在某區塊鏈創業公司任技術經理,對以太坊智慧合約有較深入的研究。熱愛技術交流,常在個人部落格上分享技術心得。

課程內容

第01課:理解以太坊相關概念

2009年比特幣開創了去中心化加密貨幣的先河,同時也誕生了具有劃時代意義的區塊鏈技術。分散式資料儲存、點對點傳輸、共識機制、加密演算法等計算機技術組成了新型應用模式。然而比特幣也並不完美,它只能當作支付手段,無法圍繞它建立應用生態。所以業內稱比特幣為區塊鏈1.0。

2013年,俄羅斯90後 Vitalik Buterin 在基於比特幣的區塊鏈技術基礎之上,提出了以太坊平臺。一個開源的、圖靈完備的、有智慧合約功能的、下一代加密貨幣與去中心化應用平臺。 經過近五年的時間發展,以太坊已經成為業內公認的最好的公鏈之一,圍繞它的生態建設已經初具規模。我們稱之為區塊鏈2.0。

本達人課基於以太坊平臺,快速帶領大家學習以太坊智慧合約的開發,並掌握去中心化應用的開發、編譯、部署的完整過程。在開始之前,本篇將以通俗易懂的方式介紹以太坊開發中涉及到的相關概念,帶領大家輕鬆入門。

以太坊

以太坊(Ethereum)是一個建立在區塊鏈技術之上的、圖靈完備的去中心化應用平臺。它允許任何人在平臺中通過智慧合約技術開發、部署和使用去中心化應用。

有沒有感到和 iOS、Android 平臺有點類似?

在區塊鏈1.0時代,我們如果需要編寫區塊鏈應用需要先從 Github 上 Download 一份比特幣原始碼,然後修改底層程式碼如網路協議、共識機制、加密演算法等等,再發布到網路中。2013、2014年的很多山寨幣就是這樣產生的,改一改比特幣的程式碼,甚至是調整其中的某些引數就造出了一個新的應用、新的幣種。

而以太坊平臺,是對底層區塊鏈技術進行了封裝,讓區塊鏈應用開發者可以直接基於以太坊平臺進行開發,開發者只需專注於應用本身,而不用關注底層技術的具體實現,從而大大降低了難度。我們可以簡單的理解,以太坊平臺提供了很多模組讓使用者來搭建應用。如果將搭建應用比作造房子,那麼以太坊就提供了地基、牆面、屋頂、地板等模組。使用者只需像搭積木一樣就可以把房子搭起來,而且房子的佈局、裝飾等等都是可以隨意組裝的。因此,在以太坊平臺上可以快速建立去中心化應用,並且成本也更低。

目前圍繞以太坊已經形成了一個較為完善的開發生態圈:社群支援、開發框架、開發工具等等。

以太坊客戶端(節點)

一個以太坊客戶端就是一個以太坊節點。它提供賬戶管理、數字資產管理、挖礦、轉賬、智慧合約的部署和執行等等功能。對於我們開發者來說,它就是一個開發者工具。

需要注意的是,在區塊鏈網路中是沒有中心伺服器的。所有以太坊節點組成了以太坊的整個區塊鏈網路,每個節點都在無時無刻同步區塊資料。

enter image description here

如上圖所示(圖片來自網路),左邊是去中心化的網路,右邊是中心化網路。我們舉個不恰當的例子來加深理解。在瀏覽器——伺服器架構中,伺服器是中心化的,所有的動態資料都儲存在伺服器端。如果伺服器發生故障,則所有的瀏覽器端都無法正常訪問應用。而在區塊鏈的去中心化網路中,所有的節點都會儲存相同的資料,任意節點發生故障,都不影響整個區塊鏈資料的寫入與讀取。

Geth 是典型的以太坊客戶端。Geth 這個名字是 Go Ethereum 開源專案的簡稱。它是基於 Go 語言開發且實現了 Ethereum 協議的客戶端軟體。

Geth 提供了一個互動式命令控制檯,通過命令來操作以太坊的各種功能(API)。

Geth 會一直同步以太坊區塊鏈上的所有資料。目前 fast 同步模式下,資料量已經有近 70G;full 同步模式下,已經達到了幾百 G。

在開發過程中,我們推薦使用以太坊區塊鏈的私有鏈 Ganache (只有幾百 M),免去同步以太坊全部區塊資料的麻煩。在後續的文章中會介紹 Ganache。

賬戶

以太坊中賬戶分為兩類:

  • 外部賬戶(EOA)

    該類賬戶被公鑰——私鑰對控制(使用者),沒有關聯任何程式碼。 外部賬戶的地址由公鑰衍生而來。

  • 合約賬戶(CA)

    該類賬戶為智慧合約分配的賬戶,被合約程式碼控制且有程式碼與之關聯。 智慧合約的部署會把合約位元組碼釋出到區塊鏈上,並使用一個特定的地址來標示這個合約,這個地址就是合約賬戶。

合約賬戶儲存了程式碼,外部賬戶則沒有。除了這點之外,這兩類賬戶對於以太坊虛擬機器(EVM)來說都是一樣的。

賬戶

如上圖所示,外部賬戶與外部賬戶之間交易僅僅是轉賬。但是外部賬戶到合約賬戶,是可以啟用各種操作的。

智慧合約

網路上關於智慧合約的解釋都很晦澀。我們可以簡單的理解為在區塊鏈上,由事件驅動、以程式碼形式存在、可執行的特殊交易合同。它是程式碼與資料的集合,是以太坊的核心。

智慧合約非常適合對信任、安全和永續性要求較高的應用場景,比如:數字貨幣、數字資產、投票、保險、金融應用、預測市場、產權所有權管理、物聯網、點對點交易等等場景。同時,智慧合約在其他行業中的應用場景同樣值得期待。

目前除數字貨幣之外,真正落地的應用還不多,業內各方都在積極探索使用場景和可落地應用。和移動網際網路剛興起之時,各行各業的 App 如雨後春筍般爆發一樣,去中心化應用的市場在初期肯定有一個紅利期。大家可抓住這波機會。

Solidity

Solidity 是一門和 JavaScript 類似的程式語言,副檔名以 .sol 結尾。它用於智慧合約的開發,並能編譯成以太坊虛擬機器(EVM)位元組碼,部署到以太坊底層區塊鏈網路上。

以太坊虛擬機器(EVM)

EVM 即以太坊虛擬機器,全稱是 Ethereum Virtual Machine。它是以太坊智慧合約的執行環境。

  • EVM 是由以太坊節點提供。每個以太坊節點中都包含 EVM。
  • Solidity 之於 EVM,就像 Java 跟 JVM 的關係一樣。
  • 以太坊虛擬機器是一個隔離的環境,在 EVM 內部執行的程式碼跟外部沒有聯絡。

EVM 執行在以太坊節點上,當我們把合約部署到以太坊區塊鏈網路上之後,合約就可以在以太坊網路中運行了。

合約編譯

以太坊虛擬機器上執行的是合約的位元組碼,類似於組合語言。這就需要我們在部署之前先對合約進行編譯,轉換成位元組碼。

推薦使用 solc 編譯器。

合約部署

合約部署就是將編譯好的合約位元組碼,通過外部賬號以傳送交易的形式部署到以太坊區塊鏈網路上。由實際礦工出塊之後,才會真正部署成功。

合約執行

合約部署後,當需要呼叫這個智慧合約的方法時,只需要向這個合約賬戶傳送訊息(交易)即可,通過訊息觸發後智慧合約的程式碼就會在 EVM 中執行了。

Gas

Gas 即礦工費。以太坊上每筆交易的執行(被礦工打包)都會被收取一定數量的 Gas。Gas的目的是限制執行交易所需的工作量,同時為執行支付費用。當 EVM 執行交易時,Gas 將按照特定規則被逐漸消耗,無論執行到什麼位置,一旦 Gas 被耗盡,將會觸發一個 out of gas 異常。當前呼叫幀所做的所有狀態修改都將被回滾。如果執行結束,還有gas剩餘,這些 Gas 將會返還給傳送賬戶。因此,我們需要一個有以太幣餘額的外部賬戶,來為發起的交易支付 Gas。

如果沒有這個限制,就會出現無法停止(如:死迴圈)的合約來阻塞以太坊區塊鏈網路。

Gas 可以認為是一個工作量單位,智慧合約越複雜(計算步驟的數量、型別、佔用的記憶體等等),所需的 Gas 就越多。Gas 的最小單位是 wei:

1eth=$10^{18}$wei=$10^{9}$gwei

GasLimit:願意支付礦工費的上限。 GasPrice:礦工費單價。

gas = GasLimit * GasPrice

gas

去中心化應用(DApp)

DApp 全稱是 Decentralized App。以太坊社群把基於智慧合約的應用稱為去中心化的應用。如果我們把區塊鏈理解為一個不可篡改的分散式賬本資料庫,智慧合約理解為和資料庫打交道的程式,那就很容易理解 DApp 了。一個 DApp 不單單有智慧合約,還需要有一個友好的使用者介面和其他的業務程式。在接下來的課程中,我們將實戰開發一個簡單的DApp。

Truffle

Truffle 是目前最流行的 DApp 開發框架,它可以幫我們處理掉很多繁瑣的事情,讓我們更專注於智慧合約的開發,迅速開始編碼 -> 編譯 -> 部署 -> 測試 -> 打包 DApp 的流程。

總結

最後我們來簡單總結一下。以太坊是平臺,它讓我們方便的使用區塊鏈技術開發去中心化的應用。應用中,我們可以使用 Solidity 來編寫和區塊鏈互動的智慧合約,合約編寫好之後,我們通過一個有餘額的外部賬戶將合約部署到以太坊節點上,並執行合約(使用 Truffle 框架可以更好的幫助我們做這些事情)。

本篇中以太坊的這些概念,初次接觸者對它們有一個基本的瞭解即可,沒有必要把每一個概念都掌握的很細緻、準確。學習是一個循序漸進、逐步深入的過程,很多時候,我們會發現過了一段時間後,對於同一個概念,會有不一樣的理解與認知。

第02課:搭建開發環境

在正式開始以太坊智慧合約的開發之前,我們需要先做好準備工作,包括相關環境的搭建、IDE 的選擇等等,才能讓我們的開發之旅得心應手。

作業系統

推薦使用 MacOSLinux 系統。謹慎使用 Windows 來開發智慧合約,可能會遇到很多奇葩的問題。本課程所有文章中的執行環境都是基於 MacOS 系統。

IDE 支援 Solidity 語法

工欲善其事,必先利於器。在使用 Solidity 語言 開始編寫智慧合約之前,需要一款得心應手的 IDE。本篇推薦使用 Atom 或是 IntelliJ IDEA 系列的 WebStorm。下面分別介紹它們如何安裝支援 Solidity 語法的外掛。

Atom

開啟 Preferences - Install 操作面板,在右邊的搜尋框中輸入“solidity”後點擊 Packages 按鈕進行搜尋:

Preferences - Install

install solidity plugins

從圖中可以看到,搜尋結果頁中 linter-solidityautocomplete-solidity 兩個外掛的下載次數特別多。linter-solidity 外掛用於 Solidity 語法高亮,autocomplete-solidity 外掛用於 Solidity 語法的自動補全。我們分別點選 install 按鈕來安裝這兩個外掛,Atom 會自動下載並安裝,重啟 Atom 後即可看到效果(友情提示:翻牆後,兩個外掛下載會更快)。

Solidity語法自動補全

Webstorm

接下來介紹在 Webstorm 中如何安裝支援 Solidity 語法高亮的外掛。開啟 Preferences - Plugins 面板:

Preferences - plugins面板

按圖所示,在右邊搜尋框內輸入“solidity”,並點選 Search in repositories。接下來就會看到我們需要用的外掛 Intellij-Solidity

Intellij-Solidity

選中 Intellij-Solidity 外掛,點選右邊面板中的 install 按鈕,WebStorm 就開始下載外掛並自動安裝。重啟 WebStorm 之後即生效。需要注意的是,此外掛只支援 Solidity 語法高亮,並不支援語法自動補全。

安裝 Node

智慧合約的開發語言 Solidity 是類似於 Javascript 的一門語言。同時,後面我們需要用到的 Truffle 快速開發框架也是基於 Javascript。所以,我們需要安裝好 Node 環境。開啟 Node 中文網 並下載相應的安裝包:

Node

按提示步驟即可安裝成功。本課程所有文章的 Node 環境都是基於如下版本:

$ node -vv8.9.0$ npm -v5.5.1

節點工具 Ganache

上一篇我們有提到 Geth 節點工具。不過 Geth 會一直同步以太坊區塊鏈上的所有資料。目前 fast 同步模式下,資料量已經有近70G;full 同步模式下,已經達到了幾百G。在開發過程中,它會消耗大量的磁碟空間及時間來進行同步操作。所以,我們推薦在開發中使用 Ganache。

Ganache只有幾百 M 大小,可以快速啟動個人以太坊區塊鏈,並可以使用它來執行測試,執行命令、檢查狀態,同時控制鏈條的執行方式。

安裝

開啟 Ganache 官網,並點選 DOWNLOAD 按鈕:

下載

下載完後,直接按提示即可安裝成功。開啟後的介面如圖所示:

安裝成功

可以看到 ACCOUNTS 面板裡已經生成了10個賬戶可用。每個賬戶裡都有100枚 ETH。

Ganache 特徵

通過 Ganache,我們可以:

  • 快速檢視所有賬戶的當前狀態,包括他們的地址、私鑰、交易和餘額;
  • 檢視 Ganache 內部區塊鏈的日誌輸出,包括響應和其他重要的除錯資訊
  • 檢查所有塊和交易,以獲取相關問題的資訊

需要注意的是,Ganache每次重啟,都會將資料還原到初始狀態。

以下是 Ganache 的幾個重要面板的截圖:

區塊面板

交易面板

日誌面板

Infura

在智慧合約開發完,通過 Ganache 部署成功並進行了初步校驗後,我們需要將合約部署到 Ropsten 測試網,及 Mainnet 主網。這裡我們推薦使用 Infura

Infura 提供了託管的以太坊節點,不再需要我們消耗大量的磁碟空間和時間來搭建本地節點。同時,它為我們的 DApp 應用提供了以太坊節點支援,我們通過 API 和開發人員工具就可以安全、可靠和可擴充套件的訪問以太坊節點。目前已經為9000多名開發人員和 DApp 應用提供以太坊節點服務。

申請節點訪問 URL

我們在瀏覽器上開啟 https://infura.io/signup (需翻牆),填寫一些資料後即可免費申請 Infura 的以太坊節點訪問 URL:

申請

填寫完你的姓名、郵箱地址,並進行了人機校驗後,點選 Submit 按鈕提交資訊,Infura 就為你分配了各個網路的以太坊節點訪問 URL(紅框處):

enter image description here

需要注意的是,Infura 並不會幫你儲存這些資訊,你需要手動複製並在本地儲存。一旦丟失或忘記,是找不回這些資訊的,只能重新申請。

至此,我們的開發環境就準備好了,下一篇文章就正式開始動手編寫智慧合約了。

第03課:Hello world 合約開發

絕大部分開發者學習一門語言的時候,都是從輸出一個 Hello World 開始。我們也從實現一個 Hello World 合約為切入點,開始進入智慧合約的世界吧。

環境準備

前面已經介紹了 Nodenpm 的安裝。本系列文章依賴的環境版本:

Node:v8.9.0 Npm:5.5.1

接下來,在你的程式碼目錄裡,建立名為 smartcontract 的資料夾,並建立如下兩個檔案 package.jsonHello.sol

smartcontract ├── Hello.sol └── package.json

package.json 檔案裡,新增如下依賴包配置:

{  "name": "smartcontract",  "version": "0.0.1",  "dependencies": {    "fs": "0.0.1-security",    "solc": "^0.4.21",    "web3": "^0.20.0"  }}

fs 模組用於檔案的相關操作; solc 模組是編譯器; web3 模組是以太坊提供的工具包,主要用於與合約的通訊。

接下來執行 npm install 下載相關的依賴包。

編寫合約程式碼

環境準備好後,就可以開始編寫合約程式碼了。 開啟 Hello.sol 檔案,編寫程式碼如下:

//pragma關鍵字:版本申明。//用來指示編譯器將程式碼編譯成特定版本,以免引起相容性問題//此處不支援0.4.0之前的編譯器,也不支援0.5.0之後的編譯器(條件為 ^)pragma solidity ^0.4.0;//contract關鍵字:合約申明//和Java、PHP中的class類似//此處是申明一個名為Hello的合約contract Hello {    //public: 函式訪問屬性(後續文章為詳細闡述)    //returns (string): 定義返回值型別為string    function say(string name) public returns (string) {        return name;    }}

程式碼很簡單。就是實現了使用者輸入什麼字串,合約就原樣返回的操作。

接下來,我們需要編寫合約部署指令碼。

合約部署指令碼

smartcontract 目錄下,新建名為 deploy.js 的檔案。程式碼如下:

//設定web3連線var Web3 = require('web3');//http://localhost:7545 為Ganache提供的節點連結var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));//讀取合約var fs = require('fs');var contractCode = fs.readFileSync('Hello.sol').toString();//編譯合約程式碼var solc = require('solc');var compileCode = solc.compile(contractCode);console.log(compileCode);//獲取合約abi和位元組碼var abi = JSON.parse(compileCode.contracts[':Hello'].interface);var byteCode = compileCode.contracts[':Hello'].bytecode;//建立合約物件var VotingContract = web3.eth.contract(abi);//部署合約,並返回部署物件var deployedContract = VotingContract.new({    data:byteCode,    from:web3.eth.accounts[0],  //部署合約的外部賬戶地址    gas:750000        //部署合約的礦工費});console.log(deployedContract);

程式碼裡我加上了簡單的註釋。這裡解釋一下 abi 這個概念。

abi 全稱是 Application Binary Interface,即應用程式二進位制介面。簡單的說,就是合約對外的介面描述。

需要注意的是,礦工費 Gas 為750000。以太坊上每筆交易的執行(被礦工打包)都會被收取一定數量的 Gas。Gas 的目的是限制執行交易所需的工作量,同時為執行支付費用。當 EVM 執行交易時,Gas 將按照特定規則被逐漸消耗,無論執行到什麼位置,一旦 Gas 被耗盡,將會觸發一個 out of gas 異常。當前呼叫幀所做的所有狀態修改都將被回滾。如果執行結束,還有Gas剩餘,這些 Gas 將會返還給傳送賬戶。因此,如果部署時丟擲 out of gas 的異常,我們可適當的提高 Gas 值。

合約部署

在當前目錄下,執行 node deploy.js 命令。我們在部署腳本里將 compileCode 變數打印出來了,粗略看看就行:

{ contracts:   { ':Hello':      { assembly: [Object],        bytecode: '6060604052341561000f57600080fd5b61016c8061001e6000396000f300606060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063d5c6130114610046575b600080fd5b341561005157600080fd5b6100a1600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061011c565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100e15780820151818401526020810190506100c6565b50505050905090810190601f16801561010e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61012461012c565b819050919050565b6020604051908101604052806000815250905600a165627a7a72305820ff14cafd1df21e1edf19eff7598bc82a98940cc0fe045d6107d04bb224014f990029',        functionHashes: [Object],        gasEstimates: [Object],        interface: '[{"constant":false,"inputs":[{"name":"name","type":"string"}],"name":"say","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]',        metadata: '{"compiler":{"version":"0.4.21+commit.dfe3193c"},"language":"Solidity","output":{"abi":[{"constant":false,"inputs":[{"name":"name","type":"string"}],"name":"say","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"function"}],"devdoc":{"methods":{}},"userdoc":{"methods":{}}},"settings":{"compilationTarget":{"":"Hello"},"evmVersion":"byzantium","libraries":{},"optimizer":{"enabled":false,"runs":200},"remappings":[]},"sources":{"":{"keccak256":"0x2e3dd18fbfbd17bb4f866b1bfbb38082172a0bb58d9396b63bab04e67d9d8e08","urls":["bzzr://d1aae746dfab03e712d8a3cb76b7d4b5bf60f48fafbffa04dfa8a2d53ad5d0ca"]}},"version":1}',        opcodes: 'PUSH1 0x60 PUSH1 0x40 MSTORE CALLVALUE ISZERO PUSH2 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0x16C DUP1 PUSH2 0x1E PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP PUSH1 0x60 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x41 JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV PUSH4 0xFFFFFFFF AND DUP1 PUSH4 0xD5C61301 EQ PUSH2 0x46 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE ISZERO PUSH2 0x51 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xA1 PUSH1 0x4 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP3 ADD DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY DUP3 ADD SWAP2 POP POP POP POP POP POP SWAP2 SWAP1 POP POP PUSH2 0x11C JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0xE1 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0xC6 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x10E JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x124 PUSH2 0x12C JUMP JUMPDEST DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x20 PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x0 DUP2 MSTORE POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 SELFDESTRUCT EQ 0xca REVERT SAR CALLCODE 0x1e 0x1e 0xdf NOT 0xef 0xf7 MSIZE DUP12 0xc8 0x2a SWAP9 SWAP5 0xc 0xc0 INVALID DIV 0x5d PUSH2 0x7D0 0x4b 0xb2 0x24 ADD 0x4f SWAP10 STOP 0x29 ',        runtimeBytecode: '606060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063d5c6130114610046575b600080fd5b341561005157600080fd5b6100a1600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061011c565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100e15780820151818401526020810190506100c6565b50505050905090810190601f16801561010e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61012461012c565b819050919050565b6020604051908101604052806000815250905600a165627a7a72305820ff14cafd1df21e1edf19eff7598bc82a98940cc0fe045d6107d04bb224014f990029',        srcmap: '25:102:0:-;;;;;;;;;;;;;;;;;',        srcmapRuntime: '25:102:0:-;;;;;;;;;;;;;;;;;;;;;;;;47:78;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;99:1;94:3;90:11;84:18;80:1;75:3;71:11;64:39;52:2;49:1;45:10;40:15;;8:100;;;12:14;47:78:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;89:6;;:::i;:::-;114:4;107:11;;47:78;;;:::o;25:102::-;;;;;;;;;;;;;;;:::o' } },  errors:   [ ':5:5: Warning: Function state mutability can be restricted to pure\n    function say(string name) public returns (string) {\n    ^ (Relevant source part starts here and spans across multiple lines).\n' ],  sourceList: [ '' ],  sources: { '': { AST: [Object] } } }

開啟 Ganache 的 LOGS 面板,可以看到部署產生的交易(Transaction)日誌:

transaction

箭頭所指就是部署成功後的合約地址:0xbf474d24ba8b19811db5deb51137ddccbe3ff288(每個人部署後的地址可能都不相同)。

我們記錄下來,後面的合約呼叫程式碼裡需要用到。同時,也可以開啟 ACCOUNTS 面板,觀察賬戶餘額的變化。

合約呼叫

合約部署成功之後,我們寫一段程式碼來呼叫合約裡的 say() 方法,檢測一下效果。在 smartcontract 目錄下,新建名為 run.js 的檔案,程式碼如下:

//設定web3連線var Web3 = require('web3');//http://localhost:7545 為Ganache提供的節點連結var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));//讀取合約var fs = require('fs');var contractCode = fs.readFileSync('Hello.sol').toString();//編譯合約程式碼var solc = require('solc');var compileCode = solc.compile(contractCode);//獲取合約abi和位元組碼var abi = JSON.parse(compileCode.contracts[':Hello'].interface);var byteCode = compileCode.contracts[':Hello'].bytecode;//建立合約物件var VotingContract = web3.eth.contract(abi);//0xbf474d24ba8b19811db5deb51137ddccbe3ff288為合約部署地址var contractInstance = VotingContract.at("0xbf474d24ba8b19811db5deb51137ddccbe3ff288");var result = contractInstance.say.call('Hello world');console.log(result);

我們來執行 node run.js 命令,可以看到在終端裡輸出了 Hello world

字串拼接

我們可以擴充套件一下。如果我們事先在合約裡定義好 Hello 字串,如何與 name 變數進行字串拼接?

在智慧合約裡進行字串的拼接可不是一件簡單的事情。我們先用絕大部分程式語言都通用的連線符 +. 來嘗試著修改程式碼:

//pragma關鍵字:版本申明。//用來指示編譯器將程式碼編譯成特定版本,以免引起相容性問題//此處不支援0.4.0之前的編譯器,也不支援0.5.0之後的編譯器(條件為 ^)pragma solidity ^0.4.0;//contract關鍵字:合約申明//和Java、PHP中的class類似//此處是申明一個名為Hello的合約contract Hello {    string str="Hello ";    //public: 函式訪問屬性(後續文章為詳細闡述)    //returns (string): 定義返回值型別為string    function say(string name) public returns (string) {        return str + name;    }}

執行 node deploy.js 部署指令碼後,丟擲了一個異常:

TypeError: Operator + not compatible with types string storage ref and string memory
return str + name;

我們將其修改為“.”並嘗試部署後,也丟擲一個異常:

TypeError: Member "name" not found or not visible after argument-dependent lookup in string storage ref return str . name;

說明在智慧合約裡,“+”和“.”都不是連線符。通過查閱 Solidity 官方文件,我們發現 Solidity 語言並不提供字串連線符的語法:

Solidity文件

那有沒有其他方式,可以實現字串的拼接功能呢?

以太坊核心開發團隊已經為其提供了字串的擴充套件類。從 strings.sol 處下載字串工具合約,儲存到 smartcontract 目錄中:

smartcontract  ├── Hello.sol  ├── deploy.js  ├── node_modules  ├── package.json  ├── run.js  └── strings.sol

修改 Hello.sol 程式碼:

pragma solidity ^0.4.0;//匯入 strings.sol 工具合約import "./strings.sol"; //注意這裡contract Hello {    //將 strings 工具合約用於所有資料型別    using strings for *;    //注意這裡    string str="Hello ";    function say(string name) public returns (string) {        return str.toSlice().concat(name.toSlice());    }}

執行部署指令碼 node deploy.js 後,丟擲一個異常:

ParserError: Source "strings.sol" not found: File not supplied initially. import "./strings.sol"

提示 strings.sol 檔案找不到,這並不是合約程式碼有問題,而是 solc 編譯器 的問題。

solc 編譯器 目前不支援關聯檔案的自動引入。我們期望編譯器以更智慧的方式引入關聯的檔案,但是這意味著它至少需要 fs 模組,而 fs 模組反過來又增加了 solc 模組的大小,同時也增加了不必要的依賴。

所以,我們需要修改部署指令碼檔案 deploy.js 的程式碼:

//設定web3連線var Web3 = require('web3');var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));//讀取strings.sol和hello.sol兩個合約     var fs = require('fs');var contractCode = {     //注意這裡的變化    "strings.sol":fs.readFileSync('strings.sol').toString(),    "Hello.sol":fs.readFileSync('Hello.sol').toString()};//編譯合約程式碼var solc = require('solc');var compileCode = solc.compile({    //注意這裡的變化    sources:contractCode},1);console.log(compileCode);//獲取合約abi和位元組碼var abi = JSON.parse(compileCode.contracts['Hello.sol:Hello'].interface);  //注意這裡的變化var byteCode = compileCode.contracts['Hello.sol:Hello'].bytecode;   //注意這裡的變化//建立合約物件var VotingContract = web3.eth.contract(abi);//部署合約,並返回部署物件var deployedContract = VotingContract.new({    data:byteCode,    from:web3.eth.accounts[0],    gas:750000});console.log(deployedContract);

讀取 strings.solHello.sol 兩個合約檔案,並一同編譯即可。再次執行 node deploy.js 命令,合約部署成功。記錄下合約地址:0x58a73dea66cea789dcfb5b4a94b7247ca010b781(每個人執行的合約地址可能都不相同)。

同時,合約呼叫指令碼 run.js 的程式碼修改部分和 deploy.js 相同:

//設定web3連線var Web3 = require('web3');var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));//讀取strings.sol和hello.sol兩個合約 var fs = require('fs');var contractCode = {    "strings.sol":fs.readFileSync('strings.sol').toString(),     //注意這裡的變化    "Hello.sol":fs.readFileSync('Hello.sol').toString()   //注意這裡的變化};//編譯合約程式碼成位元組碼var solc = require('solc');var compileCode = solc.compile({   //注意這裡的變化    sources:contractCode},1);//獲取合約abi和位元組碼var abi = JSON.parse(compileCode.contracts['Hello.sol:Hello'].interface);  //注意這裡的變化var byteCode = compileCode.contracts['Hello.sol:Hello'].bytecode;   //注意這裡的變化//建立合約物件var VotingContract = web3.eth.contract(abi);var contractInstance = VotingContract.at("0x58a73dea66cea789dcfb5b4a94b7247ca010b781");var result = contractInstance.say.call('world');console.log(result);console.log(contractInstance.say.call('Guys'));

執行指令碼後,可以看到終端打印出了兩行字串:

Hello world Hello Guys

strings.sol

工具合約中還提供了字串的比較、查詢、切割等方法,大家可以動手嘗試。本篇不做過多介紹。

至此,第一個智慧合約 Hello world 的程式碼編寫、編譯、部署、 呼叫的過程就完成了。建議大家動手執行一遍,加深理解。

相關推薦

一起智慧合約開發

課程介紹無論在科技圈還是金融圈,“區塊鏈”儼然成了最熱的詞彙。2016年,區塊鏈寫入了國家的十三五規劃中;2017年,央行基於區塊鏈技術的數字票據交易平臺測試成功;同年,工信部發布了首個區塊鏈參考架構標準。
經過2017年如火如荼的投資盛宴,區塊鏈開始逐漸走向應用落地,業內都

區塊鏈100講智慧合約solidity如何節省GAS費?

1 摘要 在以太坊上,程式碼即法律,交易即金錢。每一筆智慧合約的執行,都要根據複雜度消耗一筆GAS費(ETH)。那麼,智慧合約solidity語言的編寫,不僅要考慮安全,也要考慮語言的優化,以便高效便宜了。 本文將從以下一些方面分析如何節約GAS的程式設計總結

《區塊鏈學堂》第三課智慧合約實戰(附課程視訊)

既區塊鏈學堂第二課:以太坊架構和工具之後,時隔一週的4月2日區塊鏈學堂推出了第三課:以太坊智慧合約實戰。 本期主要介紹了以太坊智慧合約程式設計基礎及實戰。 從這一期開始我們區塊鏈學堂會在鬥魚進行直播,方便不能到現場聽課的朋友們。 以下一些現場照片,課程視訊和PPT在最後。

智慧合約開發環境搭建

以太坊合約開發最快速上手是使用remix-ide,用瀏覽器開啟即可使用。不過喜歡折騰的話,就需要手動搭環境了,本文簡單介紹了以太坊開發環境的搭建。 一、 搭建環境 本文使用的作業系統為Ubuntu 16.04。 以太坊開發需要安裝:geth、s

智慧合約學習筆記網頁互動

沒搞過web程式,花了幾天研究一下,總算是搞懂了網頁與以太坊節點的互動流程。 網頁與智慧合約互動,需要使用web3.js,它實現了通用JSON PRC規範,通過JSON RPC協議與以太坊節點進行互動。除了js以外,以太坊還提供了Java、Python等語言的API,對於沒有提供API的語言

智慧合約學習筆記使用Truffle框架開發部署智慧合約

truffle是一個智慧合約的開發框架,具體的就不介紹了,我們主要是說說怎麼使用這個框架來進行智慧合約的開發,官網戳這裡。 文章目錄 #安裝 首先我們要先安裝npm和truffle,安裝命令如下 sudo apt install npm sudo n

智慧合約學習筆記開發流程及工具鏈使用

本文主要介紹開發流程和工具鏈的使用,安裝過程百度上有好多,這裡就不贅述了 網上隨便找了一個智慧合約的例子,咱們來做一個投票系統,先用傳統的中心化方案去實現,然後在過度到區塊鏈1.0,最後再用區塊鏈2.0,感受一下開發思想的不同。 業務分析 傳統

web3j教程java使用web3j開發智慧合約交易

從廣義上講,有web3j支援三種類型的以太坊交易: 1.以太幣從一方交易到另一方 2.建立一個智慧合約 3.與智慧合約交易 為了進行這些交易,必須有以太幣(以太坊區塊鏈的代幣)存在於交易發生的以太坊賬戶中。這是為了支付gas成本,這是為支付參與交易的以太坊客戶端的交

《我區塊鏈》—— 三十四、智慧合約靜態安全分析

三十四、以太坊智慧合約靜態安全分析        以太坊的智慧合約程式碼審計,筆者找到兩種方式:一是 CertiK,一個提供智慧合約安全服務的區塊鏈平臺,是一條公鏈系統,採用 PoP(proof-of-proof)共識機制,用來分析合約需要消耗該區塊鏈平臺的原生

區塊鏈技術進階-深入詳解智慧合約語言 solidity(含原始碼)-熊麗兵-專題視訊課程...

區塊鏈技術進階-深入詳解以太坊智慧合約語言 solidity(含原始碼)—103人已學習 課程介紹         區塊鏈開發技術進階-深入詳解以太坊智慧合約語言 solidity視訊培訓教程:本課

iOS應用程式如何呼叫智慧合約

以太坊智慧合約有各種各樣的用例,但到目前為止,從你的iOS應用程式中呼叫它們非常困難。不過如果使用以太坊iOS開發套件和EtherKit,這種情況會改善很多,你可以立即開始使用。在本教程結束時,你將能夠呼叫其ABI(應用程式二進位制介面)中定義的任何公共合約函式。 對於這個專案,我們將使

如何實現一鍵呼叫智慧合約

今天有人問如何用按鈕呼叫智慧合約,我不知道在哪裡找,所以我決定寫這個,很快就可以。 在這篇文章中,我將給出一個簡單但有希望有效的演示,說明JavaScript開發人員如何建立一個能夠呼叫智慧合約的網頁,並通過單擊即可向其匯款(以太幣)。 要做到這一點,使用者將需要使用支援Web

olidity語言開發智慧合約中的繼承

我們已經探索了很多主題,在編寫智慧合約時我們發現經常使用相同的模式:例如,智慧合約具有在建構函式中設定的所有者,然後生成修改器以便僅讓所有者使用一些功能。如果我們制定實施這些功能的基礎合約並在未來的智慧合約中重複使用它們那該怎麼辦?你一定猜得到,我們將使用繼承。 在Solidity中,繼承與經典的面向物

java如何在Spring Boot中用web3j開始開發智慧合約

通過Spring的依賴注入將web3j整合到Spring Boot應用程式中。此處提供了示例應用程式: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

關於智慧合約在專案實戰過程中的設計及經驗總結(1)

此文已由作者蘇州授權網易雲社群釋出。 歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗 1.智慧合約的概述 近幾年,區塊鏈概念的大風吹遍了全球各地,有的人覺得這是一個大風口,有的人覺得他是個泡沫。眾所周知,比特幣是區塊鏈1.0,而以太坊被稱為了區塊鏈2.0,而區塊鏈1.0和2.0最主要的差別就在於以太坊擁有

關於智慧合約在專案實戰過程中的設計及經驗總結(2)

此文已由作者蘇州授權網易雲社群釋出。 歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗 7.智慧合約經驗分享 1)智慧合約開發的工具的問題 古人云“工欲善其事必先利其器”,同意良好的智慧合約的開發工具對智慧合約的開發效率有極大的提升。以下是一些比較好的智慧合約的開發組合: &nb

智慧合約的奇幻漂流

“A smart contract is a computer program executed in a secure environment that directly controls digital assets.” – Vitalik Buterin 作為以太坊

區塊鏈開發(二)部署並執行第一個智慧合約

        網路上不少部署智慧合約的文章,但是都有一個共同的特點,就是採用命令列的方式來部署,先是建立SOLC的編譯環境,然後部署Geth或者Eth節點,然後一步一步生成錢包、ABI、合約地址進行部署,對初學者來說晦澀難懂而且容易失敗,本文主要介紹如何在圖形化介面下一鍵部

WEB頁面訪問智慧合約--使用Web3

準備工作  使用remix IDE開發智慧合約程式碼,並部署合約到測試鏈上。請參考《使用remix釋出智慧合約》  安裝Ganache,使用Ganache模擬一個以太坊坊節點。請參考《Ganache模擬以太坊區塊鏈節點》 Web3簡介 Web3JS是

智慧合約的編譯器使用

注意 本節不適用於solcjs,即使在命令列模式下使用也不適用。 Solidity儲存庫的構建目標之一是solidity solc命令列編譯器。使用為您提供所有選項的說明。編譯器可以生成各種輸出,範圍從簡單的二進位制檔案和抽象語法樹(解析樹)上的彙編到氣體使用的