🏗️

Avalanche ICTT

に公開

はじめに

初めまして。
『DApps開発入門』という本や色々記事を書いているAva Labsのかるでねです。

https://amzn.asia/d/gxvJ0Pw

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

https://twitter.com/cardene777

https://qiita.com/cardene

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

今回は『Avalanche ICTT』についてまとめていきます。
Avalanche ICTTの各処理フローについて図を元にまとめているので、この記事を読むことで非エンジニアでも全体像が掴めます。
また、エンジニア向けにソースコードを元に詳しく説明しているので、技術的にも理解を深められると思います。

https://zenn.dev/heku/books/657e304323fc92

ICTT概要

Avalanche Interchain Token Transfer (ICTT)は、異なるL1ブロックチェーン間でトークンを送付するためのアプリケーションです。
このシステムは、複数のL1にデプロイされた一連のスマートコントラクトによって実装されており、クロスチェーン通信にはICM(Inter-Chain Messaging) を利用します。

https://zenn.dev/ava_labs_jp/articles/c6edd8d46ec9bc

https://zenn.dev/heku/books/f8e824e148b5de

ICTTは、1つのTokenHomeコントラクトと、1つ以上のTokenRemoteコントラクトから構成されます。

Homeコントラクトは、送付元となるトークンが存在するL1(Homeチェーン)上にデプロイされて、トークンの管理を担当します。
TokenRemoteコントラクトは、送付先となるL1(Remoteチェーン)上にデプロイされ、送付したいトークンのラップトークンのコントラクトです。

コントラクト構成

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

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

トークン送付

トークン送付のプロセスは、Homeチェーンでトークンをロックし、 Remoteチェーンでそのトークンをラップしたトークンを新規に発行します。

  1. ユーザーが保有しているトークンの transfer する権限を TokenHomeコントラクトに付与するリクエスト。
  2. transfer する権限を付与。
  3. Remoteチェーン上にトークンを送付するリクエスト。
  4. トークンをTokenHomeコントラクトにロックして、Remoteチェーン上にトークンを送付。

TokenHomeコントラクトは、各L1上のTokenRemoteコントラクトに送付されたトークン残高を追跡し、トークンがTokenHomeコントラクトに戻される時には、TokenHomeコントラクトにロックされていた元のトークンをユーザーに返却します。

  1. ユーザーがRemoteチェーンに送付したトークンを戻すリクエスト。
  2. RemoteチェーンからHomeチェーンにトークンを送付。
  3. ロックしていたトークンをユーザーに返す。

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で直接使用すること(スワップの実行、サービス利用時の手数料支払いなど)が可能になります。

フロー

https://zenn.dev/ava_labs_jp/articles/c6edd8d46ec9bc#フロー

コントラクトのデプロイ

まずは、以下のコントラクトを一式デプロイしていきます。

  • Homeチェーン
    • TokenHomeコントラクト
    • ERC20コントラクト
  • Remoteチェーン
    • TokenRemoteコントラクト

TokenHomeコントラクト

TokenHomeコントラクトをデプロイする時は、以下のようにTeleporterコントラクトの情報とERC20コントラクトのアドレスを渡します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L112-L124

https://zenn.dev/heku/books/fe10e09f39a517

ネイティブトークンを使用する場合は、以下のようにネイティブトークンのラップトークンコントラクトのアドレスを渡しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/NativeTokenHomeUpgradeable.sol#L80-L89

これは、ネイティブトークンでもERC20トークンと同じフローで処理されるように、ラップトークンに変換するようにして抽象化されています。

TokenRemoteコントラクト

TokenRemoteコントラクトのデプロイ時には、以下のようにERC20トークンを継承しているので、ERC20のトークン名やシンボルを渡す必要があります。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L30

以下の tokenNametokenSymbolERC20のトークン名とシンボルを渡しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L78-L85

ネイティブトークンの場合は、そのままネイティブトークンがMintされます。

また、TokenRemoteSettings には、以下のようにTeleporterコントラクトの情報とHomeチェーンのチェーンIDとTokenHomeコントラクトのアドレスを登録しています。
これはICM(Interchain Messaging)で必要になる情報です。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/interfaces/ITokenRemote.sol#L22-L29

Remoteコントラクトの登録

TokenHomeコントラクトには、TokenRemoteコントラクトの情報を登録していないため、事前に登録しておく必要があります。

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

TokenRemoteコントラクトの登録は、registerWithHome 関数を実行することで行われます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L207-L235

ICMを使用した場合、クロスチェーン通信時に呼ばれる関数は、以下の_receiveTeleporterMessage 関数です。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L495-L595

受け取ったメッセージタイプ(今回は REGISTER_REMOTE )ごとに処理を分岐させており、TokenHomeコントラクトでTokenRemoteコントラクトの登録を行うときは _registerRemote 関数が実行されます。
引数には、送付元のL1チェーンのIDと送付元のコントラクト(TokenRemoteコントラクト)のアドレスを渡しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L590-L594

_registerRemote 関数ではいくつかバリデーションを行った後に、TokenRemoteコントラクトの登録を行なっています。
この時、ERC20トークンの桁数が異なる場合に揃えるように

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L170-L225

担保額の供給

TokenRemoteコントラクトのデプロイ時に、initialReserveImbalance_ という値を指定することができます。
この値は、初期発行トークン量として指定することができる値です。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L151-L163

ここで指定された値は、TokenRemoteコントラクトをTokenHomeコントラクトへ登録するときに一緒に渡されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L215

そして、TokenHomeコントラクトでは、_addCollateral 関数が実行されて、指定された値分のTokenHomeコントラクトに紐づいているERC20トークンを担保として預けることで、初期発行を満たしたと判断されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L449C14-L486

以下の collateralNeeded という値が true になっていれば、初期発行分の担保を預けてもらったと判断します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L476-L477C13

TokenHomeコントラクト側では、Remoteチェーンのトークン(TokenRemoteコントラクト)を発行するリクエストを送る場合、以下のように collateralNeeded の値を確認しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L766

そのため、ユーザーはこの初期発行が満たされないうちは、該当のRemoteチェーン上にトークンを送付することはできません。

0 を指定することもでき、この場合は初期発行はなしとみなされて、TokenRemoteコントラクトのデプロイ後直後にユーザーがRemoteチェーン上にトークンを送付することも可能です。

Approve

Homeチェーン上のERC20トークンをRemoteチェーンに送付する場合、事前にTokenHomeコントラクトに紐づいているERC20トークンをTokenHomeコントラクトに送付額分 approve しておく必要があります。
この approve をしておくことで、TokenHomeコントラクトがユーザーの資金を代わりに transfertransferFrom)することができるようになります。

送付の実行

ユーザーがHomeチェーンからRemoteチェーンにERC20トークンのリクエストを出します。

Homeチェーン

TokenHomeコントラクトの send 関数を実行します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/ERC20TokenHomeUpgradeable.sol#L122-L124

内部関数である _send を実行します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L240-L276

実行条件を確認して、_prepareSend 関数でERC20トークンをTokenHomeコントラクトに送付してロックしています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L755-L788

実行している safeTransferFrom 関数は、SafeERC20TransferFrom というライブラリの中で実装されています。
from に指定したアドレス(ユーザーのアドレス)から、address(this)TokenHomeコントラクト)に amount で指定されたトークン量を送付しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/utilities/SafeERC20TransferFrom.sol#L46-L60

その後、Teleporterコントラクト経由でRemoteチェーンにリクエストを送信しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L261-L273

この時、以下のように SINGLE_HOP_SEND というメッセージタイプを指定しています。
これは、Remoteチェーン側でどのリクエストが識別できるようにするためです。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L253-L258

Remoteチェーン

ICMでは、宛先チェーンの指定されたコントラクトの _receiveTeleporterMessage が呼び出されるようになっています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L329-L368

SINGLE_HOP_SEND が指定されているので、以下のように _withdraw 関数が呼び出されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L353-L356

_withdraw 関数では、以下のように _mint 関数を呼び出して、指定されたアドレス(recipient)に指定されたトークン量(amount)のERC20トークンを送付しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L136-L139

TeleporterコントラクトやRelayerを含めたフロー図は以下のようになります。

https://zenn.dev/ava_labs_jp/articles/c6edd8d46ec9bc#フロー

トークンを戻す

Remoteチェーン上に存在するERC20トークンを、Homeチェーン上のERC20トークンに戻すフローを確認していきます。

実行する関数は、TokenHomeコントラクトと同じで send 関数です。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L114-L116

内部関数である _send 関数が呼び出されて、特定のL1チェーン宛の場合は _processSend が呼び出されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L291-L299

実行条件を確認して、ICMを活用してHomeチェーン上のTokenHomeコントラクトにメッセージを送付しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L449-L483

TokenHomeコントラクトでは、Teleporterコントラクトから _receiveTeleporterMessage 関数が呼び出され、メッセージタイプに SINGLE_HOP_SEND が指定されているため、以下の処理が実行されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L495-L512

内部では _withdraw 関数が呼び出されて、指定されたリクエスト元(ユーザー)のアドレス(recipient)に指定されたトークン量(amount )のERC20を発行します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/ERC20TokenHomeUpgradeable.sol#L163-L167

フローとしては、TokenHomeコントラクトからのトークン発行と逆になります。

追加処理実行

トークン送付に合わせて、任意の処理を実行することもできます。
この時には、以下の sendAndCall 関数を実行します。
実行したい情報(例えばRemoteチェーン上の特定のコントラクトの特定の関数を実行など)を input という引数に渡します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/ERC20TokenHomeUpgradeable.sol#L129-L137

input は以下の SendAndCallInput という情報を格納できるようになっています。
recipientContract に実行対象のコントラクトアドレスを渡し、recipientPayload に実行情報をエンコードしたバイトデータを渡します。

https://github.com/ava-labs/icm-contracts/blob/dac65983fb956586aebeadab1c4290d2f87927b4/contracts/ictt/interfaces/ITokenTransferrer.sol#L64-L76

Remoteチェーンでは、TokenRemoteコントラクトの _receiveTeleporterMessage 関数内で、メッセージタイプが SINGLE_HOP_CALL の処理(_handleSendAndCall 関数)が呼び出されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L357-L368

_handleSendAndCall 内では以下でトークン発行処理後に追加処理を実行しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L192-L194

RemoteチェーンのTokenRemoteコントラクトからリクエストを送付する時も、同じように sendAndCall 関数を実行します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L121-L123

フローは、以下のようにトークン送付時のフローに1つ処理が追加されます。

マルチホップ

2つのRemoteチェーン間でERC20トークンの送付を行うフローを確認していきます。

まず、1つ目のRemoteチェーンのTokenRemoteコントラクトの send 関数を実行します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L114-L116

この時、引数の inputdestinationBlockchainID にもう1つのRemoteチェーンのチェーンIDを指定します。
合わせて、destinationTokenTransferrerAddress にももう1つにTokenRemoteコントラクトのアドレスを指定します。

https://github.com/ava-labs/icm-contracts/blob/dac65983fb956586aebeadab1c4290d2f87927b4/contracts/ictt/interfaces/ITokenTransferrer.sol#L33-L42

_send 関数内で、事前に登録されているHomeチェーンのチェーンIDと引数に設定されている input.destinationBlockchainID の値が異なるため、マルチホップTransferと判断されて _processSendMultiHop 関数が呼び出されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L291-L299

ICMメッセージの送付先は、事前に登録されているHomeチェーン上のTokenHomeコントラクトになります。
TokenHomeコントラクトがハブとなり、Remoteチェーン間の送付リクエストを中継します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L488-L534

Homeチェーン上のTokenHomeコントラクトでは、Teleporterコントラクトから _receiveTeleporterMessage 関数が呼び出されます。 今回はメッセージタイプに MULTI_HOP_CALL` が指定されているため、以下の処理が実行されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L558C77-L590

_routeMultiHopSendAndCall 関数が内部で呼び出されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L390-L444

渡された情報を元に、もう1つのRemoteチェーンのTokenRemoteコントラクトにリクエストが送付されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L429-L441

このあとは、通常のTokenHomeコントラクトからTokenRemoteコントラクトへのリクエストと同じフローになります。
このように、TokenHomeコントラクトを経由することで、常にTokenRemoteコントラクトはTokenHomeコントラクトをもとにトークンを発行する形式にできます。

フロー図は以下のようになります。

最後に

今回は『Avalanche ICTT』についてまとめてきました。

他でも色々記事を書いているのでぜひよろしければ読んでいってください!

https://amzn.asia/d/gxvJ0Pw

https://cardene.notion.site/EIP-2a03fa3ea33d43baa9ed82288f98d4a9?source=copy_link

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://chaldene.net/

https://twitter.com/cardene777

https://cardene.substack.com/

Discussion