首页  >  文章  >  TON 项目开发教程(一):源码角度看如何在 TON Chain 上创建一个NFT

TON 项目开发教程(一):源码角度看如何在 TON Chain 上创建一个NFT

王林
王林原创
2024-06-25 07:58:39984浏览

Author: @Web3Mario(https://x.com/web3_mario)

Abstract: Following the previous article about TON Technical introduction Article, I have been studying the TON official development documents in depth during this period, and I feel that there are still some barriers to learning. The current document content seems to be more like an internal development document, which is not very friendly to new developers. Therefore, I try to sort out a series of articles about TON Chain project development based on my own learning trajectory. I hope it will be helpful for everyone to quickly get started with TON DApp development. If there are any errors in the writing, you are welcome to correct me and learn together.

What are the differences between

developing NFT in EVM and developing NFT on TON Chain

Issuing one FT or NFT It is usually the most basic need for DApp developers. So I also use this as an entry point for learning. First, let us understand the following differences between developing an NFT in the EVM technology stack and in TON Chain.基於EVMNFT通常會選擇繼承ERC-721的標準。所謂NFT,指的是不可分割的加密資產類型,且每個資產具有唯一性,即存在某些專屬的特性。而ERC-721就是對這個類型的資產的一種通用的開發範式。讓我們來看看一個常見的ERC721合約需要實現哪些函數以及記錄哪些資訊。下圖是一個ERC721介面。可以看到與FT不同,在轉帳介面中需要輸入的是待轉帳的tokenId而非數量。這個tokenId也是NFT資產唯一性最基本的體現,當然為了承載更多的屬性,通常會為每個tokenId記錄一個 是一個外部鏈接,保存了該NFT的其他可擴展數據,例如一張PFP圖片的鏈接,某些屬性名稱等。

對於熟悉

TON 项目开发教程(一):源码角度看如何在 TON Chain 上创建一个NFTSolidity

或者熟悉面向對象的開發者來說,實現這樣一個智能合約是件容易的事,只要定義好合約中需要的映射關係mapping,並根據所需功能開發相應的對這些數據的修改邏輯,即可實現一個NFT然而在TON Chain

中這一切變的不太相同,造成不同的核心原因有兩個:

  • TON中数据的存储是基于Cell实现的,而同一个账户的Cell通过有向无环图来实现。这样就导致需要之久化存储的数据不能无边界的增长下去,因为一个有向无环图来说,数据深度决定的查询成本,当深度无限延伸之后,有可能造成查询成本过高,从而导致合约陷入死锁问题。
  • 为了追求高并发性能,TON舍弃了串行执行的架构,转而采用了一个专为并行而生的开发范式,Actor模型,来重构执行环境。这就造成了一个影响,智能合约之间只能通过发送所谓内部消息的方式异步调用,注意无论是状态修改类型或只读类型的调用都需要遵循这个原则,除此之外,也需要仔细考虑异步调用若失败,如何处理数据回滚的问题。

当然关于技术上其他不同点在上一篇文章中有过详细的论述,本篇文章希望可以聚焦在智能合约开发上,所以不展开讨论。上述两条设计原则让TON中智能合约开发与EVM产生了很大区别。在開始的論述中,我們知道一個NFT合約中需要定義一些映射關係,也就是mapping,來保存NFT相關的數據。其中最重要的是owners,這個mapping儲存了某個tokenID對應的NFT的擁有者地址的映射關係,決定了地址的對應關係就是對該所有權的修改。由於理論上這是一個可以沒有邊界的資料結構,需要盡量避免。因此官方推薦以是否存在無邊界資料結構作為分片的標準。即當有類似的資料儲存需求時,透過主從合約的範式來替代,透過建立子合約的方式來管理每個key對應的資料。並透過主合約管理全域參數,或協助處理子合約之間的內部資訊互動。

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

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

TON 项目开发教程(一):源码角度看如何在 TON Chain 上创建一个NFT

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

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

nft-collection

TON 项目开发教程(一):源码角度看如何在 TON Chain 上创建一个NFT

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

该函数的返回值类型为cell,这是 TVM中的cell类型。在之前的介绍中,我们已经知道TON 区块链中的所有持久数据都存储在cell树中。每个cell最多有 1023 位任意数据和最多四个对其他cell的引用。cell在基于堆栈的 TVM 中用作内存。cell中保存的是緊編碼後的數據,要想獲取其中具體的明文數據,需要將cell轉換為被稱為slice的類型。 cell可以透過begin_parse函數轉換成為slice型,然後可以透過從slices來取得的載入資料位和其他對資料位載入🜎 中的數據。注意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种消息Modes3种消息Flags。可以将单一Mode与多个(也许没有)标志组合以获得所需的mode。组合只是意味着它们值的和填入即可。下面给出了ModesFlags的描述表格:

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合約,而這主要是透過556三行指定的。首先

55行这一大串数字是一系列标识位,注意store_uint的第一个入参是数值,第二个是位长,其中决定了该内部消息是合约创建的是后三个标记位,以及相应二进制值位为111(十进制即为4+2+1),其中前两个表示该消息将附带StateInit数据,这个数据即为新合约的源码,以及初始化所需的数据。而后一个标记位表示内部消息附载,即希望执行相关逻辑以及需要的参数。因此你会看到在第66行代码并没有设置该三位数据,则表明的是一次对已部署合约的函数调用。具体的编码规则在这里查看。

那么StateInit的编码规则即对应了49行代码,通过calculate_nft_item_state_init计算,注意stateinit数据的编码也遵循了一种既定的TL-B编码规则,除了一些标记位之外,主要涉及到两部分新合约code和以及初始化datadata的编码顺序需要与新合约指定的持久化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_internalrecv_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的開發典範 中開發DApp。與大家共同學習,把握這波機會。也歡迎大家在twitter上與我互動,碰撞一些新的有趣的dapp idea,一起開發。

以上是TON 项目开发教程(一):源码角度看如何在 TON Chain 上创建一个NFT的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn