Avalanche ICTT
はじめに
初めまして。
『DApps開発入門』という本や色々記事を書いているAva Labsのかるでねです。
以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!
今回は『Avalanche ICTT』についてまとめていきます。
Avalanche ICTTの各処理フローについて図を元にまとめているので、この記事を読むことで非エンジニアでも全体像が掴めます。
また、エンジニア向けにソースコードを元に詳しく説明しているので、技術的にも理解を深められると思います。
ICTT概要
Avalanche Interchain Token Transfer (ICTT)は、異なるL1ブロックチェーン間でトークンを送付するためのアプリケーションです。
このシステムは、複数のL1にデプロイされた一連のスマートコントラクトによって実装されており、クロスチェーン通信にはICM(Inter-Chain Messaging) を利用します。
ICTTは、1つのTokenHomeコントラクトと、1つ以上のTokenRemoteコントラクトから構成されます。
Homeコントラクトは、送付元となるトークンが存在するL1(Homeチェーン)上にデプロイされて、トークンの管理を担当します。
TokenRemoteコントラクトは、送付先となるL1(Remoteチェーン)上にデプロイされ、送付したいトークンのラップトークンのコントラクトです。

コントラクト構成
コントラクトの継承関係は以下のようになっています。

TokenHomeコントラクトに、コアの実装やERC20とネイティブトークンで共通する機能の実装や必要な関数の定義などがされています。
TokenHomeコントラクトを継承している〇〇UpgradeableContractでは、ERC20とネイティブトークンそれぞれ特有の処理をしています。
さらにそれを継承しているERC20TokenHomeコントラクトとNativeTokenHomeコントラクトは、〇〇UpgradeableContractをそのまま実装してあり、カスタマイズ実装をしたい場合などにはここに実装を追加します。
トークン送付
トークン送付のプロセスは、Homeチェーンでトークンをロックし、 Remoteチェーンでそのトークンをラップしたトークンを新規に発行します。

- ユーザーが保有しているトークンの
transferする権限を TokenHomeコントラクトに付与するリクエスト。 -
transferする権限を付与。 - Remoteチェーン上にトークンを送付するリクエスト。
- トークンをTokenHomeコントラクトにロックして、Remoteチェーン上にトークンを送付。
TokenHomeコントラクトは、各L1上のTokenRemoteコントラクトに送付されたトークン残高を追跡し、トークンがTokenHomeコントラクトに戻される時には、TokenHomeコントラクトにロックされていた元のトークンをユーザーに返却します。

- ユーザーがRemoteチェーンに送付したトークンを戻すリクエスト。
- RemoteチェーンからHomeチェーンにトークンを送付。
- ロックしていたトークンをユーザーに返す。
TokenHomeコントラクトは、ERC20トークンかネイティブトークンのいずれかを送付し、事前に登録されている任意のTokenRemoteコントラクトにトークンを送付できます。
Remoteチェーン上のトークンもERC20かネイティブトークンのいずれかであり、ユーザーはHomeチェーンとRemoteチェーン間で以下のようなERC20とネイティブトークンの組み合わせでトークンを交換できます。

また、マルチホップTransferもサポートしています。
例えば、同じTokenHomeコントラクトに接続された2つのRemoteチェーンと各チェーン上のTokenRemoteコントラクトへの送付は、まず1つ目のRemoteチェーンのTokenRemoteコントラクトからTokenHomeコントラクトへリクエストが送られて、そのリクエストを受け取ったTokenHomeコントラクトが2つ目のRemoteチェーンのTokenRemoteコントラクトにトークンの送付を行います。

基本的なトークン送付に加えて、sendAndCallインターフェースが提供されており、これを利用するとトークンの送付とスマートコントラクトでの利用を単一のICMメッセージ内で完結させることができます。
もし宛先のスマートコントラクトの呼び出しが失敗した場合、送付されたトークンは送付先チェーン上のフォールバック用アドレスに送られます。
sendAndCallインターフェースにより、送付されたトークンを他のチェーン上のdAppsで直接使用すること(スワップの実行、サービス利用時の手数料支払いなど)が可能になります。
フロー
コントラクトのデプロイ
まずは、以下のコントラクトを一式デプロイしていきます。

-
Homeチェーン
- TokenHomeコントラクト
- ERC20コントラクト
-
Remoteチェーン
- TokenRemoteコントラクト
TokenHomeコントラクト
TokenHomeコントラクトをデプロイする時は、以下のようにTeleporterコントラクトの情報とERC20コントラクトのアドレスを渡します。
ネイティブトークンを使用する場合は、以下のようにネイティブトークンのラップトークンコントラクトのアドレスを渡しています。
これは、ネイティブトークンでもERC20トークンと同じフローで処理されるように、ラップトークンに変換するようにして抽象化されています。
TokenRemoteコントラクト
TokenRemoteコントラクトのデプロイ時には、以下のようにERC20トークンを継承しているので、ERC20のトークン名やシンボルを渡す必要があります。
以下の tokenName と tokenSymbol にERC20のトークン名とシンボルを渡しています。
ネイティブトークンの場合は、そのままネイティブトークンがMintされます。
また、TokenRemoteSettings には、以下のようにTeleporterコントラクトの情報とHomeチェーンのチェーンIDとTokenHomeコントラクトのアドレスを登録しています。
これはICM(Interchain Messaging)で必要になる情報です。
Remoteコントラクトの登録
TokenHomeコントラクトには、TokenRemoteコントラクトの情報を登録していないため、事前に登録しておく必要があります。

TokenRemoteコントラクトの方は、TokenRemoteコントラクトデプロイ時に、TokenHomeコントラクトの登録は完了しています。

TokenRemoteコントラクトの登録は、registerWithHome 関数を実行することで行われます。
ICMを使用した場合、クロスチェーン通信時に呼ばれる関数は、以下の_receiveTeleporterMessage 関数です。
受け取ったメッセージタイプ(今回は REGISTER_REMOTE )ごとに処理を分岐させており、TokenHomeコントラクトでTokenRemoteコントラクトの登録を行うときは _registerRemote 関数が実行されます。
引数には、送付元のL1チェーンのIDと送付元のコントラクト(TokenRemoteコントラクト)のアドレスを渡しています。
_registerRemote 関数ではいくつかバリデーションを行った後に、TokenRemoteコントラクトの登録を行なっています。
この時、ERC20トークンの桁数が異なる場合に揃えるように
担保額の供給

TokenRemoteコントラクトのデプロイ時に、initialReserveImbalance_ という値を指定することができます。
この値は、初期発行トークン量として指定することができる値です。
ここで指定された値は、TokenRemoteコントラクトをTokenHomeコントラクトへ登録するときに一緒に渡されます。
そして、TokenHomeコントラクトでは、_addCollateral 関数が実行されて、指定された値分のTokenHomeコントラクトに紐づいているERC20トークンを担保として預けることで、初期発行を満たしたと判断されます。
以下の collateralNeeded という値が true になっていれば、初期発行分の担保を預けてもらったと判断します。
TokenHomeコントラクト側では、Remoteチェーンのトークン(TokenRemoteコントラクト)を発行するリクエストを送る場合、以下のように collateralNeeded の値を確認しています。
そのため、ユーザーはこの初期発行が満たされないうちは、該当のRemoteチェーン上にトークンを送付することはできません。
0 を指定することもでき、この場合は初期発行はなしとみなされて、TokenRemoteコントラクトのデプロイ後直後にユーザーがRemoteチェーン上にトークンを送付することも可能です。
Approve
Homeチェーン上のERC20トークンをRemoteチェーンに送付する場合、事前にTokenHomeコントラクトに紐づいているERC20トークンをTokenHomeコントラクトに送付額分 approve しておく必要があります。
この approve をしておくことで、TokenHomeコントラクトがユーザーの資金を代わりに transfer (transferFrom)することができるようになります。
送付の実行
ユーザーがHomeチェーンからRemoteチェーンにERC20トークンのリクエストを出します。
Homeチェーン
TokenHomeコントラクトの send 関数を実行します。
内部関数である _send を実行します。
実行条件を確認して、_prepareSend 関数でERC20トークンをTokenHomeコントラクトに送付してロックしています。
実行している safeTransferFrom 関数は、SafeERC20TransferFrom というライブラリの中で実装されています。
from に指定したアドレス(ユーザーのアドレス)から、address(this) (TokenHomeコントラクト)に amount で指定されたトークン量を送付しています。
その後、Teleporterコントラクト経由でRemoteチェーンにリクエストを送信しています。
この時、以下のように SINGLE_HOP_SEND というメッセージタイプを指定しています。
これは、Remoteチェーン側でどのリクエストが識別できるようにするためです。
Remoteチェーン
ICMでは、宛先チェーンの指定されたコントラクトの _receiveTeleporterMessage が呼び出されるようになっています。
SINGLE_HOP_SEND が指定されているので、以下のように _withdraw 関数が呼び出されます。
_withdraw 関数では、以下のように _mint 関数を呼び出して、指定されたアドレス(recipient)に指定されたトークン量(amount)のERC20トークンを送付しています。
TeleporterコントラクトやRelayerを含めたフロー図は以下のようになります。

トークンを戻す
Remoteチェーン上に存在するERC20トークンを、Homeチェーン上のERC20トークンに戻すフローを確認していきます。
実行する関数は、TokenHomeコントラクトと同じで send 関数です。
内部関数である _send 関数が呼び出されて、特定のL1チェーン宛の場合は _processSend が呼び出されます。
実行条件を確認して、ICMを活用してHomeチェーン上のTokenHomeコントラクトにメッセージを送付しています。
TokenHomeコントラクトでは、Teleporterコントラクトから _receiveTeleporterMessage 関数が呼び出され、メッセージタイプに SINGLE_HOP_SEND が指定されているため、以下の処理が実行されます。
内部では _withdraw 関数が呼び出されて、指定されたリクエスト元(ユーザー)のアドレス(recipient)に指定されたトークン量(amount )のERC20を発行します。
フローとしては、TokenHomeコントラクトからのトークン発行と逆になります。

追加処理実行
トークン送付に合わせて、任意の処理を実行することもできます。
この時には、以下の sendAndCall 関数を実行します。
実行したい情報(例えばRemoteチェーン上の特定のコントラクトの特定の関数を実行など)を input という引数に渡します。
input は以下の SendAndCallInput という情報を格納できるようになっています。
recipientContract に実行対象のコントラクトアドレスを渡し、recipientPayload に実行情報をエンコードしたバイトデータを渡します。
Remoteチェーンでは、TokenRemoteコントラクトの _receiveTeleporterMessage 関数内で、メッセージタイプが SINGLE_HOP_CALL の処理(_handleSendAndCall 関数)が呼び出されます。
_handleSendAndCall 内では以下でトークン発行処理後に追加処理を実行しています。
RemoteチェーンのTokenRemoteコントラクトからリクエストを送付する時も、同じように sendAndCall 関数を実行します。
フローは、以下のようにトークン送付時のフローに1つ処理が追加されます。

マルチホップ
2つのRemoteチェーン間でERC20トークンの送付を行うフローを確認していきます。
まず、1つ目のRemoteチェーンのTokenRemoteコントラクトの send 関数を実行します。
この時、引数の input の destinationBlockchainID にもう1つのRemoteチェーンのチェーンIDを指定します。
合わせて、destinationTokenTransferrerAddress にももう1つにTokenRemoteコントラクトのアドレスを指定します。
_send 関数内で、事前に登録されているHomeチェーンのチェーンIDと引数に設定されている input.destinationBlockchainID の値が異なるため、マルチホップTransferと判断されて _processSendMultiHop 関数が呼び出されます。
ICMメッセージの送付先は、事前に登録されているHomeチェーン上のTokenHomeコントラクトになります。
TokenHomeコントラクトがハブとなり、Remoteチェーン間の送付リクエストを中継します。
Homeチェーン上のTokenHomeコントラクトでは、Teleporterコントラクトから _receiveTeleporterMessage 関数が呼び出されます。 今回はメッセージタイプに MULTI_HOP_CALL` が指定されているため、以下の処理が実行されます。
_routeMultiHopSendAndCall 関数が内部で呼び出されます。
渡された情報を元に、もう1つのRemoteチェーンのTokenRemoteコントラクトにリクエストが送付されます。
このあとは、通常のTokenHomeコントラクトからTokenRemoteコントラクトへのリクエストと同じフローになります。
このように、TokenHomeコントラクトを経由することで、常にTokenRemoteコントラクトはTokenHomeコントラクトをもとにトークンを発行する形式にできます。
フロー図は以下のようになります。

最後に
今回は『Avalanche ICTT』についてまとめてきました。
他でも色々記事を書いているのでぜひよろしければ読んでいってください!
Discussion