見出し画像

【開発者ガイド】Anchorフレームワークの基礎

当記事は、こちらを翻訳・編集したものです。

0 はじめに

1 Anchorについて

Anchorフレームワークは、Rustマクロを使用して定型コードを削減し、Solanaプログラムを書くために必要な共通のセキュリティチェックの実装を簡素化します。

https://www.anchor-lang.com/

Anchorを、Solanaプログラムのためのフレームワークとして考えてください。

これは、Next.jsがWeb開発のためのフレームワークであるのと非常によく似ています。

https://nextjs.org/

Next.jsがHTMLやTypeScriptだけに依存するのではなく、Reactを使用してウェブサイトを作成できるようにするのと同様です。

AnchorはSolanaプログラムの構築をより直感的で安全にするための一連のツールと抽象化を提供します。

2 主なマクロ

Anchorプログラムに見られる主なマクロは次のとおりです。

  • declare_id: プログラムのオンチェーンアドレスを指定します

  • #[program]: プログラムの命令ロジックを含むモジュールを指定します

  • #[derive(Accounts)]: 構造体に適用され、命令に必要なアカウントのリストを示します

  • #[account]: 構造体に適用され、プログラム固有のカスタムアカウントタイプを作成します

1 Anchorプログラム

以下は、新しいアカウントを作成する単一の命令を持つシンプルなAnchorプログラムです。

このプログラムを通じて、Anchorプログラムの基本構造を説明します。

こちらがSolana Playgroundにあるプログラムです。

1 declare_idマクロ

declare_idマクロはプログラムのオンチェーンアドレス(プログラムID)を指定します。

Anchorプログラムを初めてビルドする際、フレームワークは新しいキーペアを生成してプログラムをデプロイします(特に指定がない限り)。

補足
①キーペアを作成
②そのキーペアにプログラムをデプロイとなります。

このキーペアの公開鍵は、declare_idマクロでプログラムIDとして使用されます。

Solana Playgroundを使用する場合、プログラムIDは自動的に更新され、UIからエクスポートできます。

ローカルでビルドする場合、プログラムキーペアは /target/deploy/your_program_name.json にあります。

2 #[program]マクロ

#[program]マクロは、プログラムの命令ロジックを含むモジュールを指定します。

モジュール内の各公開関数は、プログラムの個別の命令を表します。

各関数において、最初のパラメータは常に Context 型です。

後続のパラメータはオプションであり、命令に必要な追加データを定義します。

Context 型は、命令に対して以下の非引数入力へのアクセスを提供します。

補足
accountsやbumpsは特によく使う印象です。

Contextはジェネリック型で、Tは命令に必要なアカウントのセットを表します。

命令のContextを定義する際、T型はAccountsトレイトを実装する構造体です(Context<Initialize>のように)。

このcontextパラメータにより、命令は次の要素にアクセスできます。

  • ctx.accounts: 命令のアカウント

  • ctx.program_id: プログラム自体のアドレス

  • ctx.remaining_accounts: Accounts構造体に指定されていない、命令に提供された残りのアカウントすべて

  • ctx.bumps: Accounts構造体で指定されたプログラム派生アドレス(PDA)アカウントのバンプシード

プログラム派生アドレスについては、こちらもご参照ください。

3 #[derive(Accounts)]マクロ

#[derive(Accounts)]マクロは構造体に適用され、Accountsトレイトを実装します。

これは特定の命令に必要なアカウントのセットを指定および検証するために使用されます。

構造体内の各フィールドは、命令に必要なアカウントを表します。

フィールドの名前は任意ですが、アカウントの目的を示す説明的な名前を使用することをお勧めします。

Solanaプログラムを構築する際、クライアントが提供するアカウントを検証することが重要です。

Anchorでは、アカウント制約適切なアカウントタイプを指定することでこの検証を実現します。

3ー1 アカウント制約

制約は、アカウントが命令に対して有効と見なされるために満たす必要がある追加の条件を定義します。

https://www.anchor-lang.com/docs/account-constraints

制約#[account(..)]属性を使用して適用され、Accounts構造体内のアカウントフィールドの上に配置されます。

3ー2 アカウントタイプ

Anchorは、プログラムが期待するアカウントクライアントが提供するアカウントが一致することを保証するためのさまざまなアカウントタイプを提供します。

https://www.anchor-lang.com/docs/account-types

4 アクセス方法

Accounts構造体内のアカウントは、Contextを使用して命令内でアクセスできます。例えば、ctx.accounts構文を使用します。

5 アカウント検証の詳細

Anchorプログラムの命令が呼び出されると、Accounts構造体で指定された検証を次のように行います。

5ー1 アカウントタイプの検証

 命令に渡されたアカウントが、命令のコンテキストに定義されたアカウントタイプと一致することを確認します。

5ー2 制約チェック

指定された追加の制約に対してアカウントを検証します。

これにより、クライアントから命令に渡されたアカウントが有効であることを保証します。

検証に失敗した場合、命令ハンドラ関数のメインロジックに到達する前にエラーが発生します。

より詳細な例については、Anchorドキュメントの制約アカウントタイプのセクションを参照してください。

6 #[account]マクロ

#[account]マクロは、プログラムのカスタムデータアカウントタイプのフォーマットを定義するために構造体に適用されます。

各フィールドは、アカウントデータに格納されるフィールドを表します。

このマクロは以下の機能を実装します。

6ー1 所有権の割り当て

アカウントを作成する際、アカウントの所有権がdeclare_idで指定されたプログラムに自動的に割り当てられます。

6ー2 識別子の設定

アカウントタイプ固有の8バイトの識別子アカウントデータの最初の8バイトに追加されます。

これにより、アカウントタイプの識別と検証が容易になります。

6ー3 データのシリアル化およびデシリアル化

アカウントタイプに対応するアカウントデータは自動的にシリアル化およびデシリアル化されます。

7 アカウント識別子

Anchorでは、アカウント識別子は各アカウントタイプに固有8バイトの識別子です。

この識別子はアカウントタイプ名のSHA256ハッシュの最初の8バイトから導出されます。

アカウントのデータの最初の8バイトは、この識別子に予約されています。

8 識別子の使用シナリオ

識別子は次のシナリオで使用されます。

8ー1 初期化

アカウントの初期化時に、識別子がアカウントタイプの識別子で設定されます。

8ー2 デシリアル化

アカウントデータをデシリアライズする際、データ内の識別子アカウントタイプの識別子と一致するか確認します。

一致しない場合、クライアントが予期しないアカウントを提供したことを示します。

この仕組みにより、Anchorプログラム内で正しいアカウントが使用されることが保証されます。

2 IDL(interface description language)ファイル

Anchorプログラムがビルドされると、Anchorはプログラムの構造を表すインターフェース記述言語(IDL)ファイルを生成します。

このIDLファイルは、プログラムの命令を作成し、プログラムアカウントを取得するための標準化されたJSON形式を提供します。

以下に、IDLファイルがプログラムコードとどのように関連するかの例を示します。

1 命令

IDLのinstructions配列はプログラムの命令に対応し、各命令に必要なアカウントとパラメータを指定します。

2 アカウント

IDLのaccounts配列は、プログラムのデータアカウントの構造を指定するために#[account]マクロで注釈された構造体に対応します。

3 クライアント

Anchorは、Solanaプログラムとクライアントからのやり取りを簡素化するTypeScriptクライアントライブラリ(@coral-xyz/anchor)を提供します。

https://github.com/coral-xyz/anchor/tree/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor

クライアントライブラリを使用するには、まずAnchorによって生成されたIDLファイルを使用してProgramインスタンスを設定します。

1 クライアントプログラム

Programのインスタンスを作成するには、プログラムのIDL、そのオンチェーンアドレス(programId)、およびAnchorProviderが必要です。

AnchorProviderは次の2つの要素を組み合わせます。

  • Connection - Solanaクラスタへの接続(例:localhost、devnet、mainnet)

  • Wallet - (オプション)トランザクションの支払いや署名に使用されるデフォルトのウォレット

https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/provider.ts#L55

ローカルでAnchorプログラムをビルドする際、Programのインスタンスを作成する設定自動的にテストファイルで行われます。

IDLファイル/targetフォルダにあります。

フロントエンドとの統合時にウォレットアダプターを使用する場合、AnchorProviderとProgramを手動で設定する必要があります。

また、デフォルトのウォレットがない場合でも、Solanaクラスタへの接続IDLのみを使用してProgramのインスタンスを作成することもできます。

これにより、ウォレットが接続される前にプログラムアカウントを取得することができます。

2 命令の呼び出し

プログラムがセットアップされたら、AnchorのMethodsBuilderを使用して命令を作成したり、トランザクションを構築したり、送信することができます。

基本的なフォーマットは次のようになります。

  • program.methods: プログラムのIDLに関連する命令呼び出しのためのビルダーAPI

  • .instructionName: プログラムのIDLから特定の命令、命令データをカンマ区切りで渡します

  • .accounts: IDLで指定された命令に必要な各アカウントのアドレスを渡します

  • .signers: 命令に追加の署名者が必要な場合、キーペアの配列をオプションで渡します

以下に、methodsビルダーを使用して命令を呼び出す方法の例を示します。

2ー1 rpc()

rpc()メソッドは指定された命令で署名されたトランザクションを送信し、TransactionSignatureを返します。

https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/methods.ts#L283

rpcを使用する場合、プロバイダーからウォレットが自動的に署名者として含まれます。

2ー2 transaction()

transaction()メソッドはトランザクションを構築し、指定された命令をトランザクションに追加します(自動的に送信はしません)。

https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/methods.ts#L382

2ー3 instruction()

instruction()メソッドは、指定された命令を使用してTransactionInstructionを構築します。

https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/methods.ts#L348

これは、命令をトランザクションに手動で追加し、他の命令と組み合わせる場合に便利です。

3 アカウントの取得

クライアントプログラムは、プログラムアカウントを簡単に取得およびフィルタリングすることもできます。

program.accountを使用し、IDLのアカウントタイプの名前を指定します。

Anchorはすべてのアカウントをデシリアライズして返します。

3ー1 all()

all()を使用して、特定のアカウントタイプのすべての既存アカウントを取得します。

3ー2 memcmp

memcmpを使用して、特定のオフセット特定の値と一致するデータを保持するアカウントをフィルタリングします。

オフセットを計算する際には、アカウントデータの最初の8バイトがAnchorプログラムによって予約されている識別子用であることを忘れないでください。

memcmpを使用するには、取得するアカウントタイプのデータフィールドのバイトレイアウトを理解している必要があります。

3ー3 fetch()

fetch()を使用して、アカウントアドレスを指定して特定のアカウントデータを取得します。

3ー4 fetchMultiple()

fetchMultiple()を使用して、アカウントアドレスの配列を指定して複数のアカウントデータを取得します。

以上です。

サポートをしていただけたらすごく嬉しいです😄 いただけたサポートを励みに、これからもコツコツ頑張っていきます😊