2022-07-15 出版 KryptoCamp 撰稿

智能合約的概念最早由 Nick Szabo 於 1994 年提出,作為區塊鏈的一部分,他是一個狀態機(state machine)的存在,需要「交易( transactions)」來更改狀態(state),同時也可以做任何的邏輯運算( logic operation)。
我們常常透過智能合約修改區塊鏈上的各種「 狀態 」,不管是佈署還是普通交易,甚至是呼叫函式,這些動作都需要透過交易和挖礦打包這兩個動作才能達到目的。同時其是圖靈完備的,所以可以解決任何的電腦問題。
在智能合約的佈署過程中,首先我們會撰寫智能合約程式碼,經過編譯器編譯之後獲得 bytecode,真正佈署的步驟其實是將 bytecode 打包到 EVM ,這個部分在未來章節會做解釋!
Solidity 是目前針對以太坊虛擬機設計中,最知名的智能合約編輯語 言。屬於編譯型語言,而非直譯型語言。最早在 2014 年由撰寫以太坊黃皮書的 Gavin James Wood 提出,設計參考了 ECMAScript,所以也會有人認為寫過 JavaScript 的工程師對 Solidity 較為上手。
撰寫智能合約並不單只有 Solidity,還有其他的智能合約語言像是:
- Serpen :基於 Python 的智能合約語言
- LLL:類似 Low-Level 的組合語言
- Mutan:已經被棄用的類似 Go 的語言
- Vyper:類似 Python 的實驗型語言
Solidity 的特性很大程度與 EVM、區塊鏈如何運作有關,例如以 太坊底層並非像比特幣系統一樣是 UTXO(Unspent Transaction Output)的,其中 Transaction 被簡稱為 Tx。其架構更像是以帳戶為 基礎的 Account Model,具有 State。這使得合約本身也是一個特殊的 Account 型態。也因此每個地址都有 payable 與否的敘述,在編譯階段便定義了此物件是否可供支付。
在去中心化的區塊鏈網路上必須盡可能的減少人為決定的情況,所以 Solidity 的設計上強調了合約、函式、外部帳戶 EOA 的互動。每一個函數呼叫都會成為一筆交易,來去對狀態機、區塊鏈、EVM 進行互動。
【環境建置 Remix IDE】
Solidity 檔案的副檔名會是 .sol ,在這裡我們使用的整合開發環境(IDE)是 remix(https://remix.ethereum.org/)。
Remix 可以用來作為編輯器、編譯器,同時我們也會在上面佈署和操作合約。同時他也是被以太坊基金會所開發與維護的。其他常用的開發工具還包含了 Truffle 與 Ganache,在最後幾章我們會介紹到它們!
為了避免版本或系統不同導致結果有所差異,在這邊提供我使用的環境,如下:
環境 | 系統 | 版本 |
---|---|---|
作業系統 | Windows | 10 Pro x64 |
瀏覽器 | Google Chrome | 96.0.4664.110 (正式版本) (64 位元) |
編輯器 | Visual Studio Code | 1.63.0 |
Remix 無須下載軟體和註冊帳戶,只要有瀏覽器便可直接開啟進行使用,只是因為存檔的機制和瀏覽器與內存有關,因此在上面撰寫完合約最好還是逕行存檔到本地端以免遺失。
使用 Remix 時雖然介面看似複雜,但已經相對 Truffle 或其他開發環境簡易許多。首先我們打開 Remix IDE(https://remix.ethereum.org/)。
圖1-1
在 圖 1-1,點選 Workspaces 中 檔 案 目 錄 中 的 小 文 件 圖 示「Create NewFile」便可新增一個檔案。而 左 側 依 序 為 編 譯 器(Compiler)、 佈 署 與 互 動 介 面(Deploy & Run Transactions)、除錯器(Debugger)、統計分析(Solidity Static Analysis)、元件測試(Solidity Unit Testing)等功能。最下方的「插頭」符號為擴充應用程式,如果找不到相關功能可在此搜尋並點選啟動即可;而「齒輪」符號的按鈕則為 Remix 相關基本設定。
這邊我們只側重介紹編譯器(Compiler)、佈署與互動介面(Deploy & Run Transactions)兩個最常使用到的功能。
首先是在編譯器中:
- Compiler:我們可以自由選擇想要編譯的版本,這個部分需要跟我們於程式碼中宣告的版本符合。
- Language:在 Remix 中可編譯並執行的語言不只有 Solidity,但我們在這本書中只會使用到 Solidity 撰寫智能合約。
- EVM Version:以太坊虛擬機也有不同的版本迭代,通常都是使用預設的選項(compiler default)。
- Compiler Configuration:在這邊我們可以自行決定關於編譯器的設定檔,像是是否要自動編譯(每對檔案進行一次更改就會自動編譯,反之就是要手動編譯),是否要優化,是否要把警告訊息隱藏等。
圖 1-2 Remix 編譯介面
智能合約的最佳化可見: https://docs.soliditylang.org/en/v0.8.11/internals/optimizer.html
再來是在執行介面中:
- Environment:我們可以選擇編譯環境,分別能夠選擇兩種不同版本的 Javascript Virtual Machine,這會模擬在瀏覽器上運行的狀態,這個部分在後來開發工具的相關章節會特別敘述。除了 VM 我們還能選擇Injected Web3,這個選項最常的結果是跳出 MetaMask 之類的節點媒介,並與之進行互動。最後是 Web3 Provider,常用狀況是與 Ganache或本機私網 http://localhost:8545 等節點直接溝通。
- Account:若是選擇 Javascript VM 則會有數十個虛擬帳戶供我們做使用。如果是選擇 Injected Web3 並連動到 MetaMask 的話互動帳戶就是我們在錢包裡面的帳戶。
- Gas Limit:註明 Gas 使用量限制,由於在 Gas 的使用量是與我們程式碼中使用到的 Operation 數量有最直接相關,因此我們可以透過 GasLimit 來確保我們的程式消耗的 Gas 在我們預期的範圍內。
- Value:轉入智能合約的金額,後方還有單位的選項欄。由於在以太坊中的合約也屬於帳戶的一種,自然可以有匯款的選項。
圖 1-3 Remix 佈署介面
選擇完已編譯的合約即可使用 Deploy 按鈕來進行佈署,讓我們先來寫第 1 個合約吧!
【版本控制】
在合約的一開始我們必須先宣告版本,版本控制可以像以下的各種寫法,像是固定在 0.8.11 版。
pragma solidity ^0.8.11;
或者介於 0.4.16 和 0.9.0 之間。
pragma solidity >= 0.4.16 < 0.9.0;
現在最新的版本是 0.8.12,但每一個大版本都會有相對應的 breaking change 和汰除的語法,所以使用上還需要注意。尤其是在參考別人的程式碼 的時候,如果一昧地複製貼上可能會造成語法錯誤,進而出現非常大的損失。
Solidity v0.8.0 Breaking Changes
最好的方式就是在使用各種程式碼或教學的時候,都回去 Solidity 的官方文件查看當前用途或版本的資訊。雖然過程有些麻煩,不過在開發的過程中多注意一些小細節肯定沒壞處的。
還有一點需要說明的是在某些編譯器版本中需要著名 License,不然可能會報錯!例如:
//SPDX-License-Identifier: MIT
本書絕大部分的 License 都是 MIT 或 GPL 3.0,畢竟我也繼承了大部分前人的智慧,所以自認為理所應當設定成如此!
【Hello World & First Contract】
不免俗的來 Hello World 一下吧!首先我們在檔案目錄中創建一個名為 Hello.sol 的檔案。並把以下程式碼複製貼上到檔案中,紙本的讀者就要用手打了!
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;
contract HelloWorld {
string public helloworld = "Hello World!";
}
撰寫完程式碼之後到編譯器的地方按下「Compile Hello.sol」就可以將整個檔案進行編譯。在這邊我們還可以看見下方有幾個選項可以選,分別是Compilation Details、ABI、Bytecode。其實後兩者已經存在在 CompilationDetails 中了,根據合約的安全性我們不該把這些資料提供給其他人,因為只要有了這些資料便可以直接與我們的合約進行開發者等級的互動。這邊我們先不討論兩者的內容,在後續的章節會一一探討。
選擇完已編譯的合約(也就是 Helloworld – Hello.sol)即可使用 Deploy 按鈕來進行佈署。佈署完即可看見圖 1-4 的訊息:
圖 1-4 佈署資訊
首先我們可以看到佈署的合約會擁有自己的一串地址,還有一個名為「helloworld」的按鈕,按下去之後會回傳 “Hello World!” 字串。我們要如何理解 Solidity 的運作模式呢:可以把 set() 視做 input,把 get()視做 output。當然這邊的 set() 和 get() 是我們自己定義的函式,想要取其他的名字也可以!
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;
contract FirstContract {
uint public myVar;
function get() public view returns (uint) {
return myVar;
}
function set(uint x) public {
myVar = x;
}
}
這邊先偷跑一下之後的內容,在 get()
這個函數裡面:
public
代表這個函數的可視性(Visibility),意思就是說我們可以在 Remix interface 跟它互動view
的意思則為這個函數是唯讀(read-only)的。returns
就像我們已經認識的其他程式語言一樣,這個函數會回傳一個型別為 uint 的值
而這裡的 set() 和 get() 就分別為 writing function 和 read function,writingfunction 會對現行的狀態變數進行修改,而 read function 可去察看現存的狀態變數的值。同時可視性為public 的字串變數 helloworld 也會自動被設置一個 getter function 讓我們能夠在 Remix 的使用者介面與其互動(看見當前變數值)。這些內容在未來相關章節會有更深入的敘述。
註解
為了有效地提升程式碼的可讀性,我們常常會在其中加上註解來讓我們的同事可以更理解我們的目標。在 Solidity 可以使用 // 來達到單一行註解的功效,使用 / ~ / 來達到區塊註解的功效。區塊註解表示我們想要註解的內容可以是隔行的,變成一個不會被編譯和執行的閱讀區塊。
pragma solidity ^0.8.11;
contract Account{
// HI
/*
Hello
*/
}
【Practice】
- Practice 1
- 請試試看寫出一個自己的智能合約吧!
- 這個智能合約要能按下一個
get()
Function 之後會回傳 666!
- Practice 2
- Solidity 是編譯型語言還是直譯型語言?
- Practice 3
- 佈署智能合約在以太坊區塊鏈上可視為何種行為:
- A. 交易
- B. 鑄造
- C. 分岔
- D. 挖礦