ERC6900 reference-implementation
自定义的插件分几块内容
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ Plugin interface enum ┃
// ┗━━━━━━━━ ━━━━━━━━━━━━━━━━━━━┛
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ Plugin interface event ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ Plugin interface error ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ Plugin interface funs ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ Execution functions ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ Plugin view functions ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ Plugin interface functions ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
// ┏━━━━━━━━━━━━━━━┓
// ┃ EIP-165 ┃
// ┗━━━━━━━━━━━━━━━┛
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ Internal / Private functions ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
6900-reference-implementation
https://github.com/erc6900/reference-implementation
AccountExecutor.sol
AccountLoupe.sol 账户放大镜(很形象,拿着放大镜仔细看看账户里有什么)
AccountStorage.sol
AccountStorageInitializable.sol
PluginManagerInternals.sol
UpgradeableModularAccount.sol
以下是每个合约可能的作用:
-
AccountExecutor.sol: 这个合约可能是负责执行与账户相关的操作,比如转账、调用其他合约等。Executor 通常指代执行事务的实体,所以这个合约可能包含了一些核心的逻辑,用于处理账户的交易。
-
AccountLoupe.sol: Loupe 在编程和智能合约的语境中通常指的是审视或检查工具。这个合约可能提供了一些只读的功能,用来检查账户的状态,例如余额、授权情况、交易历史等。
-
AccountStorage.sol: 这个合约很可能是用来存储账户的状态,比如余额、授权信息和其他重要数据。Storage 合约通常与逻辑合约分离,以便在升级合约时保留状态。
-
AccountStorageInitializable.sol: 这个合约可能是 AccountStorage 的一个变体,它添加了初始化功能。在可升级合约的模式中,初始化功能通常用来替代构造函数,确保合约在被代理合约使用时能正确地设置其初始状态。
-
PluginManagerInternals.sol: 这个合约可能包含了插件管理的内部逻辑。在一个模块化和可扩展的系统中,PluginManager 可能用于管理各种插件的生命周期,比如安装、更新、卸载等。
-
UpgradeableModularAccount.sol: 这个合约可能是一个可升级的账户合约,它支持模块化设计。这意味着合约的功能可以通过添加或替换模块来扩展,同时还支持通过某种升级机制来更新合约的逻辑而不丢失状态。
用于管理用户账户、权限、以及与特定功 能模块的交互。在实际的应用中,这样的系统可能用于去中心化金融(DeFi)平台、多签钱包、企业级区块链解决方案等场景。
AccountExecutor.sol
-
这段代码定义了一个名为
AccountExecutor的抽象合约,它包含了一个名为_exec的内部函数,用于向其他合约发送调用。 - 合约的主要目的是确保调用的目标不是一个插件,以避免数据不一致和意外行为。
-
如果目标合约是一个插件(通过支持
IPlugin接口来判断),那么调用会被拒绝,并抛出PluginExecutionDenied错误。
以下是 _exec 函数的工作原理:
-
检查插件接口: 使用 OpenZeppelin 的
ERC165Checker工具,_exec函数首先检查目标地址是否支持IPlugin接口。如果目标合约被认定为插件,函数会立即中断执行并抛出PluginExecutionDenied错误。 -
调用执行: 如果目标不是插件,函数会继续使用
call低级函数向目标合约发送调用。调用时,可以附带value(以太)和data(调用数据)。 -
错误处理: 如果调用失败(例如,如果被调用的合约函数中断执行并回退),
_exec函数会使用assembly块来直接将错误信息冒泡上来。这意味着调 用者会收到原始的错误信息,而不是通用的错误。
assembly 块使用 "memory-safe" 指令来确保操作是内存安全的,这是 Solidity 0.8.x 版本引入的一个特性。assembly 块中的 revert 指令用于抛出错误,它使用 result 中的数据作为错误信息。add(result, 32) 是错误信息的起始位置,mload(result) 是错误信息的长度。
总结来说,AccountExecutor 合约提供了一种安全的执行机制,以确保智能合约系统中的模块化账户不会意外地直接调用插件,从而保持系统的完整性和预期行为。
AccountLoupe.sol
这段代码定义了一个名为 AccountLoupe 的抽象合约,它实现了 IAccountLoupe 接口。该合约似乎是一个工具合约,用于检索有关账户执行功能配置、执行钩子(hooks)以及已安装插件的信息。它使用了 OpenZeppelin 的 UUPSUpgradeable 合约,这表明它是设计为可升级的,而且它与插件管理器有关,因为它引用了 IPluginManager 接口。
以下是合约的主要部分及其可能的作用:
-
getExecutionFunctionConfig: 这个函数根据传入的选择器(函数签名的前 4 个字节)返回执行函数的配置。这包括关联的插件地址、用户操作验证函数和运行时验证函数。它检查选择器是否对应于一组预定义的内部函数或插件管理器函数,如果不是,则从存储中检索相关数据。
-
getExecutionHooks: 这个函数返回与特定选择器相关联的执行钩子,包括执行前和执行后的钩子。 这些钩子可能用于执行额外的逻辑,例如验证、状态更新或事件记录。
-
getPreValidationHooks: 这个函数返回与特定选择器相关联的预验证钩子,包括用户操作预验证钩子和运行时预验证钩子。这些可能用于在实际执行操作之前进行一些检查。
-
getInstalledPlugins: 这个函数返回所有已安装插件的地址数组。这可能用于检索合约当前使用的所有插件,以便进行管理或审计。
此外,合约使用了 OpenZeppelin 的 EnumerableMap 和 EnumerableSet 实用库来管理映射和集合,这些库提供了迭代映射和集合的功能。
错误处理:
- ManifestDiscrepancy: 如果插件地址与预期不符,这个错误将被抛出。
合约使用了 assembly 块来调整动态数组的大小,这是一个低级操作,通常用于优化目的。
总结来说,AccountLoupe 合约是一个为账户提供只读访问权限的工具合约,它允许外部调用者检索有关账户执行策略和已安装插件的详细信息。这种类型的合约在复杂的智能合约系统中很有用,尤其是在需要透明度和可升级性的去中心化应用程序中。
AccountStorage.sol
这段代码是一个Solidity智能合约的一部分,它定义了一系列结构体(structs),这些结构体用于表示与模块化和可升级的以太坊账户相关的复杂状态。这个合约利用了 OpenZeppelin 的 EnumerableMap 和 EnumerableSet 库来管理集合和映射,以便能够枚举它们的元素。
让我们逐一看看每个结构体和函数:
结构体(Structs)
-
PluginData:
anyExternalExecPermitted: 表示插件是否允许任何外部执行。canSpendNativeToken: 表示插件是否可以花费原生代币。manifestHash: 插件清单的哈希值。dependencies: 插件依赖的函数引用数组。dependentCount: 作为依赖函数使用的次数。
-
PermittedExternalCallData:
addressPermitted: 指定地址是否允许。anySelectorPermitted: 是否允许任何选择器。permittedSelectors: 允许的选择器映射。
-
HookGroup:
preHooks: 执行前的钩子。associatedPostHooks: 与执行前钩子相关联的执行后钩子。postOnlyHooks: 仅在执行后的钩子。
-
SelectorData:
plugin: 实现此执行函数的插件地址。userOpValidation: 用户操作验证函数引用。runtimeValidation: 运行时验证函数引用。preUserOpValidationHooks: 执行前用户操作验证钩子。preRuntimeValidationHooks: 执行前运行时验证钩子。executionHooks: 执行函数的钩子。
-
AccountStorage:
initialized和initializing: 用于初始化状态的变量。plugins: 插件地址集合。pluginData: 插件数据映射。selectorData: 选择器数据映射。callPermitted: 允许的调用映射。permittedExternalCalls: 允许的外部调用映射。supportedIfaces: 用于ERC165自省的接口支持映射。
函数
-
getAccountStorage: 使用内联汇编和
memory-safe指令,它返回一个指向预定义存储槽_ACCOUNT_STORAGE_SLOT的AccountStorage结构体的引用。 -
getPermittedCallKey: 生成一个
bytes24类型的键,该键由地址和选择器组成,用于在callPermitted映射中标识权限。 -
toFunctionReferenceArray: 将
EnumerableMap.Bytes32ToUintMap映射转换为FunctionReference数组。这用于从映射中提取所有元素并将它们存储在内存中的数组中。
总的来说,这段代码是一个高度模块化和可升级的智能合约系统的一部分,它使用复杂的状态管理来跟踪插件、执行函数、权限和钩子。这种模式允许智能合约在维护核心状态的同时,添加新功能或改进现有功能。
AccountStorageInitializable.sol
这段代码是一个用于以太坊智能合约的初始化控制抽象合约,它确保初始化函数只能在合约生命周期中被正确地调用一次。这是为了防止在合约升级时重置或破坏合约的状态。
关键点如下:
- AlreadyInitialized 和 AlreadyInitializing 错误:用于防止重复初始化。
- initializer 修饰符:确保函数只在合约尚未初始化时调用。
- _disableInitializers 函数:用于在初始化后禁用进一步的初始化调用。
这个模式对于可升级的智能合约特别重要,因为它们需要在升级后保持状态不变,同时允许新的初始化逻辑执行。
这段代码定义了一个名为 AccountStorageInitializable 的抽象合约,它是一个使用初始化模式的合约,确保某些函数只能在合约生命周期中被调用一次。这是一个常见的模式,用于可升级合约,以确保在升级过程中,状态变量可以被正确地初始化,而不会被重复初始化。这个抽象合约使用了之前定义的 AccountStorage 结构体和 getAccountStorage 函数。
错误定义
- AlreadyInitialized: 如果尝试对已经初始化的合约进行初始化,则会抛出这个错误。
- AlreadyInitializing: 如果在初始化过程中再次尝试初始化,则会抛出这个错误。
Modifier
- initializer:
这个修饰符用于确保它修饰的函数只能在合约的生命周期中被调用一次。它首先检查是否是最顶层的调用(即没有其他初始化函数正在被执行),然后检查合约是否已经初始化。如果这些检查通过,它将设置
initializing标志,执行函数,然后清除initializing标志。如果检查失败,则会抛出AlreadyInitialized错误。
函数
- _disableInitializers:
这个内部函数用于禁用初始化函数,以防止它们在合约生命周期中被再次调用。它首先检查是否正在执行初始化过程,如果是,则抛出
AlreadyInitializing错误。如果不是,它会将_storage.initialized设置为uint8的最大值,从而永久禁用初始化函数。
这个合约的设计使得它可以被用作其他合约的基础,这些合约需要确保它们的初始化逻辑只被执行一次,例如在部署时或在合约升级后。这是一个关键的安全特性,用于防止在合约升级过程中的不当初始化,可能会导致合约状态被意外地篡改。
PluginManagerInternals.sol
这个智能合约 PluginManagerInternals 是一个抽象合约,它实现了 IPluginManager 接口的一部分,用于管理插件的内部逻辑。这个合约包含了添加、移除和管理插件的功能,以及与插件相关的验证函数和钩子(hooks)。
合约使用了OpenZeppelin的库和结构,例如 EnumerableMap 和 EnumerableSet,以及自定义的 FunctionReferenceLib 库来处理函数引用。
合约定义了一系列错误类型,用于处理各种异常情况,例如数组长度不匹配、插件已安装、插件未安装、依赖项不匹配等。
以下是合约中的一些关键点:
-
修饰符(Modifiers):
notNullFunction: 确保传递的函数引用不是空的。notNullPlugin: 确保传递的插件地址不是空地址。
-
内部函数:
_setExecutionFunction: 为特定的函数选择器设置执行插件。_removeExecutionFunction: 移除特定选择器的执行插件。_addUserOpValidationFunction和_removeUserOpValidationFunction: 添加和移除用户操作验证函数。_addRuntimeValidationFunction和_removeRuntimeValidationFunction: 添加和移除运行时验证函数。_addExecHooks和_removeExecHooks: 添加和移除执行钩子。_installPlugin: 安装插件,并根据插件的清单更新账户存储。_uninstallPlugin: 卸载插件,并清理相关的账户存储。_addOrIncrement和_removeOrDecrement: 用于在EnumerableMap中添加或递增键值对,或者移除或递减键值对。_isValidPluginManifest: 验证插件的清单哈希是否有效。_resolveManifestFunction: 解析清单中的函数引用,将它们映射到实际的函数引用。
-
事件(Events):
PluginInstalled: 当插件安装成功时触发。PluginUninstalled: 当插件卸载成功时触发。
这个合约是一个复杂的插件管理系统的一部分,它允许智能合约动态地添加功能和逻辑,而无需更改或重新部署主合约。这种模式是以太坊智能合约开发中的模块化和灵活性的一个示例。
插件示例
ModularSessionKeyPlugin
这个智能合约 ModularSessionKeyPlugin 是一个插件,用于管理一个模块化账户的临时所有权。它允许指定的外部拥有账户(EOA)或智能合约在一定时间内拥有模块化账户的权限。这个插件主要用于演示 ERC-6900 功能,并不适合生产环境使用。
合约的关键特性和功能包括:
-
添加和移除会话密钥:
addSessionKey: 添加一个新的会话密钥,设置其允许的选择器以及有效时间范围。removeSessionKey: 移除一个已存在的会话密钥。addSessionKeyBatch: 批量添加会话密钥。removeSessionKeyBatch: 批量移除会话密钥。
-
查看函数:
getSessionDuration: 获取特定会话密钥的有效时间范围。getSessionKeysAndSelectors: 获取账户的所有会话密钥和它们允许的选择器。
-
插件安装和卸载:
onInstall: 安装插件时调用,可以初始化会话密钥。onUninstall: 卸载插件时调用,会清除所有会话密钥。
-
验证函数:
userOpValidationFunction: 用于验证用户操作的签名是否有效。runtimeValidationFunction: 用于运行时验证调用者是否有权执行操作。
-
插件清单和元数据:
pluginManifest: 提供插件的清单信息,包括执行函数、验证函数和依赖项。pluginMetadata: 提供插件的元数据,如名称、版本和作者。
-
会话信息结构 (
SessionInfo):- 存储会话密钥的有效时间范围。
-
事件:
SessionKeyAdded: 当添加新的会话密钥时触发。SessionKeyRemoved: 当移除会话密钥时触发。SessionKeysAdded: 当批量添加会话密钥时触发。SessionKeysRemoved: 当批量移除会话密钥时触发。
此外,合约使用了 OpenZeppelin 的 ECDSA 库来处理签名,以及 EnumerableSet 来管理会话密钥集合。合约还使用了 PluginStorageLib 库来管理关联存储。
最后,合约遵循 EIP-165 标准,实现了 supportsInterface 函数,以便可以查询合约是否支持特定的接口。
这个合约的设计旨在演示如何通过插件为模块化智能合约添加临时权限管理功能。通过使用会话密钥,账户的临时所有者可以在指定的时间范围内,使用指定的函数选择器来执行操作。这增加了账户的灵活性和安全性,因为可以严格控制谁有权在何时进行何种操作。
TokenSessionKeyPlugin
这个智能合约 TokenSessionKeyPlugin 是 ModularSessionKeyPlugin 的子插件,用于实现允许调用 ERC-20 transferFrom 函数的会话密钥的逻辑。它允许会话密钥的持有者通过 transferFromSessionKey 函数访问模块化智能合约账户(MSCA),这个函数会调用 PluginExecutor 合 约中的 executeFromPluginExternal 函数。
合约的关键特性和功能包括:
-
执行函数:
transferFromSessionKey: 允许会话密钥的持有者调用目标 ERC-20 合约的transferFrom函数。
-
插件安装和卸载:
onInstall: 安装插件时调用,但在此合约中没有实现任何逻辑。onUninstall: 卸载插件时调用,但在此合约中没有实现任何逻辑。
-
插件清单 (
pluginManifest):- 提供插件的清单信息,包括执行函数、验证函数、依赖项和允许的外部调用。
- 在这个插件中,目标 ERC-20 合约和
transferFrom函数的选择器是硬编码的,因为pluginManifest函数要求未来不改变允许的外部调用信息。
-
插件元数据 (
pluginMetadata):- 提供插件的元数据,如名称、版本和作者。
-
EIP-165 支持:
supportsInterface: 实现了 EIP-165 标准,以便可以查询合约是否支持特定的接口。
这个合约的设计允许会话密钥的持有者在指定的时间范围内,使用 transferFrom 函数来转移代币。它通过扩展 ModularSessionKeyPlugin 的功能,为会话密钥添加了特定的用例——允许调用 ERC-20 合约的 transferFrom 方法。这种设计模式允许开发者根据需要创建更多的子插件,以实现对不同类型外部调用的权限管理。