從零開始學新東西😵💫,資源導向程式語言 Cadence (ㄧ)
在探索新語言、新學習項目時,我認為最重要的就是「不要放棄」,所以限制自己的學習範圍很重要,我的目標很簡單 :
- 不要迷失在浩瀚無垠的「窩不知道」當中痛哭流涕😭,至少能心安理得學會些什麼即可
- 試著在學習範圍內先快速迭代小東西出來(不一定要是成品,也可以是文章、影片)
而這次我要學的程式語言是 Flow Blockchain 的智能合約語言 Cadence。在這篇文章中,我將透過 Flow 的開發者工具 Playground 來學習怎麼寫 Cadence 智能合約以及跟帳戶、 Transactions 互動。
在這篇文章中,你會看到:
ㄧ、基礎認識 Flow Blockchain & Cadence 程式語言
1) Flow 和 Cadence 入門介紹
2) Cadence 語法和基本型別介紹
3) Cadence Functions 跟複合型別(Composite Types) 介紹二、Flow Playground 實作
用 Flow 開發者工具 Playground 上寫第一個 Cadence 智能合約,並使用 Transactions & Scripts 跟智能合約互動。
ㄧ、認識 Flow Blockchain & Cadence 程式語言
1) Cadence 是在 Flow Blockchain 上編寫 DApp 的程式語言
在了解 Cadence 前,我們可能要先知道 DApp 是什麼?
一般來說, App 軟體開發環境是前端 + 後端(伺服器 & 資料庫)。
而 DApp 通常則是指把後端的伺服器與資料庫改成了區塊鏈,也就是前端 + 後端(區塊鏈)。
Client-Side可以直接在區塊鏈上讀寫 data、部署程式碼、執行程式碼、驗證用戶等,而 Client-side 可以是手機、Game Console、POS System 等任何裝置。
一般來說,我們使用 flow-cli, 就能執行我們的第一行 Cadence 程式碼了, 我們只需要一個程式碼編譯器,然而,運行一個 DApp 不僅僅需要我們解釋智能合約程式碼,它還要與整個區塊鏈上的 global state 互動。而 Flow 已經幫我們把環境建置好,為我們提供了許多入門選項:
1) 測試網
2) 一個獨立的 Flow Emulator
3) Playground
2) Cadence 語法和基本型別
Cadence 語法是強型別(strictly typed)的,而預設的基本型別包含:
- Int
- Fix64
- Address
- String
- Array
- Dictionary
*強型別(strictly typed)的好處是,開發者在編譯時期(Compile-Time)就能夠發現錯誤,減少執行時期(Runtime)發生錯誤的機會。
3) Cadence Functions 跟複合型別(Composite Types)
- Cadence Function 是帶有 name labels 的 value types(類似 Swift )
- Cadence 中有一個
optional
用法,舉例let optional:String?
,意思是判斷這個變數的「值」是不是空的(nil) 。
Cadence 有兩種複合型別(Composite Types):
- 結構(struct): value 類型
- 資源(resource):線性類型(可以移動,但一次只能存在一個地方)
Cadence 資源會使用 <-
表示移動該資源,而特殊關鍵字如 create
和 destroy
,以及 @
都是表示這個物件是資源類型,例如 let canvas:@Canvas
(看到 @ 表示此為資源,在這裡定義 Canvas 為一個資源)
二、Playground 實作
Playground目前僅支持 Chrome 瀏覽器 👀
在開始實作以前,先快速介紹一下 Playground 的功能:
1) Cadence 編輯器 — 用來儲存 Cadence 程式碼的地方
Playground 上中間這一塊編輯器就是讓你輸入 Cadence 程式碼的地方,Playground 基本上模擬了 Flow 區塊鏈,而你只能在編輯器這裡定義智能合約結構和資源類型,而不是在 Transactions 或 Scripts 區,這我後面會再解釋。
2) 帳戶
在 Flow 中,所有內容都與帳戶一起儲存,包括智能合約。因此,要做任何事情,你至少需要能訪問一個或多個帳戶。很棒的是, Flow Playground 已經為我們提供了五個自動生成的帳戶(我們就不必擔心私鑰或其他事情,專心開發合約吧)。注意,目前 Playground 限制是每個帳戶只能部署一個合約。
接下來快速解釋一下 Flow 帳戶,主要有兩個區域:
1) 第一個區域是「合約區域 」。這是儲存任意數量智能合約的區域,這些智能合約包含 type definitions, fields 和 functions 等功能。此區域還包含智能合約介面,這些介面基本上是幫助其他合約導入和執行程式的地方。
2) 第二個區域是「帳戶文件系統」 。此區域是儲存帳戶資源以及控制訪問這些物件的地方。
物件儲存在帳戶文件系統的路徑下。路徑由域名(domain)和識別碼(identifier)組成。識別碼(identifier)是由自己定義的。
在文件帳戶系統中,只有三個有效域名:儲存(storage)、私有(private)和公有(public):
- 儲存(storage)域名
儲存了帳戶所有的物件(例如 tokens 或 NFTs 等資源物件),它只能由帳戶所有者直接訪問。 - 私有(private)域名
就像一個私有 API,只有帳戶所有者和他們授予訪問權限的人可以使用這些 API 介面來調用你的私有資產中所定義的 functions。 - 公有(public)域名
有點像你帳戶的公共 API。網路上的任何人都可以訪問這些功能。
3) Transactions
這裡是定義 Transactions 的地方,Flow 中的 Transaction 被定義為由一個或多個帳戶簽名、任意大小的 Cadence 程式碼塊。
Transactions 一般用於改變區塊鏈的狀態,參與 Transactions 的每一方都必須使用私鑰對 Transactions 數據進行加密簽署 。Transaction 可以訪問簽署交易帳戶的 /storage/ 和 /private/ 域,並且可以對這些域進行讀寫,當然也包含公有合約中的 functions 讀取和調用。
在 Playground 上已經把加密簽署 UI 化了,簽署 Transactions 只需點擊一下即可,我後面會再實作。
4) Scripts
在這裡你可以找到 Flow Scripts,這些 Scripts 是不需要任何帳戶的授權,且只能讀取程式碼,你也就自然不會產生任何 Transactions gas fee。(事實上現在在 Playground 送 Transactions 其實也是沒有任何 gas fee 費用的)
接下來我們來實作:
ㄧ、寫第一個 Cadence 合約並部署到 0x01 帳戶
二、跟 0x01 帳戶上的 Yo 合約互動
ㄧ、寫第一個 Cadence 合約並部署到 0x01 帳戶上
程式碼如下:
解釋一下程式碼, pub 代表 public,這個合約是公開的, Yo 這裡直接被視為合約名稱。 fun 是 function,在 Yo 合約中,我們寫了一個 sayHi function,function 裡頭 log “Yo, ______” 的字串。
執行合約部署的步驟如下:
- 選定 0x01 帳戶
- 在編輯器寫完此段程式碼
- 點擊綠色的 deploy 按鈕
- 在下方 Deployment Result console 中看到成功 Deployment
- 接著在左方 0x01 帳戶會看到 “Yo”,表示我們把命名為 Yo 的合約部署到 0x01 帳戶了
跟 0x01 帳戶上的合約互動
把 Yo 合約部署到 0x01 帳戶後,然後勒?要怎麼樣跟這個合約互動啊? Flow 提供了兩種與合約互動的方法:
1) Scripts — 只能讀取合約
2) Transactions —需要經過身份驗證來改變區塊鏈狀態並進行加密簽名
接下來我們兩者都來試試。
1) Scripts —只能讀取合約
選擇左下方 “Script”,開始寫你的第一份 Script,接著按下 Excute 執行你的Script 程式碼。
程式碼如下:
在這邊,我們要跟 0x01 中的 Yo 合約互動,所以我們要先從 0x01 把 Yo 合約 Import 進來。 Import Yo from 0x01
。再來寫了一個 public function 名為 main,在裡頭宣告變數 name = “Badu”,接著呼叫 Yo 合約中的 sayHi function。
在 Script Results Console 的結果就會看到 “Yo, Badu” 。而 Result > {“type:”Void”} 又是什麼意思?
通常我們使用 Scripts 只是用來獲取公共狀態的資訊,因此期望它們返回某值。 在我們這個例子中,沒有顯式任何返回值,Flow 會給與類似 Javascript function 返回為 undefined 的結果,返回 Void 類型。
2) Transactions — 需要經過身份驗證來改變區塊鏈狀態並進行加密簽名
在剛剛的範例中,我們直接透過 Scripts 去跟 0x01 的 Yo 合約中的 public function 做互動。而在 Transactions ,我們則需要增加授權帳戶跟合約互動。
在 Flow 上面,每個 Transactions 會有 4 個連續階段(都是可選擇 optional 的),分別是:
- 準備階段(prepare phase)
- 前期(pre-phase)
- 執行階段(execute phase)
- 後期(post phase)
如果我們要在這 4 個階段分享數據、物件,我們可以在 Transactions 內部宣告 local variables,就像我們這段程式碼,先宣告 let name: String
在個範例中,我們只需要「準備」和「執行」階段。在準備階段,我們獲取帳戶地址並將其儲存以供在執行階段訪問,在執行階段我們調用 Yo 合約的 sayHi function。
- 準備階段
我們可以直接訪問由 auth account 實例提供的帳戶儲存和其他私有 function,在這裡我們先使用 address filed。 - 執行階段(儲存所有 transactions 的主要邏輯)
在這階段,你可能無法訪問帳戶的私有身份驗證物件,但假設你已正確完成所有操作,比如你已經使用 local variables 來儲存你在準備階段借用或創建的任何類型的資源,那你就可以在執行階段使用帳戶的這些資源。
在 Transation Signers 的地方選擇任一帳戶,在這邊我選擇 0x02 後點擊 Send,Transaction Results Console 會顯示 “Yo,0x2"。
在初步熟悉 Playground 的操作與 Cadence 的撰寫方式後,接下來我們再進階一點學習如何借用資源。
接下來我們來實作:
ㄧ、 把複雜一點的 Cadence 智能合約部署到 0x02 帳戶上
二、 跟 0x02 帳戶上的合約互動,創建 capability 跟 reference
ㄧ、 把複雜一點的 Cadence 智能合約部署到 0x02 帳戶上
要注意,在 Flow 上面,你的資源、結構都是包在合約中的,不能在合約之外宣告任何變數。
點擊 0x02 Account,並將以下這份合約在編輯器上撰寫,接著點擊綠色的 deploy,就會看到 0x02 Account 出現 “Artist”,代表 Artist 合約已部署在 0x02 Account 上了。
程式碼如下:
- init()
只有在初創合約時會運行 init() function,我們可以訪問 self.account。在 init() 中提供對帳戶的完全私人訪問權限。 - 帳戶 Storage API
在 init() 中我們調用了兩個 functions,save
和link
是帳戶 Storage API 的一部分,在這邊使用,是為了能夠讓 Printer 資源實例保留,為其他人提供訪問權限。 - Save Function
我們想要在 Flow Blockchain 上保留的所有內容都必須儲存。在這裡,這個帳戶內 ,我們給了一個值<- create Printer(width:5 , height:5)
:一個 Printer 資源和一個儲存它的唯一位置。我們在這裡定義了一個路徑/storage/ArtistPicturePrinter
,將儲存的物件分成兩部分:第一部分是域(domain),如我上面解釋過的,Flow 帳戶的域(domain) 只會有三種,在這邊我們用 storage(值的實際位置)。 - Link Function
在 Save Function,我們只是用合約在帳戶儲存了一個 Printer 資源實例 ,假設有人要在這個合約中的 Canvas 印出圖片,會需要合約帳戶的授權簽名,而事實上 Cadence 採用基於能力(capability-based)的訪問控制,允許智能合約將部分 storage 公開給其他帳戶,這時候我們會使用 Link Function。
我們先調用 link self.account.link<&Printer>
<&Printer>
在這邊指的是,我們定義這個 link 是什麼類型的引用,以及引用了什麼。在 Flow 上,我們可以為資源和結構創建引用,引用使我們能夠訪問資源物件的 fileds 和 functions。
我們首先先創建 capability 路徑 /public/ArtistPicturePrinter
ArtistPicturePrinter 是標識符(identifier),在這邊標識符名稱是存在 public 域,與 storage 域有相同名稱是可以的,因為它們來自不同的域。
target: storage/ArtistPicturePrinter
在這邊 target 參數,指的是一旦我們獲得一個 capability 後,我們將與之互動的實際資源/物件位置。
在剛剛的步驟,我們已經先用 save 和 link function 去創建了 Printer 資源 Capability。一般來說,我們該怎麼引用或借用呢?
通常我們可以這樣創建引用:
let name = badu
let nameRef: &String = &name as &String
或者可以借用 Capability:
在這個例子中,我們在這裡先宣告 printerRef 變數,並使用 getAccount()
來訪問公共帳戶 0x01,我們用getCapability
定義了特定的引用類型 <&Artist.Printer>
,接著用 borrow()
從中借用一個引用。如果沒有 panic,我們可以順利訪問該資源/物件的 filed 和 functions。
如果目標物件是空的、已經被借用,或者如果請求的引用類型超出了 允許的範圍,那就會顯示 Couldn’t borrow reference to Printer。
二、 跟 0x02 帳戶上的合約互動,創建 Capability 跟 Reference
接下來我們在 Transactions 處創建一筆新的 Transaction,把它命名為 Second Transaction。並開始撰寫與 0x02 帳戶 Artist 合約互動的 Transaction 程式碼,程式碼附在下面。
接著對 0x02 送出 Transaction,就會得到 “Picture Printed” 或是 “Picture with ______ already exsited” 的 log 結果。
解釋一下這個 transaction 程式碼,在這裡,我們希望引用 Artist 合約中的 Printer 資源,去畫出一張圖。首先我們先定義變數 pixels 和 picture。
在這裡 pitcture 變數我們用 @Artist.Picture
資源因為 picture 本身會來自於 Artist 智能合約,也只能在該智能合約範圍中使用。
- 準備階段(Prepare statement)
我們先用getAccount()
訪問 0x02 帳戶,我們用getCapability
定義了特定的引用類型<&Artist.Printer>
,接著用borrow()
從中借用一個引用。如果我們在這個公共地址下沒有找到 Printer 資源,我們會得到此 Capability 借用為空(nil) ,將會觸發 panic,並停止執行此 Transaction。如果沒有 panic,我們可以順利訪問該資源/物件的 filed 和 functions。 - 執行階段(Excute Statment)
我們輸出簡單的 log 語句,判斷 Picture 最後是否有順利被印出。最後我們必須處理我們的 picture 資源,在這邊我們還沒有地方可以儲存它,所以用了destroy self.picture
最後我們可以回去 0x02 帳戶的 Storage 看看,在 ArtistPicturePrinter 中,我們的 prints dictionary 確實包含了相對應 key 和 value。
下一篇,我會寫 Flow Emulator 實作,用 Flow Emulator 在 flow-cli 本地端託管 Flow 環境,創建帳戶、簽署交易和部署智能合約。Flow Emulator 的概念類似是你電腦上有一個完整 Flow 區塊鏈的小型本機端版本。