著者 | @Web3Mario

要約: 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 は、このタイプの資産の一般的な開発パラダイムです。一般的な ERC721 契約で実装する必要がある機能と、記録する必要がある情報を見てみましょう。下の写真はERC721インターフェイスです。 FT とは異なり、転送インターフェイスに入力する必要があるのは、金額ではなく、転送する tokenId であることがわかります。この tokenId は、NFT アセットの一意性の最も基本的な表現でもあります。もちろん、より多くの属性を保持するために、通常、このメタデータは、NFT の他のスケーラブルなデータを保存する外部リンクです。 PFP イメージ、特定のプロパティの名前などへのリンクとして。

Solidity に精通している開発者、またはオブジェクト指向に精通している開発者にとって、このようなスマート コントラクトは、いくつかのキー マッピング関係など、コントラクトで必要なデータ型を定義し、必要に応じて対応するものを開発するだけで簡単に実装できます。これらのデータの変更ロジックによりNFTを実現できます。

ただし、これは TON Chain では同じではありません。違いには主に 2 つの理由があります。

● TON のデータ ストレージは Cell に基づいて実装され、同じアカウントの Cell は有向非巡回グラフを通じて実装されます。これは、有向非巡回グラフの場合、クエリ コストがデータの深さによって決定されるため、保存する必要があるデータが境界なく増加することがないことを意味します。深さが無限に広がると、クエリ コストが高くなりすぎて、次のような問題が発生する可能性があります。契約は行き詰まりの問題に陥っています。

● 高い同時実行性能を追求するため、TON はシリアル実行アーキテクチャを放棄し、並列処理に特化した開発パラダイムであるアクター モデルを採用し、実行環境を再構築しました。これには影響があります。また、スマート コントラクトは、いわゆる内部メッセージを送信することによってのみ非同期で呼び出すことができます。これは、状態変更タイプであっても、読み取り専用タイプの呼び出しであっても、この原則に従う必要があります。非同期呼び出しが失敗した場合にデータのロールバックを処理する方法を慎重に検討する必要があります。

もちろん、他の技術的な違いについては前の記事で詳しく説明しましたので、この記事ではスマート コントラクトの開発に焦点を当てたいと考えていますので、説明しません。上記の 2 つの設計原則は、TON と EVM でのスマート コントラクト開発に大きな違いをもたらします。最初の議論では、NFT 関連データを保存するには、NFT コントラクトでいくつかのマッピング関係、つまりマッピングを定義する必要があることがわかりました。それらの中で最も重要なのは所有者です。このマッピングには、特定の tokenID に対応する NFT 所有者アドレスのマッピング関係が保存され、これによって NFT の所有権が変更されます。これは理論的には無限にあり得るデータ構造であるため、可能な限り回避する必要があります。したがって、無制限のデータ構造の存在をシャーディングの標準として使用することが公式に推奨されています。つまり、同様のデータ ストレージ要件がある場合は、マスター/スレーブ コントラクト パラダイムが代わりに使用され、各キーに対応するデータはサブコントラクトを作成することによって管理されます。また、メイン コントラクトを通じてグローバル パラメーターを管理したり、サブコントラクト間の内部情報のやり取りの処理を支援したりできます。

これは、TON の NFT も同様のアーキテクチャを使用して設計する必要があることを意味します。各 NFT は、所有者のアドレス、メタデータなどの専用データを保存し、メイン コントラクトを通じて全体の状況を管理します。 NFT名、シンボル、総供給量など

アーキテクチャを明確にしたら、次はコア機能要件を解決する必要があります。このマスタースレーブ契約方式を採用しているため、どの機能を主契約が担うのか、どの機能を副契約が担うのかを明確にする必要があります。この 2 つは、どのような内部情報が伝達されるか、および実行エラーが発生した場合に以前のデータをどのようにロールバックするかということを介して渡されます。通常、複雑な大規模プロジェクトを開発する前に、クラス図を渡して相互間の情報の流れを明確にし、内部呼び出しが失敗した後のロールバックロジックを慎重に検討する必要があります。 もちろん、上記のNFT開発は単純です。ですが、同様の検証を行うこともできます。

ソースコードからTONスマートコントラクトを開発する方法を学ぶ

TON は、スマート コントラクト開発言語として、Func という C ライクな静的型付け言語を設計することを選択しました。次に、TON スマート コントラクトの開発方法を TON 公式ドキュメントから選択しました。興味のある友達は自分で調べてみてください。この場合、単純な TON NFT の例が実装されています。コントラクト構造を見てみましょう。コントラクト構造は 2 つの機能コントラクトと 3 つの必要なライブラリに分かれています。

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

これは、最初の知識ポイント、TON スマート コントラクトにデータを永続的に保存する方法を紹介します。通常、Solidity でのデータの永続的保存は、スマート コントラクトの状態変数の種類に応じて EVM によって自動的に処理されることがわかります。実行後は最新の値に基づいて自動的に永続化および保存されるため、開発者はこのプロセスを考慮する必要がありません。しかし、これは Func には当てはまりません。開発者は対応する処理ロジックを自分で実装する必要があります。この状況は、C および C++ が GC プロセスを考慮する必要があるのと似ていますが、他の新しい開発言語では通常、ロジックのこの部分が自動化されます。 。コードを見てみましょう。まず、必要なライブラリをいくつか紹介します。次に、最初の関数load_dataが永続的に保存されたデータを読み取るために使用されていることがわかります。そのロジックは、最初にget_dataを通じて永続的なコントラクトストレージセルを返すことです。これは、ライブラリ stdlib.fc によって実装された標準によって行われ、これらの関数の一部は通常、システム関数として使用できます。

この関数の戻り値の型は cell です。これは TVM のセル型です。前回の紹介で、TON ブロックチェーン内のすべての永続データがセル ツリーに保存されていることはすでにわかりました。各セルには、最大 1023 ビットの任意のデータと、他のセルへの最大 4 つの参照を含めることができます。セルはスタックベースの TVM のメモリとして使用されます。セルに格納されるのは、コンパクトに暗号化されたデータであり、特定の平文データを取得するには、セルをスライスと呼ばれる型に変換する必要があります。 begin_parse 関数を使用してセルをスライス タイプに変換し、スライスからデータ ビットと他のセルへの参照をロードすることでセル内のデータを取得できます。 15 行目の呼び出しメソッドは、最初の関数の戻り値で 2 番目の関数を直接呼び出す関数内の糖衣構文であることに注意してください。そして最後に、データの永続化順序に従って、対応するデータを順番にロードします。このプロセスは Solidity とは異なり、ハッシュマップに基づいて呼び出されないため、呼び出しの順序を間違えることはできないことに注意してください。

save_data 関数のロジックは同様ですが、これが逆のプロセスである点が異なり、次の知識ポイントであるセル ビルダーのタイプである新しいタイプ ビルダーが導入されます。データ ビットと他のセルへの参照はビルダーに保存でき、その後、新しいセルに最終化できます。まず、標準関数 begin_cell を使用してビルダーを作成し、次にストア関連関数を使用して関連関数を保存します。上記の呼び出し順序は、ここでの格納順序と一致している必要があることに注意してください。最後に、end_cell を使用して新しいセルの構築を完了します。このとき、セルはメモリ内で管理され、最後に、最も外側の set_data を通じてセルの永続的な保存が完了します。

次に、ビジネス関連の機能を見てみましょう。まず、次の知識ポイントである、今紹介したマスター/スレーブ アーキテクチャで頻繁に使用される、コントラクトによる新しいコントラクトの作成方法を紹介する必要があります。 TON では、スマート コントラクト間の呼び出しが内部メッセージの送信によって実装されることがわかっています。これは、send_raw_message と呼ばれるメッセージを通じて実現されます。最初のパラメータはメッセージでエンコードされたセルであり、2 番目のパラメータはトランザクションの実行方法の違いを示すために使用される識別ビットであることに注意してください。異なる内部設定が設定されます。 TON には現在、メッセージ送信の実行モードとして 3 つのメッセージ モードと 3 つのメッセージ フラグがあります。単一のモードを複数の (おそらくなしの) フラグと組み合わせて、目的のモードを取得できます。単に結合するとは、それらの値の合計を埋めることを意味します。モードとフラグの説明表を以下に示します。

最初のメイン関数であるdeploy_nft_itemを見てみましょう。名前が示すように、これは新しいNFTインスタンスを作成またはキャストするために使用される関数であり、send_raw_messageを通じて内部コントラクトが送信され、送信フラグが選択されます。フラグ ビットが 1 の場合は、エンコーディングで指定された手数料が、この実行のガス手数料としてのみ使用されます。上記の導入後、このコーディング ルールは新しいスマート コントラクトを作成する方法に対応する必要があることが容易に理解できます。

それでは、それがどのように実装されているかを見てみましょう。

51行目を直接見てみましょう。上記の2つの関数は、メッセージに必要な情報を生成するために使用される補助関数です。これは、スマートコントラクトの内部メッセージを作成するためのエンコードプロセスです。中央は実際には、内部メッセージの要件を記述するためにいくつかの識別ビットが使用されます。次の知識ポイントは、メッセージの実行方法を記述するために TL-B と呼ばれるバイナリ言語を選択し、異なるフラグを設定することによって実装されます。特定の関数に関する内部情報については、新しいコントラクトの作成とデプロイされたコントラクト関数呼び出しの 2 つの使用シナリオが考えられます。 51行目のメソッドは前者に相当し、新たなnftアイテムコントラクトを作成するもので、主に55行目、56行目、57行目で規定されています。まず、55 行目の大きな一連の数字は一連の識別ビットです。store_uint の最初の入力パラメータは値で、2 番目は内部メッセージがコントラクトによって作成されたかどうかを決定するビット長であることに注意してください。 、最後の 3 つのマーキング ビット、および対応するバイナリ値ビットは 111 (10 進数は 4+2+1) で、最初の 2 つは、メッセージに StateInit データが伴うことを示します。このデータは、ソース コードです。新しい契約と初期化に必要なデータ。後者のフラグ ビットは、内部メッセージの添付ファイルを示します。つまり、関連するロジックと必要なパラメーターが実行されることが予想されます。したがって、コードの 66 行目では 3 桁のデータが設定されていないことがわかります。これは、デプロイされたコントラクトへの関数呼び出しを示しています。詳細なコーディング規則はここで参照できます。

StateInit のエンコード ルールは、calculate_nft_item_state_init によって計算された 49 行のコードに対応します。stateinit データのエンコードには、いくつかのフラグ ビットに加えて、主に新しいコントラクトの 2 つの部分が含まれることに注意してください。コードを作成し、データを初期化します。データのエンコード順序は、新しい契約で指定された永続セルの格納順序と一致している必要があります。 36 行目にあるように、初期化データには、ERC721 の tokenId に似た item_index と、標準関数 my_address によって返される現在のコントラクト アドレス (collection_address) が含まれています。このデータの順序は、次の宣言と一致しています。 nftアイテム。

次の知識ポイントは、TON では、すべての未生成のスマート コントラクトが生成されたアドレスを事前に計算できるということです。これは、Solidity の create2 関数と似ており、ワークチェーン識別子のビットと stateinit のハッシュの 2 つの部分で構成されます。前の導入で、前者は TON 無限シャーディング アーキテクチャに対応するために指定する必要があることがわかりました。現在は統一された値です。標準関数ワークチェーンから取得します。後者は標準関数 cell_hash によって取得されます。この例に戻りますが、calculate_nft_item_address は、新しい契約アドレスを事前に計算する関数です。そして、生成された値を内部メッセージの受信アドレスとして 53 行目のメッセージにエンコードします。 nft_content は、作成されたコントラクトへの初期化呼び出しに対応します。具体的な実装については、次の記事で紹介します。

send_royalty_params に関しては、読み取り専用リクエストの内部メッセージに対する応答である必要があります。前の紹介では、TON の内部メッセージにはデータを変更する可能性のある操作だけでなく、読み取りメッセージも含まれていることを特に強調しました。このように実装する必要があるのは操作だけです。したがって、このコントラクトはそのような操作です。まず、67 行目はリクエストに応答した後のリクエスターのコールバック関数のマークを表していることに注意してください。返されるデータは、要求された商品インデックスと対応するロイヤルティ データです。

次に、次のナレッジ ポイントを紹介します。 TON には、recv_internal と recv_external という名前の 2 つの統合呼び出し入口があり、後者はすべての外部メッセージの統合呼び出し入口です。メッセージの開発 オペレーターは、要件に従って、関数内のメッセージで指定されたさまざまなフラグ ビットに基づいて、スイッチのようなメソッドを使用する必要があります。ここでのフラグ ビットは、上記の行 67 のコールバック関数フラグです。この例に戻ると、まずメッセージの空きチェックを実行し、次にメッセージ内の情報をそれぞれ解析して、sender_address を取得します。このパラメーターは、以降のアクセス許可チェックに使用されることに注意してください。ここは別の構文シュガーに属します。ここでは詳しく説明しません。次に、op 操作のフラグ ビットが解析され、対応するリクエストがさまざまなフラグ ビットに従って処理されます。このうち、上記の関数はそれぞれ一定のロジックに従って呼び出されます。たとえば、ロイヤルティ パラメータのリクエストに応答したり、新しい nft を作成してグローバル インデックスをインクリメントしたりします。

次のナレッジポイントは 108 行目です。この関数の処理ロジックは、Solidity の require 関数と同様に、標準関数 throw_unless を通じてスローされます。 2 番目はビットのブール値を確認するもので、ビットが false の場合はエラー コードとともに例外がスローされます。この行では、equal_slicesを用いて上記で解析したsender_addressがコントラクトの永続ストレージのowner_addressと等しいかどうかを判定し、許可判定を行っています。

最後に、コード構造を明確にするために、永続性情報の取得に役立つ一連の補助関数が開発されました。開発者はこの構造を参照して独自のスマート コントラクトを開発できます。

TON エコシステムでの DApp 開発は非常に興味深いものであり、EVM の開発パラダイムとは大きく異なるため、一連の記事を通じて TON Chain で DApp を開発する方法を紹介します。皆さんと一緒に学び、このチャンスの波を掴みましょう。また、twitter で私と交流して、新しくて興味深い dapp のアイデアをぶつけ、一緒に開発することも歓迎します。