TON專案開發教學(一):原始碼角度看如何在TON Chain上建立一個NFT

本文約4446字,閱讀全文需要約6分鐘
目前TON官方文件學習起來有一些門檻,因此試著以自己的學習軌跡,梳理一系列關於TON Chain項目開發的文章,希望可以對大家快速入門TON DApp開發有一些幫助。

原文作者:@ Web3 Mario (https://x.com/web3_mario)

承接上一篇關於TON 技術介紹的文章,這段時間深入研究了一下TON 官方開發文檔,感覺學習起來還是有些門檻,當前的文檔內容似乎更像是一個內部開發文檔,對新入門的開發者來說不太友好,因此試著以自己的學習軌跡,梳理一系列關於TON Chain 專案開發的文章,希望可以對大家快速入門TON DApp 開發有一些幫助。行文有誤也歡迎大家指正,一起學習。

在EVM 中開發NFT 和在TON Chain 上開發NFT 有哪些不同

發行一個FT 或NFT 對於DApp 開發者來說通常是最基本的需求。因此我也以此作為學習入口。首先讓我們來了解以下在EVM 技術堆疊中開發一個NFT 和在TON Chain 中的差異。基於EVM 的NFT 通常會選擇繼承ERC-721 的標準。所謂NFT,指的是不可分割的加密資產類型,且每個資產具有唯一性,即存在某些專屬的特性。而ERC-721 就是這個類型的資產的一種通用的開發範式。讓我們來看一個常見的ERC 721 合約需要實作哪些函數以及記錄哪些資訊。下圖是一個ERC 721 介面。可以看到與FT 不同,在轉帳介面中需要輸入的是待轉帳的tokenId 而非數量。這個tokenId 也是NFT 資產唯一性最基本的體現,當然為了承載更多的屬性,通常會為每個tokenId 記錄一個metadata,這個metadata 是一個外部鏈接,保存了該NFT 的其他可擴展數據,例如一張PFP 圖片的鏈接,某些屬性名稱等。

TON專案開發教學(一):原始碼角度看如何在TON Chain上建立一個NFT

對於熟悉Solidity 或熟悉物件導向的開發者來說,實現這樣一個智能合約是件容易的事,只要定義好合約中需要的資料類型,例如一些關鍵的映射關係mapping,並根據所需功能開發相應的對這些資料的修改邏輯,即可實作一個NFT。

然而在TON Chain 中這一切變的不太相同,造成不同的核心原因有二:

  • 在TON 中資料的儲存是基於Cell 實現的,而同一個帳戶的Cell 透過有向無環圖來實現。這樣就導致需要之久化儲存的資料不能無邊界的成長下去,因為一個有向無環圖來說,資料深度決定的查詢成本,當深度無限延伸之後,有可能造成查詢成本過高,從而導致合約陷入死鎖問題。

  • 為了追求高並發性能,TON 捨棄了串行執行的架構,轉而採用了一個專為並行而生的開發範式,Actor 模型,來重建執行環境。這就造成了一個影響,智能合約之間只能透過發送所謂內部訊息的方式非同步調用,注意無論是狀態修改類型或只讀類型的調用都需要遵循這個原則,除此之外,也需要仔細考慮非同步呼叫若失敗,如何處理資料回滾的問題。

當然關於技術上其他不同點在上一篇文章中有過詳細的論述,本篇文章希望可以聚焦在智能合約開發上,所以不展開討論。上述兩條設計原則讓TON 中智能合約開發與EVM 產生了很大差異。在開始的論述中,我們知道一個NFT 合約中需要定義一些映射關係,也就是mapping,來保存NFT 相關的資料。其中最重要的就是owners,這個mapping 儲存了某個tokenID 對應的NFT 的擁有者地址的映射關係,決定了NFT 的所有權,轉帳就是對該所有權的修改。由於理論上這是一個可以沒有邊界的資料結構,需要盡量避免。因此官方推薦以是否存在無邊界資料結構作為分片的標準。即當有類似的資料儲存需求時,透過主從合約的範式來替代,透過建立子合約的方式來管理每個key 對應的資料。並透過主合約管理全域參數,或協助處理子合約之間的內部資訊互動。

這也意味著在TON 中的NFT 也需要採用類似的架構來設計,每個NFT 都是一個獨立的子合約,保存了諸如所有者地址,metadata 等專屬數據,並透過一個主合約來管理全局數據,例如NFT name,symbol,總供應量等。

在明確了架構後,接下來就需要解決核心功能的需求了,由於採用了這個主從合約的方式,因此就需要明確哪些功能由主合約承載,哪些功能由子合約承載,並且兩者之間通過什麼內部資訊溝通,同時當出現執行錯誤時,如何回溯先前的資料。通常情況下,在開發複雜的大型專案之前,透過一個類別圖並明確彼此之間的資訊流,並仔細思考內部呼叫失敗後的回滾邏輯是必要的,當然上述NFT 開發雖然簡單,但也可以做類似驗證。

TON專案開發教學(一):原始碼角度看如何在TON Chain上建立一個NFT

從源碼學習開發TON 智能合約

TON 選擇了設計一種類C 語言的、靜態類型語言,名為Func 來作為智能合約開發語言,那麼接下來就讓我們從源碼來學習如何開發TON 智能合約,我選擇了TON 官方文檔中的NFT 示例來進行介紹,有興趣的小夥伴可以自行去查閱。在這個case 中實作了一個簡單的TON NFT 例。讓我們來看看合約結構,共分為兩個功能合約以及三個必要的庫。

TON專案開發教學(一):原始碼角度看如何在TON Chain上建立一個NFT

這兩個主要的功能合約即按照上述的原則進行設計,首先讓我們來看下主合約nft-collection 的代碼:

TON專案開發教學(一):原始碼角度看如何在TON Chain上建立一個NFT

這引入了第一個知識點,如何在TON 智能合約中持久化存儲數據,我們知道在Solidity 中數據的持久化存儲是由EVM 根據參數的類型自動處理的,通常情況下,智能合約的狀態變量將在執行結束後根據最新值自動被持久化存儲,開發者並不需要考慮這個過程。但在Func 中並非如此,開發者需要自己實現對應的處理邏輯,而這個情況有點類似C 和C++ 需要考慮GC 的流程,但其他新的開發語言通常會將這部分邏輯自動化處理。讓我們來看看程式碼,首先引入一些需要的函式庫,然後看到第一個函數load_data 用於讀取被持久化儲存的數據,其邏輯為首先透過get_data 傳回持久化合約儲存cell,注意這是由標準庫stdlib.fc 實作的,通常情況下可以將其中的一些函數視為系統函數來使用。

此函數的傳回值類型為cell,這是TVM 中的cell 類型。在先前的介紹中,我們已經知道TON 區塊鏈中的所有持久性資料都儲存在cell 樹中。每個cell 最多有1023 位元任意資料和最多四個對其他cell 的引用。 cell 在基於堆疊的TVM 中用作記憶體。 cell 中保存的是緊編碼後的數據,要獲取其中具體的明文數據,需要將cell 轉換為被稱為slice 的類型。 cell 可以透過begin_parse 函數轉換成為slice 類型,然後可以透過從slice 載入資料位元和對其他cell 的參考來獲得cell 中的資料。注意15 行程式碼中的這種呼叫方法是一個func 中的語法糖,可以直接呼叫第一個函數的回傳值的第二個函數。並在最後依照資料持久化順序依序載入對應的資料。注意這個過程和solidity 不同,並不是根據hashmap 調用,所以這個調用順序不能亂。

在save_data 函數中,邏輯與之類似,只不過這是一個反向的過程,這就引入了下一個知識點,一個新的類型builder,這是cell 建構器的類型。資料位元和對其他cell 的引用可以儲存在建構器中,然後建構器可以最終化為新cell。首先透過標準函數begin_cell 建立一個builder,並依序透過store 相關函數儲存相關函數,注意上文中呼叫順序與此處儲存順序需要保持一致。最後透過end_cell 完成新cell 構建,這時該cell 會管理在記憶體中,最後透過最外層的set_data,就可以完成對該cell 的持久化儲存。

TON專案開發教學(一):原始碼角度看如何在TON Chain上建立一個NFT

接下來讓我們來看下業務相關函數,首先需要先介紹下一個知識點,如何透過合約創建一個新的合約,這將在剛剛介紹的主從架構中經常被使用。我們知道在TON 中,智能合約之間的呼叫是透過發送內部訊息的方式來實現的。這是透過一個名為send_raw_message 來實現的,注意第一個參數是message 編碼後的cell,第二個參數是標識位,用於表明該交易的執行方式的區別,在TON 中設置了不同的內部訊息發送的執行方式,目前有3 種訊息Modes 和3 種訊息Flags。可以將單一Mode 與多個(也許沒有)標誌組合以獲得所需的mode。組合只是意味著將它們值的和填入即可。下面給出了Modes 和Flags 的描述表格:

TON專案開發教學(一):原始碼角度看如何在TON Chain上建立一個NFT

那麼讓我們來看第一個主要函數,deploy_nft_item,顧名思義,這是一個用於創建或者說鑄造新NFT 實例的函數,經過一番操作編碼一個msg 後,通過send_raw_message 發送該內部合約,並選擇了flag 1 的發送標識位,僅將編碼中指定的fee 作為本次執行的gas fee。經過上文的介紹我們很容易意識到,這個編碼規則應該是對應創建一個新的智能合約的方式。那讓我們來看看具體是怎麼實現的。

讓我們直接看51 行,上面兩個函數是用於生成message 所需資訊的輔助函數,因此我們後面再來看,這是一個用於創建智能合約的內部消息的編碼過程,中間的一些數字其實也是一些標識位,用於說明該內部訊息的需求,這裡要引入下一個知識點,TON 選擇了一種名為TL-B 的二進位語言來描述訊息的執行方式,並且根據設定不同的標記位來實現某些特定功能的內部訊息,最容易想到的兩個使用場景,新合約建立和已部署合約函數呼叫。而51 行的這種方式即對應了前者,創建一個新的nft item 合約,而這主要是透過55 , 56 , 57 三行指定的。首先55 行這一大串數字是一系列標識位,注意store_uint 的第一個入參是數值,第二個是位長,其中決定了該內部訊息是合約創建的是後三個標記位,以及相應二進位值位元為111 (十進位即為4+ 2+ 1),其中前兩個表示該訊息將附帶StateInit 數據,這個數據即為新合約的源碼,以及初始化所需的數據。而後一個標記位表示內部訊息附載,即希望執行相關邏輯以及所需的參數。因此你會看到在第66 行程式碼並沒有設定該三位數據,則表示的是一次對已部署合約的函數呼叫。具體的編碼規則在這裡查看。

那麼StateInit 的編碼規則即對應了49 行程式碼,透過calculate_nft_item_state_init 計算,注意stateinit 資料的編碼也遵循了一種既定的TL-B 編碼規則,除了一些標記位之外,主要涉及兩部分新合約code 和以及初始化data。 data 的編碼順序需要與新合約指定的持久化cell 的儲存順序保持一致。在36 行可以看到,初始化資料有item_index,也就是類似與ERC 721 中的tokenId,以及由標準函數my_address 傳回的目前合約位址,即為collection_address,這個資料的順序與nft-item 中的宣告一致。

接下來一個知識點就是在TON 中,所有未生成的智能合約而可以預先計算其生成後的地址,這點與Solidity 中的create 2 函數類似,在TON 中新地址的生成由兩部分組成,workchain識別位元與stateinit 的雜湊值拼接而成,前者在先前的介紹中我們已經知道是為了對應TON 無限分片架構而需要被指定的,目前為統一值。由標準函數workchain 獲得。後者由標準函數cell_hash 獲得。因此回到這個例子,calculate_nft_item_address 即為預先計算新合約位址的函數。並將生成值在第53 行編碼到message 中,作為該內部訊息的接收位址。而nft_content 則對應了對被建立合約的初始化調用,具體的實作在下一篇文章中介紹。

至於send_royalty_params,則需要是對某隻讀請求的內部訊息的相應,在先前的介紹中,我們特意強調了在TON 中內部訊息不光包含可能會修改資料的操作,只讀操作也需要透過這種方式實現,因此該合約即為此類操作,首先值得注意的是67 行表示響應該請求後對請求者回調函數的標記,記下來即為返回的數據,分別是請求的item index,以及相應的royalty數據。

接下來讓我們引入下一個知識點,TON 中智能合約只有兩個統一的入口,名為recv_internal 和recv_external,其中前者為所有內部消息的統一調用入口,後者為所有外部消息的統一調用入口,開發者需要在函數內部根據需求,採用類似switch 的方式根據message 指定的不同標記位元來回應不同的請求,這裡的標記位元即為上述67 行的回呼函數標記。回到該例子,首先對message 進行空位檢查,通過後分別解析message 中的信息,首先在83 行解析獲得sender_address,該參數將用於後續的權限檢查,注意這裡的~操作符,屬於另一個語法糖。這裡先不展開將。接下來解析op 操作標記位,而後面依不同的標記位,分別處理對應請求。其中即根據某些邏輯分別呼叫了上述的函數。例如回應對royalty 參數的請求,或鑄造新的nft,並自增全域index。

接下來一個知識點對應了108 行,想必大家透過命名也可以知道該函數的處理邏輯,與Solidity 中的require 函數類似,Func 中透過標準函數throw_unless 來拋出異常,第一個入參為錯誤碼,第二個是檢查位元布林值,若位false 則拋出異常,並附帶該錯誤碼。而在這行中透過equal_slices 來判斷上面解析到的sender_address 是否等於該合約持久化儲存的owner_address,做權限判斷。

TON專案開發教學(一):原始碼角度看如何在TON Chain上建立一個NFT

最後為了讓程式碼結構更清晰,開始閒了一系列幫助獲取持久化資訊的輔助函數,在這裡就不展開介紹了,開發者可以參考這種結構來開發自己的智能合約。

TON專案開發教學(一):原始碼角度看如何在TON Chain上建立一個NFT

TON 生態的DApp 開發實在是件有趣的事情,與EVM 的開發範式有很大差異,因此我會透過一系列文章來介紹如何在TON Chain 中開發DApp。與大家共同學習,把握這波機會。也歡迎大家在twitter 上與我互動,碰撞一些新的有趣的dapp idea,一起開發。

原創文章,作者:马里奥看Web3。轉載/內容合作/尋求報導請聯系 report@odaily.email;違規轉載法律必究。

ODAILY提醒,請廣大讀者樹立正確的貨幣觀念和投資理念,理性看待區塊鏈,切實提高風險意識; 對發現的違法犯罪線索,可積極向有關部門舉報反映。

推薦閱讀
星球精選