Author: @Web3Mario
Abstract: 前回のTON技術紹介の記事に引き続き、TON公式開発ドキュメントを深く勉強しているのですが、まだまだ学習の敷居が高いと感じており、現在のドキュメントの内容はどちらかというと内部開発ドキュメントに近く、新規開発者にはあまり優しくないようなので、私自身の学習の軌跡を整理してみようと思います。そこで、私自身の学習軌跡をもとに、TONチェインプロジェクト開発に関する一連の記事を整理してみることで、TON DApp開発を素早く始めるための一助になればと思います。もし間違いがあれば、ぜひ訂正して一緒に勉強しましょう。
EVMでNFTを開発することと、TONチェーンでNFTを開発することの違いは何ですか
FTまたはNFTをリリースすることは、通常、DApp開発者にとって最も基本的な要件です。ですから、私はこれを学習曲線として使っています。まず、EVM技術スタックとTONチェーンでNFTを開発する場合の以下の違いを理解しましょう。EVMベースのNFTは通常、ERC-721標準を継承することを選択します。NFTは分割不可能な暗号資産タイプであり、各資産は固有のもの、つまりその資産にしかない特徴があります。ERC-721はこの種の資産に共通する開発パラダイムです。どのような機能を実装し、どのような情報を共通のERC721コントラクトに記録する必要があるのか見てみましょう。下図はERC721のインターフェースです。FTとは異なり、送金インターフェースでは数量ではなく、送金するトークンのtokenIdを入力する必要があることがわかります。このtokenIdはNFTアセットの一意性を示す最も基本的な表現でもありますが、もちろん、より多くの属性を持ち運ぶために、通常、各tokenIdにはメタデータが記録されています。メタデータは、NFTに関する他の拡張可能なデータへの外部リンクであり、例えば、PFP画像へのリンク、属性名などです。

Solidityやオブジェクト指向に慣れている開発者にとっては、このようなスマートコントラクトを実装するのは簡単です。
しかし、TONチェーンでは、これはまったく同じことではありません。paddingleft-2">
TONでは、データの保存はセルに基づいており、同じアカウントのセルは有向無サイクルグラフを通して実現されます。有向非周期グラフは、クエリのコストを決定するデータの深さは、クエリのコストの無限拡張の深さが高すぎる場合、デッドロック問題に契約につながるので、これは、データの持続的なストレージの必要性につながる無期限に成長することはできません。
高い並行処理性能を追求するため、TONはシリアル実行アーキテクチャを放棄し、代わりに並列処理のために作られた開発パラダイム、アクターモデルを採用して実行環境を再構成しました。これにより、スマートコントラクトは、いわゆる内部メッセージを相互に送信することによってのみ非同期に呼び出すことができるようになり、状態変更呼び出しと読み取り専用呼び出しの両方がこの原則に従う必要があることに注意し、これに加えて、非同期呼び出しが失敗した場合のデータロールバックの処理方法についても慎重に検討する必要があります。
もちろん、他の技術的な違いについては前の投稿で詳しく説明しましたが、この投稿ではスマートコントラクトの開発に焦点を当てません。上記の2つの設計原則により、TONでのスマートコントラクト開発はEVMとは大きく異なります。はじめに、NFTコントラクトはNFT関連データを格納するためにいくつかのマッピング関係(マッピング)を定義する必要があることが分かっています。最も重要なのは所有者で、このマッピングはトークンIDに対応するNFTの所有者アドレスのマッピングを格納し、NFTの所有権を決定します。このマッピングは理論上制約のないデータ構造であるため、可能な限り避けるべきです。そのため、公式にはボーダレスなデータ構造の存在をシャーディングの基準とすることが推奨されている。つまり、同様のデータ保存ニーズがある場合、マスター・スレーブ契約のパラダイムは、各キーに対応するデータを管理するサブ契約を作成することで置き換えられる。そして、マスター契約を通じてグローバルパラメーターを管理したり、サブ契約間の内部情報の相互作用を処理するのに役立てたりする。
これはまた、TONのNFTが同様のアーキテクチャで設計される必要があることを意味します。各NFTは、所有者アドレス、メタデータなどの専有データを保持する個別のサブ契約であり、マスター契約を通じて、NFT名、シンボル、総供給量などのグローバルデータを管理します。
アーキテクチャを定義したら、次はコア機能の要件に取り組みます。 このマスタースレーブコントラクトアプローチのため、どの機能をマスターコントラクトが担い、どの機能をサブコントラクトが担うのか、また、両者間の通信にどのような内部情報を使用するのか、さらに、実行エラーが発生した場合に以前のデータをロールバックする方法などを定義する必要があります。通常、大規模で複雑なプロジェクトを開発する前には、クラス図を見ながら、それらの間の情報の流れを定義し、内部呼び出しが失敗した場合のロールバックロジックについて慎重に考える必要がありますが、上記のNFTの開発も、単純ではありますが、同様に検証することができます。

ソースコードからTONスマートコントラクトの開発を学ぶ
ソースコードからTONスマートコントラクトの開発を学ぶ
TONはスマートコントラクトの開発言語として、Funcと呼ばれるC言語、静的型付け言語のクラスを設計することを選択しましたので、ソースコードからTONスマートコントラクトを開発する方法を学びましょう。今回は、シンプルなTON NFTの例を実装しています。2つの機能契約と3つの必要なライブラリに分かれている契約の構造を見てみましょう。

2つの主な機能コントラクトは、上記の原則に従って設計されています。まず、主なコントラクトであるnft-collectionのコードを見てみましょう。collectionコード:

TON スマートコントラクトでデータを永続的に保存する方法という、最初の知識を紹介します。通常、スマートコントラクトの状態変数は、実行終了時に最新の値に基づいて自動的に永続化されるため、開発者はこのプロセスについて考える必要はありません。これは、CやC++がGC処理を考慮する必要があるのと少し似ていますが、他の新しい開発言語では通常、この部分のロジックは自動化されています。コードを見てみましょう。まず、必要なライブラリをいくつか紹介し、最初の関数load_dataは永続化されるデータを読み込むために使用されます。ロジックは、最初にget_dataを使用して永続契約ストレージセルを返すことです。これは標準ライブラリstdlib.fcによって実装されていることに注意してください。
この関数の戻り値はセル型で、TVMのセル型である。前回の紹介でわかったように、TONブロックチェーンのすべての永続データはセルツリーに格納されている。各セルは最大1023ビットの任意のデータと、他のセルへの最大4つの参照を持つ。cellはスタックベースのTVMでメモリとして使用される。cellは厳重にエンコードされたデータを保持し、その中の特定の平文データを取得するには、cellをsliceと呼ばれる型に変換する必要がある。cellはbegin_parse関数によってslice型に変換することができる。begin_parse関数でセルをスライス型に変換し、スライスからビットをロードして他のセルを参照することで、セル内のデータを得ることができる。この15行目の呼び出しは、最初の関数の戻り値から2番目の関数を直接呼び出す func の構文上の糖分であることに注意。そして最後には、データが永続化された順にロードされる。この処理は、solidityとは異なり、ハッシュマップに基づいて呼び出されるわけではないので、この呼び出しの順序を台無しにすることはできないことに注意してください。
save_data関数では、逆の処理であることを除けば、ロジックは同様で、次のポイントであるセルビルダーの新しいタイプにつながる。データ・ビットや他のセルへの参照はビルダーに格納することができ、それを新しいセルに確定することができる。まず標準関数 begin_cell を使ってビルダーを作成し、次に store 関連関数を使って関連する関数を格納する。上記の呼び出しの順番は、ここでの格納の順番と一致させる必要があることに注意。最後に、end_cellで新しいセルの構築が完了し、メモリ上で管理される。そして、一番外側のset_dataでセルの永続的な保存が完了する。

次に、ビジネス関連の機能を見ていきましょう。まず、次の知識を紹介する必要があります。これは、先ほど紹介したマスター・スレーブ・アーキテクチャで頻繁に使用されます。TONでは、スマート・コントラクト間の呼び出しは内部メッセージの送信によって行われることがわかっている。これは send_raw_message と呼ばれるメソッドで実行される。 第 1 引数はセルとしてエンコードされたメッセージで、第 2 引数はトランザクションの実行方法の違いを示す識別子ビットであること、そして TON で送信される内部メッセージには異なる実行方法があり、現在 3 つのメッセージ Modes と 3 つのメッセージ Flags があることに注意。単一のモードといくつかのフラグ(あるいはフラグなし)を組み合わせて、希望するモードにすることが可能である。モードとフラグを説明する表を以下に示します。

では、最初のメイン関数であるdeploy_itemを見てみましょう。nft_itemは、その名前が示すように、新しいNFTインスタンスを作成(またはキャスト)するための関数です。いくつかの操作でmsgをエンコードした後、send_raw_messageを介して内部コントラクトを送信します。このエンコーディング・ルールが、新しいスマート・コントラクトの作成方法に対応するものであることに気づくのは簡単だ。では、その方法を見てみよう。
51行目を直接見てみましょう。 上の2つの関数は、メッセージに必要な情報を生成するヘルパー関数なので、後で見てみましょう。 これは、スマートコントラクトを作成するために使われる内部メッセージをエンコードする処理で、真ん中の数字のいくつかは、実際には内部メッセージの必要性を示す識別子ビットです。TONは、メッセージの実行を記述するためにTL-Bと呼ばれるバイナリ言語を選択し、内部メッセージのいくつかの特定の機能を実現するために異なるマーキングビットの設定に応じて、最も簡単に2つのシナリオ、新しいコントラクトの作成とコントラクト関数の呼び出しの展開を考える。51行目のアプローチは前者、つまり55、56、57行目で指定されている新しいnft項目コントラクトの作成に対応する。まず、55行目の一連の数値は、識別子のビット列です。store_uintへの最初の入力は数値であり、2番目はビット長であることに注意してください。これは、内部メッセージが識別子の最後の3ビットによって作成されたコントラクトであることを決定するとともに、対応する2進数値111(10進数では4+2+1)であり、その最初の2ビットは、新しいコントラクトのソースコードであるStateInitデータと、コントラクトの初期化を伴うメッセージであることを示しています。最初の2ビットは、メッセージがStateInitデータを伴うことを示す。StateInitデータは、新しいコントラクトのソースコードであり、初期化に必要なデータである。最後の2ビットは、メッセージがStateInitデータを伴うことを示している。これは、新しいコントラクトのソースコードであり、初期化に必要なデータである。つまり、66行目のコードは3ビットのデータをセットしているのではなく、デプロイされたコントラクトへの関数コールを示していることがわかります。具体的なコーディングルールはこちらをご覧ください。
したがって、StateInitのエンコーディング規則は、calculate_nft_item_state_initによって計算されたコードの49行目に対応し、stateinitデータのエンコーディングも、確立されたTL-Bエンコーディング規則に従っていることに注意してください。初期化data.dataは、新しい契約によって指定された永続セルと同じ順序でエンコードされる必要がある。36行目を見てわかるように、初期化データはitem_indexで、これはERC721のtokenIdに似ており、標準関数my_addressによって返される現在のコントラクトのアドレスはcollection_addressであり、このデータの順序はnft-itemの宣言と一致している。
次に知っておくべき点は、TONでは、生成されていないスマートコントラクトはすべて、生成後のアドレスを事前に計算できるということです。これは、Solidityのcreate2関数に似ており、新しいアドレスの生成は、ワークチェーン識別子ビットとstateinitのハッシュ値という2つの部分から構成されます。前者は、前回紹介したように、TON無限スライシングアーキテクチャのために指定する必要があり、現在は一律の値です。これは標準関数workchainから得られる。後者は標準関数cell_hashから得られる。つまり、例に戻ると、calculate_nft_item_addressは新しいコントラクトのアドレスを事前に計算する関数である。結果の値は 53 行目のメッセージにエンコードされ、内部メッセージの送信先アドレスとなる。そして、nft_contentは、作成されたコントラクトの初期化呼び出しに対応します。
send_royalty_paramsに関しては、読み取り専用リクエストに対応する内部メッセージである必要があります。 前回の紹介で、TONの内部メッセージはデータを変更する可能性のある操作だけでなく、このように実装する必要がある読み取り専用操作も含むことを意図的に強調しましたので、コントラクトはそのような操作です。要求は、要求者のマークのコールバック関数の後に行われるべきであり、それぞれ、戻りデータは、項目インデックスの要求だけでなく、対応するロイヤリティデータであることに注意してください。
次に、知識の次のポイントを紹介しましょう、TONのスマートコントラクトは、2つだけrecv_internalとrecv_externalと呼ばれる統一されたエントリ、前者はすべての内部メッセージのために、後者はすべての外部メッセージの統一された呼び出しのために、開発者は、エントリの統一された呼び出しのニーズに応じて関数にする必要があり、開発者は、内部の関数を使用する必要があります。開発者は、上記67行目のコールバック関数のタグである、メッセージで指定された異なるタグビットに基づくスイッチのようなアプローチを使用して、必要に応じて関数内で異なるリクエストに応答する必要があります。まず、83行目でメッセージを解析してsender_addressを取得します。これは、その後のパーミッションチェックに使用されます。演算子は別の構文糖に属していることに注意。次に、opオペレーションのタグビットを解析し、異なるタグビットに従って リクエストを処理する。つまり、上記の関数は何らかのロジックに従って呼び出される。たとえば、ロイヤリティパラメータの要求に応じて、または新しいnftをキャストし、自己インクリメントグローバルindex.
知識の次のポイントは108行に対応し、関数のロジックによって命名される必要があります知ることができる、とrequire関数内のSolidityは、例外をスローする標準関数throw_ unlessを通じてFuncに似ている、例外をスローするFuncの最初のエントリです。unlessは例外をスローするFuncの標準関数で、最初のパラメーターはエラーコード、2番目はビット単位のブール値チェックで、もしfalseならエラーコードで例外をスローします。この行では、equal_slicesを使用して、上記で解析されたsender_addressがコントラクトの永続ストアのowner_addressと等しいかどうかを判断し、権限判定を行っています。

最後に、コードの構造を明確にするために、永続性情報を取得するための一連の補助関数がアイドル状態になり始めました。開発者は、独自のスマート・コントラクトを開発するために、この構造を参照することができる。

DApp開発のTONエコシステムは実に興味深いもので、EVMとは全く異なるパラダイムです。TONチェーンでDAppsを開発する方法に関する記事