Ethereum smart contract 撰寫

Hsieh Yung-chen
Taipei Ethereum Meetup
15 min readNov 17, 2016

應11/26在北科大的區塊鍊愛好者年會,我們社群在第二天早上會有一場worksop。說是workshop,但我們時間實際只有大約150分鐘,不夠時間讓大家一起動手,只有台上演示一下而已。所以先筆記下要講的東西,到時候大會照這篇文件做,也好讓底下觀眾容易跟上。

概念講解

在實際動手之前,我們先來瞭解一下Ethereum smart contract是怎麼一回事。

Ethereum smart contract 就像一般程式一樣,在執行的時候會根據使用者當時狀態,以及所下指令的不同而有不同的結果。舉個販賣機的例子來講,我的狀態有0, 5, 10三種,另外有投五元和投十元兩種指令。當我狀態在0的時候投下五塊,這樣販賣機會變成5的狀態。這時侯再投下五塊,販賣機會到10的狀態。一樣的指令,會根據當前狀態不同而有不同結果。而在區塊鍊上要做到每個節點都能跑出一樣的結果,那就要讓每個節點上的同一個smart contract有相同的狀態,屬於同步的部分,就交給區塊鍊的同步來做。

另外每個不同的smart contract都會有不同的邏輯,也會定義很多不同的狀態,或是資料格式等等。在區塊鍊上放的都是這些smart contract的“值”,根據不同的邏輯,狀態,這些值也會有不同的解讀方式。而要知道那些smart contract要如何解讀,我們會需要這些值的接口,稱為ABI (Application Binary Interface)。

整理一下實際流程,一個Ethereum smart contract寫好並編譯之後,我會得到編譯過的binary code,以及一個json格式的ABI。接下來用ABI創造一個smart contract的空殼,把剛剛的binary code作為data塞進去,接著廣播到整個Ethereum blockchain network,等待好心的礦工幫你把妳的smart contract放上區塊鍊。放上區塊鍊後,會得到一個該smart contract的id (或說account, address)。得到這個id之後,我們就可真的來跟這個smart contract來做互動。根據不同的互動指令,來改變該smart contract在區塊鍊上的狀態。

從區塊鍊的角度來看,當miner收到一個包含create smart contract的指令後,他會把他接到的值,放進區塊鍊裡。同時會回傳這些值加上時間的hash,作為這個smart contract的id。而指令方面,當miner接收到一個新的該smart contract指令(指令裡會指定該smart contract的id),他會先從區塊鍊裡找出該smart contract的status,根據當前status以及剛剛的指令做運算。運算出來的結果再放進他在做的區塊裡。當他做的區塊被整個區塊鍊網路所接受,那大家就會知道先前對smart contract的互動指令已經完成,smart contract來到新的status。由於區塊鍊會自己同步,所以該網路里所有節點讀取到狀態皆會相同,且smart contract的執行結果不受外在環境影響(外在環境是指像溫度,或其他外部消息等等),所以不管是哪個礦工挖到新的區塊,只要該指令有被放進區塊鍊裡,那就都會得到一樣的結果。

事前準備

根據上面的解釋,接下來的hands on會需要用到的一些功能:

  1. 寫程式的地方
  2. 編譯器
  3. 與區塊鍊網路連線,廣播指令
  4. ethereum 區塊鏈狀態檢查
  5. smart contract狀態檢查

所以先介紹一下等等會用到的一些工具:

  1. Geth: Geth是go版本的Ethereum全節點,與Ethereum區塊鍊網路連線,會同步所有的區塊戀上所有的資訊。
  2. Mist: Ethereum wallet,背後需要有一個全節點支撐。Mist提供介面化的皮包,讓使用者更容易跟底層節點互動。
  3. Solidity realtime compiler
  4. etherscan.io
  5. ethstats.net

準備階段:這次Demo如果沒有意外,會是用Ethereum mainchain來做演練。在開始之前我們需要先安裝好一個Ethereum全節點,我用的是Geth,是官方指定版本,更新最快。Geth怎麼開啟以及打開的一些參數不再贅述,打開之後他會開始去跟網路上其他節點要資料,以便在本地端同步建構出一條區塊鍊。Ethereum mainchain行之有年,他的區塊鍊上包括很多過去的交易,合約。同步這些資料再加上驗證需要至少半天的時間,是環境準備裡面所需時間最久的。

geth
或是你也可以直接跑Mist, 他背後也會幫你把節點跑起來,跟網路上其他節點同步區塊鍊。

底下為了讓流程可以比較順暢,會先示範用Mist錢包示範怎麼放上合約。接著才講解合約內容,以及如何跟其他人的合約做互動。最後會講解Mist跟Geth之間對應的指令,方便讀者後續寫程式碼可以接上自己的邏輯(不在區塊鍊上的程式邏輯),而不用直接操作Mist。

部署契約

Mist 打開後會長這個樣子

直接到契約頁面,按下”部署新契約“

直接到契約頁面,按下”部署新契約“

進入部署契約後,您可以選擇從哪個帳戶發起,並選擇要給這個新產生的契約多少ETHER。

我用Main account來部署合約,並傳0.01Ether給這合約。

寫下代碼後右邊選擇要部署的契約,要部署的契約會根據你左邊撰寫的代碼,列出所有可以部署的契約。選擇要部署的契約後,根據不同契約會有不同參數要填。

這個Split Ether的契約不用任何參數,所以是空的。

這裡不對gas做另外的解釋,詳細內容可以看文末附上這週的Ethereum meetup Taipei錄影/投影片,昶吾有對Ethereum做詳盡的解釋。按下部署契約之後跳出確認訊息,輸入該帳號的密碼即可部署契約。

阿我不小心重開沒注意到數字歸零,但我懶得再重做重新截圖。所以這筆交易只會花到手續費,沒有value

成功部署後,點選交易可以觀看交易細節,其中有個交易代碼,利用這個交易代碼也可以到etherscan查詢這筆交易的細節(此筆交易:https://etherscan.io/tx/0x4745a57c54e342278052f10c9cf403764d4a3fd540c44afafbdcad66ca285386)。

等待一段時間讓礦工把這筆交易收近區塊鍊裡。可以得到此合約的帳戶代碼。如此就完成智能合約的創造。

智能合約內容

第一個合約是SplitEther。

先講一下這個合約的邏輯,這個合約用來分配Ether,會把他收到的金額對半分給receive1和receive2。

第一行是要宣告程式語言所使用的版本,好讓編譯器知道如何編譯。

在第二行用了contract這個關鍵字,宣告了一個叫SplitEther的合約。而從架構上來看,這合約內容只有一個函式,其他什麼都沒有。

接下來合約裡宣告了唯一的一個函式,這個涵式名稱與合約相同(都是SplitEther),這樣的函式稱為建構子(constructor),在每次合約被部署的時候會被執行,因為部署只有一次,所以只會被執行一次。前面有提到,選擇部署合約的時候,根據不同合約會有不同參數需要填。而這個參數就是建構子的參數。

若我把receive1, receive2的宣告移到建構子的參數的話,部署合約的時候就需要給這兩個參數的值。

回到程式碼這邊,建構子函式裡接著宣告了兩個address。這個address是solidity特有的關鍵字。Ethereum上的address有兩種,可以是合約或是個人帳戶。這邊給定了兩個receiver。

設定好receiver之後,接著就是把合約收到的Ether頗半分給他們。這裡要講的內容有兩個,第一個是msg變數,第二個是send函式。

首先是msg,在區塊鍊系統裡,可以只分成兩種資訊,一為區塊,二為被收在區塊裡的資料。既為分散式系統代表任何人都可以產生區塊以及資料,這樣的資料在比特幣被稱為“交易”(transaction),但Ethereum不全是交易所已用關鍵字“msg”。我們可以透過這個關鍵字得到這條訊息的傳送者,金額,等等資訊。還記得我們在SplitEther的建構子裡,建構子只會在合約產生的時候被執行一次,所以我們可以確定的是這個msg一定是部署合約的指令。而這個金額就是當初部署合約時輸入的0.01Ether。

send為address的其中一個函式,其作用為“從智能合約”的餘額,打錢給這個address。這邊講一個概念,區塊鏈是一個分散式系統,只要每個節點都能根據得到的指令做出同樣的對應動作,那就可以達到共識。PKI是其中一種,當每個節點認為一筆交易附上的簽章為合法,那金額的狀態就會轉變(轉移)。只要大家都認為是正確即可,不一定要完全正確。而智能合約走的就不是PKI這套,智能合約的地址來源為其hash值的前幾碼,並無相對應私鑰。也因此沒有一個人或是中央伺服器能透過簽章來改變智能合約的狀態。所以我們的合約裡要明訂資金轉移的規則,好讓去中心系統裡不同節點有個既定的規則來處理合約狀態。另外一點直得注意的是,send這個函數有回傳值,若資金轉移成功則回傳1,失敗則回傳0。失敗的情況有很多種可能,比如說合約的餘額不足,或是帳戶餘額不夠付手續費。這裡需要另外提出來的原因是因為,大部分的智能合約函式並不沒有回傳值,因為這些函式只會直接修改智能合約在區塊鍊上的狀態,並沒有回傳的對象,除非是智能合約內部函式。

SplitEther合約裡只有一個建構子函式,意味著這個智能合約只有在部署的時候有效。當然智能合約也可以訂一其他的函式,其他的狀態,好讓不只是合約發起者能跟合約互動,大家都可以跟合約互動,甚至是合約跟合約之間。

Token 合約

接下來透過另外一個智能合約: Token,來講解其他較為重要的關鍵字以及函式。

Token合約是一個最普通的合約,可以在網路上查到很多的範例,大家只要這著範例直接部署合約,就可以產生自己專屬的Token。這次Demo只留最簡單的部分。

部署合約

設定參數,部署合約。此筆交易:https://etherscan.io/tx/0xf2f6ab58ec71f9671eb6d20d86d49c25b9ef7bcb531b0cbd5c3a8765bf9791ee

程式碼部分,分為設定全域變數,建構子函式,transfer函式,以及fallback函式四個部分來講解。

第一部分:全域變數的設定

這裡宣告了三個全域變數,除了常見的幾個變數類別關鍵字以及前面提過的address類別關鍵字。這裡多了mapping和public關鍵字。

public關鍵字顧名思義就是把該變數設為公開,如此網路上每個人都可以(知道接口的情況下,及ABI,前面有稍微提到,後面會再細講)知道此變數內容。文章前面有提到ABI接口的作用,意思若沒有接口,合約的內容都是無法解讀的。因此在把全域變數設定為public的時候,ABI這邊便會直接新增一個讀取該public變數的接口。Geth可以利用這個接口取的變數資訊,Geth部分留到最後面演示。而Mist錢包也提供了方便的視覺化介面。

Mist錢包提供視覺化介面,用來讀取智能合約上的public變數。

再來是mapping關鍵字,他是一個字典,輸入鍵值可以得到相對應的另一個值。這裡我們設定每個不同的address會對應到一個非負整數,欲來記錄每個address的餘額。Solidity也有提供陣列,兩個的功能有時侯很像。但在儲存空間寸土寸金的區塊鍊系統裡,這種針對每個address的記錄,最好都是用mapping來做。

第二部分為建構子函式。

Mytoken 函式與合約本身同名,為建構子。在合約被生成的時候會執行,這裡吃了兩個變數,分別為 _tokenName 以及 _totalSupply 。在合約生成的時候,把他們的值分配到剛剛宣告的全域變數里。比較值得一提的是,在最後一行裡,把這比預生成的全部token,都給這個指令的發送者,就是合約的部署人。

第三部分為transfer函式。

transfer函式為唯一在這個合約裡自己定義的函式。其作用為token的轉移。其邏輯為:檢查若餘額足夠,則發送者餘額扣除所傳金額後加到接送者的餘額裡。一開檢查金額以及溢位若沒通過使用throw關鍵字來停止動作。值得一提的是,區塊鏈每個節點都會有一整份區塊鏈資料,如此的檢查在指令尚未被公佈至網路里就可以被檢查得到。若此時仍硬送出指令,則區塊鏈在執行時抓到錯誤,所有狀態會被改回至指令執行前,但已使用gas不會歸還。

自定義函式會出現右邊寫至契約的下拉式選單中。選好後會跳出對應參數輸入格。右下可以選擇指令發起人
任何address都可以發起指令,但若會遇到throw關鍵字而停止指令的參數,在指令尚未送出前就可以抓到。
最後一部分為fallback函式

這個沒有任何命名的函式,稱為fallback function。每個合約至多可以有一個。當有任何對這個智能合約發起的指令,在合約裡無法找到相對應的執行代碼,就會執行fallback function。會需要的原因,是因為合約地址本身跟一般地址並無差異,本來任何就可以對任何地址傳送金錢。像這樣的動作在合約裡並無相對應的指令。令一點要注意的是,若合約裡有Ether餘額,但並沒有寫傳送Ether的相對應規則,則Ether會卡在這個合約裡。MyToken這個合約並沒有寫傳送Ether的規則,也不需要餘額,所以寫一個fallback function,若有沒定義到的指令,直接終止該指令。在新的版本的Solidity裡,多了一個新的關鍵字payable。若沒有payable關鍵字,指令是無法帶有Ether的,這也是為了防止Ether被卡在合約裡。

與合約互動

拼圖的最後一塊為與合約的互動,前面的演示都只有自己跟自己部署的合約互動。若要跟其他人部署的合約互動,則需要他人部署合約之接口ABI。

契約頁面的右邊有“顯示界面”選項。按下後會跳出ABI。ABI為josn格式,若仔細看的話,會知道他其實就是合約的殼。

ABI為josn格式,若仔細看的話,會知道他其實就是合約的殼。這裡就不多花篇幅講解。到契約頁面(我事先先把合約刪掉了,但資料都還在區塊鏈上,所以我也透過這樣的方式取回合約),選擇“新增觀察契約”,並輸入剛剛的ABI,以及合約所在地址,名字可以亂取沒關係。

這樣合約就回來了。

後記&更多資源

因篇幅的關係,Mist跟Geth底層指令的對應關係留到下一篇在講。Solidity並不是一個很成熟的程式語言,其很多內容都有一直在修改,並不保證這裡所有的東西以後仍會是正確的。更多資訊可以查Solidity文件。上面也有更完整的智能合約教程。關於更多Ethereum的介紹,除了網路可以查到的資源以外,這裡有我們在社群分享的所有資訊,正好上一次的聚會昶吾分享Ethereum Ecosystem,是很好的參考。

--

--

Hsieh Yung-chen
Taipei Ethereum Meetup

CTO @ JOYSO project. A PhD student of National Taiwan University. And a dencentralize app developer/researcher.