Руководство по разработке проекта TON (часть 1): Как создать NFT на основе исходного кода в сети TON Chain

Автор: @Web3Mario (_mario)

Аннотация: Продолжая предыдущую статью о технических аспектах TON, в последнее время было уделено внимание изучению официальной документации по разработке TON. При изучении документов возникло ощущение, что материалы больше похожи на внутреннюю документацию для разработчиков, и для новичков может быть не очень удручающим. Поэтому я решил сформулировать серию статей о разработке проекта TON Chain на основе собственного опыта изучения, с надеждой, что они смогут помочь начинающим разработчикам быстрее освоиться с разработкой TON DApp. Если в статье содержатся ошибки, буду рад обратной связи и совместному обучению.

Какие различия между разработкой NFT в EVM и разработкой NFT на цепи TON

Выпуск FT или NFT обычно является самым основным требованием для разработчиков DApp. Поэтому я выбрал это в качестве точки входа для изучения. В первую очередь давайте рассмотрим разницу между созданием NFT в технологическом стеке EVM и на TON Chain. Основанный на EVM NFT обычно выбирает стандарт наследования ERC-721. NFT - это тип неделимого зашифрованного актива, каждый из которых имеет уникальную специфику. ERC-721 является универсальным шаблоном разработки для этого типа активов. Давайте посмотрим, какие функции должен реализовывать обычный контракт ERC721 и какую информацию он должен записывать. На следующей картинке представлен интерфейс ERC721. Как видно, в отличие от FT, в функции перевода необходимо указать идентификатор tokenId, а не количество. Этот tokenId является основным проявлением уникальности NFT-актива, конечно, для того, чтобы содержать больше свойств, обычно для каждого tokenId записывается метаданные, которые являются внешней ссылкой и содержат другие расширенные данные об этом NFT, например, ссылку на изображение PFP, некоторые имена свойств и т.д.

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

Для разработчиков, знакомых с Solidity или объектно-ориентированным программированием, создание такого смарт-контракта - это простое дело. Достаточно определить необходимые типы данных в контракте, такие как некоторые ключевые отображения (mapping), и разработать соответствующую логику изменения этих данных в соответствии с требуемыми функциональными возможностями, чтобы создать NFT.

Однако, всё это становится несколько иным на TON Chain, и есть две основные причины, объясняющие это.

  • Хранение данных в TON основано на ячейках, а ячейки одного и того же аккаунта реализованы с помощью ориентированного ациклического графика. Это означает, что данные, которые требуется хранить в течение длительного времени, не могут бесконечно расти безгранично, поскольку для ориентированного ациклического графа стоимость запросов определяется глубиной данных, и когда глубина бесконечно увеличивается, может возникнуть проблема высокой стоимости запросов, что приведет к блокировке контракта.
  • Для достижения высокой производительности параллельной работы TON отказалась от архитектуры последовательного выполнения и вместо этого использовала разработанную специально для параллельной работы парадигму разработки - модель акторов. Это привело к тому, что между смарт-контрактами может происходить только асинхронный вызов путем отправки так называемых внутренних сообщений. Обратите внимание, что как вызовы, изменяющие состояние, так и только для чтения, должны следовать этому принципу. Кроме того, необходимо тщательно рассмотреть, как обрабатывать откат данных в случае неудачи асинхронного вызова.

Конечно, в предыдущей статье были подробно рассмотрены другие отличия в техническом плане, а в этой статье мы хотели бы сосредоточиться на разработке смарт-контрактов, поэтому не будем раскрывать эту тему. Эти два принципа проектирования привели к значительным отличиям между разработкой смарт-контрактов в TON и EVM. В начале нашего изложения мы узнали, что для NFT контракта нужно определить отображающиеся отношения, то есть mapping, для сохранения данных, связанных с NFT. Самым важным из них является owners, которые хранят отображающиеся отношения между адресами владельцев NFT, соответствующих определенному tokenID, что определяет право собственности на NFT, а передача - это изменение этого права собственности. Поскольку это теоретический безграничный тип данных, нужно стараться избегать его. Поэтому официально рекомендуется использовать наличие безграничной структуры данных как критерий для фрагментации. То есть, когда существуют подобные потребности в хранении данных, их заменяют парадигмой главного и подчиненного контрактов, создавая дочерние контракты для управления данными каждого ключа. И через главный контракт управляют глобальными параметрами или помогают обрабатывать взаимодействие между дочерними контрактами.

Это также означает, что для NFT в TON необходимо использовать аналогичную архитектуру для проектирования, где каждый NFT представляет собой отдельный дочерний контракт, который содержит специальные данные, такие как адрес владельца, метаданные и т.д., и управляется главным контрактом, который содержит глобальные данные, такие как имя NFT, символ, общее количество и т.д.

После ясности в архитектуре необходимо решить потребности в основных функциях. Поскольку выбран такой подход с главным и дополнительным контрактами, необходимо определить, какие функции несет главный контракт, какие функции несет вспомогательный контракт, и какая внутренняя информация передается между ними, а также как выполнять откат данных при возникновении ошибок выполнения. Обычно перед разработкой сложных крупных проектов необходимо создать диаграмму классов и определить потоки информации между ними, а также тщательно обдумать логику отката при сбоях внутренних вызовов. Это необходимо даже для разработки NFT, хотя она и проще, чем разработка сложных проектов.

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

Изучение разработки умных контрактов TON из исходного кода

TON выбрал разработку языка программирования с типами данных, аналогичного языку C, под названием Func, для разработки языка смарт-контрактов. Теперь давайте изучим разработку смарт-контрактов TON из исходного кода. Я выбрал пример NFT из официальной документации TON для этого рассказа, и те, кто заинтересован, могут изучить его самостоятельно. В этом примере реализован простой пример TON NFT. Давайте посмотрим на структуру контракта, которая состоит из двух функциональных контрактов и трех необходимых библиотек.

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

Эти два основных функциональных контракта разработаны в соответствии с вышеуказанными принципами. В первую очередь давайте рассмотрим код основного контракта nft-collection:

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

Это вводит первый учебный материал, как сохранять данные в постоянном хранилище в TON смарт-контракте. Мы знаем, что в Solidity постоянное хранилище данных обрабатывается автоматически EVM в соответствии с типом параметра, и, как правило, переменные состояния смарт-контракта будут автоматически сохранены после выполнения в соответствии с последним значением, и разработчику не нужно беспокоиться об этом процессе. Но в Func это не так, разработчику нужно самостоятельно реализовать соответствующую логику обработки. Это немного похоже на то, что в C и C++ нужно учитывать процесс GC, но другие новые языки разработки обычно автоматизируют эту часть логики. Давайте посмотрим на код, сначала введем несколько необходимых библиотек, а затем увидим первую функцию load_data для чтения данных, которые были сохранены в постоянном хранилище, ее логика заключается в том, чтобы сначала вернуть хранилище ячейки постоянного смарт-контракта через get_data, обратите внимание, что это реализовано стандартной библиотекой stdlib.fc, и обычно некоторые из этих функций можно рассматривать как системные функции для использования.

Тип возвращаемого значения этой функции — cell, который является типом ячейки в TVM. Из предыдущего введения мы уже знаем, что все постоянные данные в TON Блокчейн хранятся в дереве ячеек. Каждая ячейка имеет не более лонг 1023 бит произвольных данных и длинные четыре ссылки на другие ячейки. Cell используется в качестве памяти в TVM на основе стека. Ячейка содержит строго закодированные данные, и в ордерах для получения конкретных данных в открытом тексте необходимо преобразовать ячейку в тип, называемый срезом. Ячейка может быть преобразована в тип среза с помощью функции begin_parse, а затем данные в ячейке могут быть получены путем загрузки битов данных и ссылок на другие ячейки из среза. Обратите внимание, что этот метод вызова в строке 15 кода является синтаксическим сахаром в func, который может напрямую вызывать вторую функцию возвращаемого значения первой функции. В конце соответствующие данные загружаются в ордер сохранения данных. Обратите внимание, что этот процесс отличается от solidity, он не основан на hashmap, поэтому ордер этого вызова не должен быть испорчен.

В функции save_data логика аналогична, за исключением того, что это обратный процесс, что вводит новый тип - builder, тип конструктора ячейки. Данные и ссылки на другие ячейки могут быть сохранены в конструкторе, а затем конструктор может быть преобразован в новую ячейку. Сначала создается builder с помощью стандартной функции begin_cell, затем связанные функции сохраняются с помощью функций store, обратите внимание, что порядок вызова в предыдущем тексте должен сохраняться. Наконец, новая ячейка строится с помощью функции end_cell, после чего эта ячейка управляется в памяти, и, наконец, с помощью внешней функции set_data можно завершить постоянное хранение этой ячейки.

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

Далее давайте посмотрим на связанные с бизнесом функции, сначала нужно представить одну ключевую точку, как создать новый контракт через контракт, что будет часто использоваться в только что представленной мастер-серверной архитектуре. Мы знаем, что в TON вызовы между смарт-контрактами осуществляются путем отправки внутренних сообщений. Это реализуется через функцию send_raw_message, где первый параметр - закодированная ячейка сообщения, а второй параметр - флаг, используемый для указания различий в способе выполнения транзакции. В TON установлены различные режимы отправки внутренних сообщений, на данный момент есть 3 режима Modes и 3 флага Flags. Можно комбинировать один режим с несколькими (возможно, нет) флагами, чтобы получить нужный режим. Комбинация просто означает заполнение их значений их суммы. Ниже приведена таблица описания Modes и Flags:

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

Итак, давайте рассмотрим первую основную функцию, deploy_nft_item. Как следует из названия, это функция для создания или, по сути, минтинга нового экземпляра NFT. После некоторых операций кодируется сообщение msg, которое отправляется внутреннему контракту через send_raw_message, и выбирается флаг 1 в качестве идентификатора отправки, при этом указывается только fee из кодировки в качестве gas fee для текущего выполнения. Исходя из предыдущего объяснения, легко понять, что эти правила кодирования соответствуют способу создания нового смарт-контракта. Давайте посмотрим, как это реализуется.

Давайте сразу перейдем к 51 строке. Два функции выше используются как вспомогательные для генерации информации, необходимой для создания сообщения. Поэтому мы рассмотрим их позже. Это процесс кодирования внутреннего сообщения для создания смарт-контракта. Некоторые цифры посередине на самом деле являются флагами, которые указывают на требования внутреннего сообщения. Здесь нам нужно ввести следующую тему: 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 и инициализирующие данные. Порядок кодирования данных должен соответствовать порядку хранения постоянных ячеек нового контракта. На строке 36 видно, что инициализирующие данные включают item_index, аналогичный tokenId в ERC721, и текущий адрес контракта, возвращаемый стандартной функцией my_address, то есть collection_address, порядок этих данных совпадает с объявлением в nft-item.

Далее одним из ключевых моментов в TON является возможность заранее вычислить адреса всех еще не созданных смарт-контрактов, что аналогично функции create2 в Solidity. Генерация нового адреса в TON состоит из двух частей: бит идентификации рабочей цепи и хеш-значения stateinit, причем первый, как мы уже знаем из предыдущего обзора, должен быть указан для соответствия бесконечной архитектуре разделения TON, и в настоящее время является универсальным значением. Он получается из стандартной функции workchain. Второе значение получается из стандартной функции cell_hash. Таким образом, возвращаясь к данному примеру, функция calculate_nft_item_address представляет собой функцию предварительного вычисления нового адреса контракта. Значение, вычисленное на 53-й строке, кодируется в сообщение в качестве адреса получателя данного внутреннего сообщения. А nft_content соответствует вызову инициализации создаваемого контракта, конкретная реализация которого будет представлена в следующей статье.

Что касается send_royalty_params, это должен быть ответ на внутреннее сообщение для определенного запроса только для чтения. Как мы уже упоминали ранее, в TON внутренние сообщения не только содержат операции, которые могут изменять данные, но также операции только для чтения также реализуются таким образом. Поэтому этот контракт относится к таким операциям. Во-первых, стоит обратить внимание на строку 67, которая представляет собой маркер обратного вызова функции запроса после выполнения. Запишите это и верните данные, которые были запрошены, а именно индекс элемента запроса и соответствующие данные royalty.

Далее давайте введем еще одну точку знания, в TON умные контракты имеют только два унифицированных входа, названных recv_internal и recv_external, причем первый является унифицированным вызовом для всех внутренних сообщений, а второй - для всех внешних сообщений, разработчики должны в функции в зависимости от потребностей использовать подобный switch-способ для реагирования на различные запросы, здесь флагом является флаг функции отката в строке 67. Возвращаясь к этому примеру, сначала проверяем пустое сообщение, затем разбираем информацию из сообщения, сначала в строке 83 разбираем sender_address, этот параметр будет использоваться для последующей проверки разрешений, обратите внимание на оператор ~ здесь, это еще один сахар синтаксиса. Здесь пока не будем раскрывать. Затем разбираем флаг операции op, а затем в зависимости от различных флагов обрабатываем соответствующие запросы. В том числе вызываются функции, упомянутые выше. Например, отвечая на запросы параметров royalty или майнтинга новых nft и автоматически увеличивая глобальный индекс.

Следующая точка знания соответствует 108 строке, вероятно, каждый может понять логику обработки этой функции через именование. Аналогично функции require в Solidity, в Func исключение выбрасывается через стандартную функцию throw_unless, где первый аргумент - это код ошибки, а второй - логическое значение проверки; если значение false, то выбрасывается исключение с этим кодом ошибки. А в этой строке с помощью equal_slices проверяется, равен ли sender_address, разобранный выше, адресу owner_address, хранящемуся в постоянной памяти этого контракта, для проверки разрешений.

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

Наконец, чтобы сделать структуру кода более понятной, был начат ряд вспомогательных функций для получения постоянной информации. Здесь не будем раскрывать их подробности, разработчики могут использовать эту структуру для разработки собственного смарт-контракта.

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

Разработка DApp в экосистеме TON действительно интересное занятие, которое существенно отличается от парадигмы разработки EVM, поэтому я собираюсь рассказать о том, как разрабатывать DApp в сети TON через серию статей. Давайте вместе изучать это и использовать этот шанс. Я также приглашаю вас взаимодействовать со мной в Twitter и выдвигать новые интересные идеи для dapp, чтобы вместе развиваться.

TON0.13%
Посмотреть Оригинал
На этой странице может содержаться сторонний контент, который предоставляется исключительно в информационных целях (не в качестве заявлений/гарантий) и не должен рассматриваться как поддержка взглядов компании Gate или как финансовый или профессиональный совет. Подробности смотрите в разделе «Отказ от ответственности» .
  • Награда
  • комментарий
  • Репост
  • Поделиться
комментарий
0/400
Нет комментариев
  • Закрепить