首頁  >  文章  >  從源碼角度看如何在TON Chain上創建一個NFT

從源碼角度看如何在TON Chain上創建一個NFT

WBOY
WBOY原創
2024-06-25 18:45:02727瀏覽

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

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

从源码角度看如何在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 Chain上创建一个NFT

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

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

    从源码角度看如何在TON Chain上创建一个NFT

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

    从源码角度看如何在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 Chain上创建一个NFT

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

    从源码角度看如何在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,即類似與 ERC721 中的 tokenId,以及由標準函數 my_address 傳回的目前合約地址,即為 collection_address,這個資料的順序與 nft-item 中的聲明保持一致。

    接下來一個知識點就是在TON 中,所有未生成的智能合約而可以預先計算其生成後的地址,這點與Solidity 中的create2 函數類似,在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 Chain上创建一个NFT

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

    从源码角度看如何在TON Chain上创建一个NFT

    TON 生態的 DApp 開發實在是件有趣的事情,與 EVM 的開發範式有很大差異,因此我會透過一系列文章來介紹如何在 TON Chain 中開發 DApp。

    以上是從源碼角度看如何在TON Chain上創建一個NFT的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述:
    本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn