はじめに
2025年2月11日に発売される、『DApps開発入門』という本を書いているかるでねです。
今回は、最小限のモジュラースマートアカウントの仕組みを提案しているERC7579についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIPについてまとめています。
概要
モジュラースマートアカウントその各モジュールの最小限のインターフェースを定義している規格です。
ERC7579では、アカウントに以下の3つのインターフェースを定義しています。
- execution
- config
- fallback
また、ERC7579に準拠したアカウントは、以下の規格に準拠している必要があります。
- EIP165
- 1271
- EIP2771
- EIP4337
動機
従来のスマートアカウントにモジュールアーキテクチャを採用することで以下の利点を得ています。
-
拡張性
- 機能をモジュールに分割することで、新機能の追加や改良がしやすくなる。
-
将来性の確保
- 新しいユースケースに対応しやすい設計を実現できる。
-
カスタマイズ性
- 開発者やユーザーがアカウントの機能を自由に変更できる。
しかし、現状各プロジェクトが独自の方法でスマートアカウントを実装している ため、モジュールの断片化や特定のベンダー (プロバイダー) に依存した実装になってしまっています。
ERC7579の標準化を進めることで、以下のようなメリットを提供します。
- 異なるスマートアカウント間でモジュールを共通利用できる。
- 異なるウォレットアプリやSDKでスマートアカウントを利用可能にする。
- 特定のプロバイダーへの依存を防ぐ。
ただし、標準化によってスマートアカウントの実装が制約されると技術革新を阻害する可能性があります。
そのため、最小限の実装にして相互運用性を確保することを目標としています。
定義
Smart Account
スマートコントラクトで作られたアカウントで、複数のモジュールを組み合わせることで機能を拡張できます。
Module
スマートアカウントの特定の機能を提供する独立したスマートコントラクトです。
- Validator
取引が正しいかどうかをチェックするためのモジュール。
例えば、署名の検証などを行います。
- Executor
スマートアカウントの代わりにトランザクションを実行するモジュール。
- Fallback Handler
スマートアカウントが対応していないリクエストを処理するモジュール。
EntryPoint
ERC4337に基づく、AccountAbstractionのための信頼できるコントラクト。
Validation
取引を実行してもよいかどうかを判断するプロセス。
ERC4337を使う場合はvalidateUserOp
関数が使われる。
ERC4337ではvalidateUserOp
関数が該当します。
Execution
取引を実際に実行するプロセス。
ERC4337を使う場合はuserOp.callData
をEntryPointコントラクトから呼び出して実行される。
アカウントの仕様
検証
ERC7579では、どのバリデーターを使うかはスマートアカウントの実装に任せられている。
例えば、ERC4337を使う場合は、トランザクションのuserOp.signature
にバリデーターの情報を入れることができる。
セキュリティのために、バリデーション前にデータのチェックを行う必要があります。
実行
スマートアカウントがトランザクションを実行できるように、以下のインターフェースを実装する必要があります。
interface IExecution {
/**
* @dev Executes a transaction on behalf of the account. MAY be payable.
* @param mode The encoded execution mode of the transaction.
* @param executionCalldata The encoded execution call data.
*
* MUST ensure adequate authorization control: e.g. onlyEntryPointOrSelf if used with ERC-4337
* If a mode is requested that is not supported by the Account, it MUST revert
*/
function execute(bytes32 mode, bytes calldata executionCalldata) external;
/**
* @dev Executes a transaction on behalf of the account. MAY be payable.
* This function is intended to be called by Executor Modules
* @param mode The encoded execution mode of the transaction.
* @param executionCalldata The encoded execution call data.
*
* @return returnData An array with the returned data of each executed subcall
*
* MUST ensure adequate authorization control: i.e. onlyExecutorModule
* If a mode is requested that is not supported by the Account, it MUST revert
*/
function executeFromExecutor(bytes32 mode, bytes calldata executionCalldata)
external
returns (bytes[] memory returnData);
}
execute
スマートアカウントが直接トランザクションを実行するための関数。
mode
に指定された 実行モードに従って処理を行う。
- 単発の呼び出し。
- バッチ処理。
- delegatecall (他のコントラクトのコードをスマートアカウント内で実行)。
- staticcall (読み取り専用の実行)。
executionCalldata
にトランザクションの実行に必要なデータが含まれます。
ERC4337を使う場合はonlyEntryPointOrSelf
で制御するなど適切な権限管理が必要です。
executeFromExecutor
スマートアカウントの代わりにExecutorモジュールを介してトランザクションを実行するための関数。
onlyExecutorModule
というアクセス制御を適用し、Executorモジュール以外からの実行を防ぐ必要があります。
executeUserOp
/**
* @dev ERC-4337 executeUserOp according to ERC-4337 v0.7
* This function is intended to be called by ERC-4337 EntryPoint.sol
* @param userOp PackedUserOperation struct (see ERC-4337 v0.7+)
* @param userOpHash The hash of the PackedUserOperation struct
*
* MUST ensure adequate authorization control: i.e. onlyEntryPoint
*/
function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external;
スマートアカウントは、ERC4337と互換性を持たせるために、executeUserOp
関数を任意で実装することができます。
この関数はERC4337に基づくメタトランザクションを実行できる関数です。
以下のように、userOp.callData
の先頭 4 バイト (関数の識別子) はexecuteUserOp.selector
であり、それを除いたuserOp.callData[4:]
を実行します。
(bool success, bytes memory innerCallRet) = address(this).delegatecall(userOp.callData[4:]);
このようにdelegatecall
を使うことで、スマートアカウントを実行者としてトランザクションを実行可能です。
実行モード
mode
は32
バイトの値で以下のような構造になっています。
フィールド名 | バイト数 | 説明 |
---|---|---|
callType |
1 | 呼び出しの種類 (single call, バッチ, delegatecall , staticcall ) 。 |
execType |
1 | 失敗時の処理方法 (失敗時にrevert するかどうか)。 |
unused |
4 | 将来の標準化のための予約領域。 |
modeSelector |
4 | 拡張用のモード識別子。 |
modePayload |
22 | 追加データ。 |
-
callType
(1バイト)-
0x00
- 単発の呼び出し (single call)。
-
0x01
- バッチ処理 (batch call)。
-
0xfe
- 読み取り専用の staticcall。
-
0xff
- delegatecall。
-
-
execType
(1バイト)-
0x00
- 失敗したら
revert
する。
- 失敗したら
-
0x01
- 失敗しても
revert
せず、エラー処理を行う。
- 失敗しても
-
データのエンコード
実行データ(executionCalldata
)は、トランザクションの種類ごとに以下のようにエンコードされます。
-
単発の呼び出し
abi.encodePacked(target, value, callData)
-
delegatecall
abi.encodePacked(target, callData)
-
バッチ処理
-
Execution[]
の配列をabi.encode
でエンコード。
-
struct Execution {
address target;
uint256 value;
bytes callData;
}
実装時のポイント
-
execute()
とexecuteFromExecutor()
は必須で実装します。 - アカウントがサポートしていない実行モードを要求された場合、
revert
する必要があります。
アカウントの管理
ERC7579に準拠するスマートアカウントは、以下のIAccountConfig
インターフェースを実装する必要があります。
interface IAccountConfig {
/**
* @dev Returns the account id of the smart account
* @return accountImplementationId the account id of the smart account
*
* MUST return a non-empty string
* The accountId SHOULD be structured like so:
* "vendorname.accountname.semver"
* The id SHOULD be unique across all smart accounts
*/
function accountId() external view returns (string memory accountImplementationId);
/**
* @dev Function to check if the account supports a certain execution mode (see above)
* @param encodedMode the encoded mode
*
* MUST return true if the account supports the mode and false otherwise
*/
function supportsExecutionMode(bytes32 encodedMode) external view returns (bool);
/**
* @dev Function to check if the account supports a certain module typeId
* @param moduleTypeId the module type ID according to the ERC-7579 spec
*
* MUST return true if the account supports the module type and false otherwise
*/
function supportsModule(uint256 moduleTypeId) external view returns (bool);
}
accountId
スマートアカウントの一意の識別子(ID)を取得する関数。
supportsExecutionMode
スマートアカウントが特定の 実行モード (execution mode) に対応しているかを確認する関数。
実行モードについては、こちらを確認してください。
supportsModule
スマートアカウントが特定のモジュールタイプをサポートしているかを確認する関数。
- Validation Moduleの場合、
moduleTypeId
は1
。 - Executor Moduleの場合、
moduleTypeId
は2
。 - Fallback Handler Moduleの場合、
moduleTypeId
は3
。
モジュールの管理
ERC7579では、スマートアカウントにモジュールを追加・削除できるように以下のIModuleConfig
インターフェースを定義する必要があります。
interface IModuleConfig {
event ModuleInstalled(uint256 moduleTypeId, address module);
event ModuleUninstalled(uint256 moduleTypeId, address module);
/**
* @dev Installs a Module of a certain type on the smart account
* @param moduleTypeId the module type ID according to the ERC-7579 spec
* @param module the module address
* @param initData arbitrary data that may be required on the module during `onInstall`
* initialization.
*
* MUST implement authorization control
* MUST call `onInstall` on the module with the `initData` parameter if provided
* MUST emit ModuleInstalled event
* MUST revert if the module is already installed or the initialization on the module failed
*/
function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external;
/**
* @dev Uninstalls a Module of a certain type on the smart account
* @param moduleTypeId the module type ID according the ERC-7579 spec
* @param module the module address
* @param deInitData arbitrary data that may be required on the module during `onInstall`
* initialization.
*
* MUST implement authorization control
* MUST call `onUninstall` on the module with the `deInitData` parameter if provided
* MUST emit ModuleUninstalled event
* MUST revert if the module is not installed or the deInitialization on the module failed
*/
function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external;
/**
* @dev Returns whether a module is installed on the smart account
* @param moduleTypeId the module type ID according the ERC-7579 spec
* @param module the module address
* @param additionalContext arbitrary data that may be required to determine if the module is installed
*
* MUST return true if the module is installed and false otherwise
*/
function isModuleInstalled(uint256 moduleTypeId, address module, bytes calldata additionalContext) external view returns (bool);
}
installModule
スマートアカウントに 新しいモジュールを追加する関数。
uninstallModule
スマートアカウントから 既存のモジュールを削除する関数。
isModuleInstalled
指定されたモジュールがスマートアカウントにインストールされているかどうかを確認する関数。
Hook
スマートアカウントがトランザクションを実行する前後で特定の処理を実行するための仕組みです。
スマートアカウントがトランザクションを実行するときは、単純に対象のコントラクトやアドレスにcall
しますが、フックを利用すると以下のようなことが可能になります。
- 実行前に特定の条件をチェック(例: ユーザーが十分な権限を持っているか)
- 実行後にログを記録(例: 取引履歴を保存する)
- モジュールのインストール/アンインストール時の追加処理
- 取引の成功・失敗時にカスタムエラーハンドリング
フックは 「トランザクションの前後」 で呼び出される関数として定義されます。
- トランザクションの前 (
preCheck
)-
execute
関数かexecuteFromExecutor
関数実行前にpreCheck
関数を呼ぶことが必須です。 -
installModule
やuninstallModule
の前にもpreCheck
関数を呼ぶことが推奨されます。
-
- トランザクションの後 (
postCheck
)-
execute
関数かexecuteFromExecutor
関数実行前にpostCheck
関数を呼ぶことが必須です。 -
installModule
やuninstallModule
の前にもpostCheck
関数を呼ぶことが推奨されます。
-
ERC1271 Forwarding
ERC1271は、スマートコントラクトが署名を検証できるようにする標準規格です。
EOAでは、メッセージに署名してecrecover
関数で検証できますが、スマートコントラクトは秘密鍵を持っていないため署名できません。
そこで、ERC1271ではisValidSignature
関数を使用して、外部のバリデーター(署名検証モジュール)を利用できるようにします。
isValidSignature
function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4);
上記のisValidSignature
関数は以下のように動作します。
-
hash
(検証したいメッセージのハッシュ)を受け取る。 -
signature
(検証する署名)を受け取る。 - 署名が有効なら
0x1626ba7e
を返す(MAGIC_VALUE
)。 - 署名が無効ならrevert`するか別の値を返す。
Fallback
スマートアカウントに定義されていない関数が呼び出されたときに、それを処理する仕組みです。
ERC7579では、フォールバックを外部のフォールバックハンドラーに転送することができます。
Upgradeableなコントラクトもこのfallbackの仕組みを活用しています。
ERC165
ERC165は、スマートコントラクトがどのインターフェースを実装しているかを識別するための標準規格です。
これにより、外部のコントラクトやツールが、特定のコントラクトがERC7579を実装しているかを簡単に判別できるようになります。
モジュールの仕様
ERC7579では、スマートアカウントの機能を拡張するための「モジュール (Modules)」 を定義しています。
スマートアカウントは、さまざまなモジュールを組み合わせることで、取引の検証や実行、フォールバック処理、追加のチェック処理などを行うことができます。
モジュールは、特定の役割ごとに分類され、それぞれ一意のタイプID (module type ID)を持っています。
モジュールの種類とタイプID
モジュール名 | タイプ ID | 役割 |
---|---|---|
Validator | 1 |
取引の署名を検証 |
Executor | 2 |
取引を実行 |
Fallback Handler | 3 |
未対応の関数を処理 |
Hook | 4 |
取引の前後でカスタム処理を実行 |
※1つのモジュールが複数のタイプ ID を持つことも可能です。
例えば、あるモジュールが「Validator」と「Executor」の両方の機能を持つこともあり得ます。
モジュールのインターフェース
全てのモジュールは、IModule
インターフェースを実装する必要があります。
interface IModule {
/**
* @dev This function is called by the smart account during installation of the module
* @param data arbitrary data that may be required on the module during `onInstall` initialization
*
* MUST revert on error (e.g. if module is already enabled)
*/
function onInstall(bytes calldata data) external;
/**
* @dev This function is called by the smart account during uninstallation of the module
* @param data arbitrary data that may be required on the module during `onUninstall` de-initialization
*
* MUST revert on error
*/
function onUninstall(bytes calldata data) external;
/**
* @dev Returns boolean value if module is a certain type
* @param moduleTypeId the module type ID according the ERC-7579 spec
*
* MUST return true if the module is of the given type and false otherwise
*/
function isModuleType(uint256 moduleTypeId) external view returns(bool);
}
onInstall()
スマートアカウントにモジュールを追加する関数。
onUninstall()
スマートアカウントからモジュールを削除する関数。
isModuleType()
モジュールが指定したタイプ ID に対応しているかを確認する関数。
Validator
バリデーターは、取引の署名を検証する役割を持つモジュールです。
ERC4337 に対応した validateUserOp()
を実装する必要があります。
interface IValidator is IModule {
/**
* @dev Validates a UserOperation
* @param userOp the ERC-4337 PackedUserOperation
* @param userOpHash the hash of the ERC-4337 PackedUserOperation
*
* MUST validate that the signature is a valid signature of the userOpHash
* SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch
*/
function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256);
/**
* @dev Validates a signature using ERC-1271
* @param sender the address that sent the ERC-1271 request to the smart account
* @param hash the hash of the ERC-1271 request
* @param signature the signature of the ERC-1271 request
*
* MUST return the ERC-1271 `MAGIC_VALUE` if the signature is valid
* MUST NOT modify state
*/
function isValidSignatureWithSender(address sender, bytes32 hash, bytes calldata signature) external view returns (bytes4);
}
validateUserOp
取引 (UserOperation
) の署名を検証する関数。
署名が無効の場合はERC4337のSIG_VALIDATION_FAILED
を返します。
isValidSignatureWithSender
ERC1271に準拠した署名の検証を行う関数。
署名が正しければMAGIC_VALUE
を返すします。
Executor
エグゼキューターは、スマートアカウントの代わりに取引を実行する役割を持つモジュールです。
executeFromExecutor()
を使って取引を実行します。
スマートアカウントが直接execute
関数を呼ぶのではなく、エグゼキューターモジュールがexecuteFromExecutor
関数を実行します。
スマートアカウントの権限を適切に管理し、不正なエグゼキューターが取引を実行できないようにする必要があります。
Fallback Handler
フォールバックハンドラーは、スマートアカウントが対応していない関数呼び出しを処理する役割を持つモジュールです。
call
またはstaticcall
を使って処理を行います。
msg.sender
を正しく管理するためにERC2771の_msgSender()
を使用する必要があります。
Hook
フックは、トランザクションの前後でカスタム処理を実行するモジュールです。
interface IHook is IModule {
/**
* @dev Called by the smart account before execution
* @param msgSender the address that called the smart account
* @param value the value that was sent to the smart account
* @param msgData the data that was sent to the smart account
*
* MAY return arbitrary data in the `hookData` return value
*/
function preCheck(address msgSender, uint256 value, bytes calldata msgData) external returns (bytes memory hookData);
/**
* @dev Called by the smart account after execution
* @param hookData the data that was returned by the `preCheck` function
*
* MAY validate the `hookData` to validate transaction context of the `preCheck` function
*/
function postCheck(bytes calldata hookData) external;
}
preCheck
取引の前に実行される関数。
署名や権限チェックなどを実行することができます。
postCheck
取引の後に実行される関数。
エラーログの記録や追加の処理を実行することができます。
補足
最小限の設計アプローチ
できるだけシンプルな設計にする理由
スマートアカウントは新しい概念であり、まだ最適な設計が整っているわけではありません。
ルールを決めすぎると将来的に柔軟な設計ができなくなる可能性があります。
異なるプロジェクトが異なる方法でスマートアカウントを作れるようにするべきという観点から、ERC7579では「最小限のインターフェース」だけを標準化 し、それ以上の仕様は開発者に任せる方針を取っています。
既存のスマートアカウントを参照
ERC7579の設計は、すでに運用されている以下のスマートアカウントから得られた知見に基づいています。
- Gnosis Safe
- Biconomy Smart Accounts
- ERC4337を使用したスマートアカウント
モジュールの相互運用性
異なるスマートアカウントでも、同じモジュールを再利用できるようにしています。
「バリデーター」「エグゼキューター」「フック」などのモジュールを標準化することで、開発者は新しいアカウントを作るときにゼロからすべてを作る必要がなくなります。
異なるスマートアカウント同士でも共通のモジュールを活用できるようにしています。
拡張性の確保
なぜ拡張機能(Extensions)が必要なのか?
最小限の仕様にとどめる一方で、スマートアカウントの設計やユースケースは進化していくため、新しい機能を追加できるようにする必要があります。
例えば、
- あるプロジェクトはERC4337に依存しないスマートアカウントを作りたいかもしれない。
- あるプロジェクトはより高度なアクセス管理を追加したいかもしれない。
このような 「標準には含まれないが、多くのアカウントで共通する機能」 は、拡張機能として別のERCで定義することができます。
拡張機能の提案方法
新しい機能を標準化したい場合は、**「[FEATURE] Extension for ERC-7579」**のような形式で新しいERCを提案することが推奨されます。
例えば、「ERC7579の拡張機能として新しい署名検証方法を追加する」や「ERC7579の拡張機能として、新しい取引の実行方法を追加する」などです。
こうすることで、ERC7579のコア仕様を変更することなく、新しい機能を追加できます。
実行モードの設計
実行モードをbytes32
にする理由
ERC7579では、実行モード(execution mode)をbytes32
形式に統一しています。
これは、取引の種類ごとに異なる関数を作るのではなく、1つの関数で柔軟に処理できるようにするため です。
モジュールの種類を分ける理由
スマートアカウントの 「Validator」「Executor」などのモジュールを明確に分類することが重要です。
例えば、Validatorは取引の署名を検証する役割ですが、もしExecutorとしても動作するようになってしまうと、バリデーターが勝手に取引を実行できてしまうリスクがあります。
そのため、各モジュールの役割を明確に分け、セキュリティを確保することが重要です。
アカウントID (accountId
) の設計
ERC7579では、スマートアカウントを識別するためにaccountId
関数を定義しています。
以下の理由からaccountId
関数を使用しています。
- フロントエンドでスマートアカウントの種類を判別するのに役立つ。
- ERC165のようなインターフェース検出よりも、柔軟でわかりやすい。
- Keccak256ハッシュではなく、人間が読める形式でIDを取得できる。
ERC4337との関係
ERC7579は現在ERC4337に依存しています。
しかし、将来的には「ERC4337の仕組みではないスマートアカウント」も登場する可能性があるため、その場合はERC4337の部分を拡張機能として分離する予定です。
互換性
すでにデプロイされているスマートアカウントは、コントラクトをアップグレードすることでERC7579に準拠することができます。
コントラクトのアップグレードができない場合でも、 fallback handlerとしてERC7579に準拠したコントラクトを追加するなどして互換性を持たせることができます。
実装
以下にインターフェースの参考実装コードが格納されています。
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.21;
interface IERC7579Account {
// MUST be emitted when a module is installed
event ModuleInstalled(uint256 moduleTypeId, address module);
// MUST be emitted when a module is uninstalled
event ModuleUninstalled(uint256 moduleTypeId, address module);
/**
* @dev Executes a transaction on behalf of the account.
* @param mode The encoded execution mode of the transaction. See ModeLib.sol for details
* @param executionCalldata The encoded execution call data
*
* MUST ensure adequate authorization control: e.g. onlyEntryPointOrSelf if used with ERC-4337
* If a mode is requested that is not supported by the Account, it MUST revert
*/
function execute(bytes32 mode, bytes calldata executionCalldata) external;
/**
* @dev Executes a transaction on behalf of the account.
* This function is intended to be called by Executor Modules
* @param mode The encoded execution mode of the transaction. See ModeLib.sol for details
* @param executionCalldata The encoded execution call data
*
* MUST ensure adequate authorization control: i.e. onlyExecutorModule
* If a mode is requested that is not supported by the Account, it MUST revert
*/
function executeFromExecutor(bytes32 mode, bytes calldata executionCalldata)
external
returns (bytes[] memory returnData);
/**
* @dev ERC-1271 isValidSignature
* This function is intended to be used to validate a smart account signature
* and may forward the call to a validator module
* @param hash The hash of the data that is signed
* @param data The data that is signed
*
* MAY forward the call to a validator module
* The validator module MUST be called with isValidSignatureWithSender(address sender, bytes32 hash, bytes signature)
* with sender being the msg.sender of this function
* MUST sanitize the data parameter to before forwarding it to the validator module
*/
function isValidSignature(bytes32 hash, bytes calldata data) external view returns (bytes4);
/**
* @dev Returns the account id of the smart account
* @return accountImplementationId the account id of the smart account
*
* MUST return a non-empty string
* The accountId SHOULD be structured like so:
* "vendorname.accountname.semver"
* The id SHOULD be unique across all smart accounts
*/
function accountId() external view returns (string memory accountImplementationId);
/**
* @dev Function to check if the account supports a certain execution mode (see above)
* @param encodedMode the encoded mode
*
* MUST return true if the account supports the mode and false otherwise
*/
function supportsExecutionMode(bytes32 encodedMode) external view returns (bool);
/**
* @dev Function to check if the account supports a certain module typeId
* @param moduleTypeId the module type ID according to the ERC-7579 spec
*
* MUST return true if the account supports the module type and false otherwise
*/
function supportsModule(uint256 moduleTypeId) external view returns (bool);
/**
* @dev Installs a Module of a certain type on the smart account
* @param moduleTypeId the module type ID according to the ERC-7579 spec
* @param module the module address
* @param initData arbitrary data that may be required on the module during `onInstall`
* initialization.
*
* MUST implement authorization control
* MUST call `onInstall` on the module with the `initData` parameter if provided
* MUST emit ModuleInstalled event
* MUST revert if the module is already installed or the initialization on the module failed
*/
function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external;
/**
* @dev Uninstalls a Module of a certain type on the smart account
* @param moduleTypeId the module type ID according the ERC-7579 spec
* @param module the module address
* @param deInitData arbitrary data that may be required on the module during `onInstall`
* initialization.
*
* MUST implement authorization control
* MUST call `onUninstall` on the module with the `deInitData` parameter if provided
* MUST emit ModuleUninstalled event
* MUST revert if the module is not installed or the deInitialization on the module failed
*/
function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external;
/**
* @dev Returns whether a module is installed on the smart account
* @param moduleTypeId the module type ID according the ERC-7579 spec
* @param module the module address
* @param additionalContext arbitrary data that may be required to determine if the module is installed
*
* MUST return true if the module is installed and false otherwise
*/
function isModuleInstalled(uint256 moduleTypeId, address module, bytes calldata additionalContext)
external
view
returns (bool);
}
セキュリティ
delegatecallに関するセキュリティリスク
安全でないコントラクトにdelegatecall
するとストレージが改ざんされる恐れがあります。
攻撃者が悪意のあるモジュールを追加してストレージを書き換えることで、資産を盗んだり、アカウントの管理者を変更したりできてしまいます。
この問題には、以下の方法で対処できます。
- 信頼できるコントラクトだけに
delegatecall
を許可する。 -
onlyTrustedModules
のようなアクセス制御を実装する。 -
delegatecall
先のコントラクトが想定された動作をするかを事前にチェックする。
モジュールのインストール/アンインストール時の攻撃リスク
モジュールのonInstall
関数やonUninstall
関数の中で、攻撃者が意図しないコールバックを発生させてリエントランシー攻撃をする可能性があります。
リエントランシー攻撃とは?
攻撃者がonInstall
関数の中で再びinstallModule
関数を呼び出すような攻撃。
これにより、意図しないステートの変更が発生してモジュールの制御を奪われる可能性があります。
また、悪意のあるモジュールがonUninstall
関数の中でrevert
すると、アンインストールできなくなる可能性があります。
これにより、アカウントが「削除できないモジュール」を持ってしまう可能性があります。
この問題には、以下の方法で対処できます。
-
onInstall
関数とonUninstall
関数の中で外部コールを最小限にする。 -
reentrancyGuard
を実装して再帰的な呼び出しを防ぐ。 - 強制アンインストールの仕組みを導入する(一定時間が経過すると削除可能にするなど)。
複数のモジュールを扱う際の管理ミス
スマートアカウントには、複数のモジュールがインストール可能ですが、一部のモジュールは1つしかアクティブにできない場合があります。
例えば、フォールバックハンドラーは1つしか存在できないので、新しいハンドラーの追加時に古いハンドラーが正しく削除されないと、不要な機能が残る可能性があります。
この問題には、以下の方法で対処できます。
- 「1つだけ存在できるモジュール」については、古いモジュールを明示的に削除する処理を実装する。
- インストール前にすでに存在するモジュールを確認する。
-
isModuleInstalled
関数を実装する。
フォールバックハンドラーの認可ミス
フォールバックハンドラーは、未定義の関数が呼び出された際に処理を担当するモジュールです。
しかし、認可の設定を誤ると、攻撃者がスマートアカウントのフォールバック機能を悪用して資産を盗める可能性があります。
この問題には、以下の方法で対処できます。
- フォールバックハンドラーが
msg.sender
ではなく_msgSender()
を使うようにする(ERC2771に対応)。 - フォールバックハンドラー内で、意図しない関数が呼ばれないようにチェックを行う。
悪意のあるHooksによる攻撃
フックがpreCheck
関数内でrevert
すると、取引が実行できなくなります。
また、フックがpostCheck
関数で意図しないステート変更を行うと、資産が漏洩する可能性があります。
この問題には、以下の方法で対処できます。
- 「信頼できるフック」だけを許可する(ホワイトリスト管理)。
-
preCheck
関数が失敗した場合にリカバリーできる仕組みを作る。
自己呼び出し(address(this)
)を利用したバッチ処理の問題
スマートアカウントはinstallModule
関数などの設定変更機能を持っていますが、これをaddress(this)
から呼び出すことで、本来許可されていない設定変更が行われる可能性があります。
例えば、address(this).installModule(...)
を呼び出すことで、本来認可されていない操作を強制的に実行できる可能性や、外部からのアクセス制御が厳しくても自己呼び出しを使うことで回避できてしまう可能性があります。
この問題には、以下の方法で対処できます。
- 自己呼び出しを使った設定変更を禁止する。
-
msg.sender != address(this)
のようなチェックを入れる。
引用
zeroknots (@zeroknots), Konrad Kopp (@kopy-kat), Taek Lee (@leekt), Fil Makarov (@filmakarov), Elim Poon (@yaonam), Lyu Min (@rockmin216), "ERC-7579: Minimal Modular Smart Accounts [DRAFT]," Ethereum Improvement Proposals, no. 7579, December 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7579.
最後に
今回は「最小限のモジュラースマートアカウントの仕組みを提案しているERC7579」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!