【ERC4337(2)】EntryPoint、Paymaster、Smart Contract Account、ContractFactoryの実装の仕方

Web3.0, ブロックチェーン

お疲れ様です。
次世代ビジネス推進部の荻原です。

今回は【ERC4337(1)】EntryPointのhandleOps()の処理を理解する、Paymasterを指定してガス代について検証するの続きです。

ERC4337を利用してERC721のmintとtransferを行うために、必要なコントラクトの準備をしていきます。Alchemy等のSaaSもあるのですが、すべてのコントラクトを自分で用意し、ローカルで完結させようと思います。

この記事内で紹介するコードはあくまで検証用なのでセキュリティ面に問題がある点はご了承ください。

実行環境は次の通りです。

手順としては、

  • EntryPoint
  • Paymaster
  • ERC721
  • Smart Contract Account
  • ContractFactory

5つのコントラクトを用意し、UserOperation実行に必要な資金をEntryPointにdepositします。

目次

1. EntryPointの準備
2. Paymasterの準備
3. ERC721コントラクトの準備
4. Smart Contract Accountの準備
5. ContractFactoryの準備
6. depositoTo()の実行

1. EntryPointの準備

EntryPointは、Smart Contract Accountを介してUserOperationのcallDataで指定されたコントラクトの関数を実行するコントラクトです。そのトランザクションを発行するのはBundlerというコンポーネントです。

BundlerとしてAlchemyなど外部のサービスを利用する場合はそのサービスが用意したEntryPointのコントラクトを利用することになります。
今回は自分で用意しますが、account-abstractionで用意されているEntryPointをそのままdeployで問題ありません。

import文だけ以下のようにパスを修正しました。

2. Paymasterの準備

account-abstractionのIPaymasterを継承して作成します。

上記は最低限の実装をしたどのようなUserOperationでも通すPaymasterです。どのようなUserOperationのスポンサーにもなってしまいます。
本来は特定のUserOperationのみスポンサーになりたいはずなので validatePaymasterUserOp()にUserOperationのpaymasterAndDataを検証する実装をすることができます。paymasterAndDataの20バイト目以降にsignatureやタイムスタンプの情報を含めてそこで利用できます。

Paymasterもdeployをします。

EntryPointとPaymasterに関してはUserOperationのsignatureやpaymasterAndDataの検証方法等に変更がない限りは基本的に1回deployしたものをずっと使うことになるかと思います。

3. ERC721コントラクトの準備

NFTのコントラクトです。ERC4337を使うための特別な実装はありません。通常のERC721を実装しました。

こちらも先にdeployしておきます。

4. Smart Contract Accountの準備

account-abstractionのSimpleAccountを参考に、今回必要な実装をしました。

IAccountと、NFTを受け取れるようにするためにIERC721Receiverを継承します。

constructorでは、EntryPointのaddressとadminというaddressを引数に取っています。adminは、signerとして許容するアカウントを想定しています。今回は validateUserOp()で、adminが署名したsignature以外の場合は弾くように実装しました。

onERC721Received()はコントラクトでERC721を受け取れるようにするために必要な実装です。

_requireFromEntryPoint()は関数の実行者をEntryPointに限定するためのものです。

execute()または executeBatch()を使って外部のコントラクトを実行します。今回はこれを使ってNFTのmintとtransferを実行します。

こちらのコントラクトはまだdeployしません。UserOperationで初回実行された際にContractFactoryがdeployしてくれます。

5. ContractFactoryの準備

先ほど作成したコントラクトをdeployするためのコントラクトです。

OpenZeppelinのCreate2を参考にコントラクトをdeployする関数を作成します。
Create2の deploy()をそのまま叩こうとすると、 SELFBALANCEという禁止されているオペコードが使われておりエラーになるので新しく作成します。

deploy()ではまず getNewAddress()でdeployするコントラクトのバイトコードとconstructorの引数 とsaltから、新しくdeployするcontract addressを求めます。saltは一意のaddressを求めるために指定します。
求めたaddressにコントラクトがdeployされていればそのaddressを返し、そうでなければdeployします。
そして accountAddresses[]にdeployしたaddressを格納していきます。

getAddress()はdeploy済みのSmart Contract Accountのaddressを取得します。 getLength ()accountAddresses[]の長さを返します。

また、 getNewAddress()は直接叩くことでdeploy前にUserOperationのsenderとなる値を求めることができます。

ContractFactoryもdeployしておきます。
Smart Contract Accountの実装を更新した際には新しいContractFactoryをdeployします。

6. depositoTo()の実行

ガス代を支払うための資金をEntryPointに送金しておきます。

今回はPaymasterを利用するのでTruffleを利用して次のように実行しました。

結果は次のように確認できます。

valueの値はUserOperationで設定するガス代関連の値から以下のようにして最低限必要な分がわかります。callGasLimit、verificationGasLimit、preVerificationGas、maxFeePerGasを値が決まったらこの値以上となる数値を指定しました。

次回、これを踏まえて実際にUserOperationを実行します。

参考