作者:Seungmin Jeon 来源:medium 翻译:善欧巴,金色财经
首尔国立大学区块链学院的 Decipher Open Source Warriors 团队撰写了一篇关于 ERC-6900 的文章。本文基于代码进行分析,从提案的背景到实现的方法和意义。
简介:ERC-4337 的局限性
ERC-4337是一个标准,允许在不改变以太坊客户端的情况下顺利使用合约钱包(账户),通过一个名为“用户操作”的对象进行额外的验证,取代传统交易。遵循 ERC-4337 标准的合约账户可以包含各种功能,例如支付 Gas 费用的 Paymaster、批量交易、BLS 签名聚合、社交恢复和会话密钥。
与传统的 EOA(外部拥有帐户)相比,这可以实现更高水平的用户体验。例如,使用ERC-4337的paymaster,用户可以使用ERC-20代币而不是ETH来支付gas费,或者让协议支付。此外,如果用户可以通过会话密钥临时将其帐户委托给协议,则不必为每笔交易单击按钮,他们只需单击一下即可与协议进行交互。
然而,ERC-4337的提出是为了在不改变协议的情况下实现账户抽象,因此并没有定义智能合约账户应该采取什么形式。于是,各种形式的合约账户被提出,导致以下两个问题:
1.不同账户形式导致的兼容性问题
目前,各个公司(例如ZeroDev、Biconomy)都提供SDK形式的合约账户,但每个公司提供的账户形式都不同,导致兼容性问题。这使得支持合约钱包的应用程序的用户很难在应用程序之外使用这些钱包,也使得应用程序很难容纳各种合约账户的用户。换句话说,虽然通过 ERC-4337 改进了 UX,但它具有将用户锁定在应用内的副作用。
2.账户的扩展性问题
通过 ERC-4337 提供的大多数功能都在账户内运行,但由于账户采用智能合约的形式,因此更改和可扩展性都受到限制。虽然可以对账户应用Proxy结构来进行升级,但这个过程也很不方便。例如,如果用户想要向其现有合约帐户添加新功能,则必须将整个代码转移到具有该功能的新合约中。此升级过程可能会导致新合同的额外审计成本等问题。此外,由于为所有用户提供统一形式的帐户,因此存在用户无法选择其帐户内的功能的缺点。
为了解决这些问题,2023 年 4 月提出了名为 ERC-6900 的新标准。
什么是 ERC-6900?
ERC-6900 是一个名为“模块化智能合约账户和插件”的 EIP,为与 ERC-4337 兼容的账户提供了标准。它基于模块化结构,允许在帐户中自由安装和删除各种功能,类似于在 Android 上安装或卸载应用程序。包含要包含在帐户中的功能的模块合约称为插件。
ERC-6900通过其模块化结构,使用户能够轻松地向其帐户添加或删除各种插件(功能)。特别是,由于当前的合约帐户通常仅限于特定应用程序,因此使用 ERC-6900 可以允许单个合约帐户轻松地在多个应用程序中使用。
虽然这是一个非常有趣的想法,但在实际代码中实现它时必须考虑以下因素,重点关注安全性和用户体验:
插件的安装和删除
首先考虑的应该是如何实现插件的安装和删除。ERC-6900 的开发者从 Diamond Proxy 中汲取了灵感。Diamond Proxy 是典型可升级代理的扩展结构,将合约划分为多个组件(或方面),并只允许其中的某些组件进行升级。与简单的可升级代理结构不同,简单的可升级代理结构需要更改整个逻辑合约才能进行升级,钻石代理允许仅选择和升级所需的组件。
在 Diamond Proxy 中,合约的各种功能被划分为称为 Facet 的组件,Proxy 使用 来调用它们delegatecall
。delegatecall
是一个“借用”外部合约函数并在自己的合约上下文中使用它们的函数,允许通过外部函数对其自己的合约存储进行强大的操作。
此外,Diamond Proxy 维护基于函数选择器的映射,以指定每个方面的访问路径。这允许代理合约自由访问每个方面内的功能。
Diamond Proxy 的结构 来源:ERC-2535 官方文档
此外,为了安全起见,Diamond Proxy 将所有数据存储在代理合约中,并将可访问的数据分配给每个方面。如果对facet使用的数据没有权限限制,在某些情况下可能会出现问题。例如,zkSync 在以太坊上部署了类似 Diamond 代理的合约,为治理、L1 ↔ L2 桥接和汇总数据发布创建了方面。如果治理方面可以访问用于桥接的参数,则可能会因不当操作或攻击而危及整个系统。
总结起来,Diamond Proxy 具有三个主要特点:
协议功能分为多个方面合约,通过 访问delegatecall
。
代理合约可以通过函数的选择器调用切面函数。
每个方面可访问的数据都受到限制。
Diamond Proxy 具有这些特性,针对构建多功能合约系统进行了优化。这与 ERC-6900 创建“具有各种功能的智能合约账户”的目标非常吻合。因此,ERC-6900借用了Diamond Proxy的结构来实现安装和删除插件的功能。然而,ERC-6900 和 Diamond Proxy 之间存在显着差异,稍后将讨论。
2. 用户与账户交互
假设建立了一个可以自由安装和删除插件的环境,那么要考虑的第二个方面是用户如何与帐户交互。合约账户与用户的交互主要分为两种:一是通过ERC-4337的入口点合约进行用户操作进行交互,二是用户直接调用合约账户内的函数。ERC-6900 将这两种类型的交互分别区分为“用户操作验证”和“运行时验证”,并为每种交互提供单独的验证和执行流程。
3. 许可
要考虑的第三个方面是许可。如果任何人都可以创建和安装插件,则必须考虑这些插件被滥用的可能性。例如,想象一个会话密钥插件,它可以在一段时间内提供帐户所有权。如果会话密钥所有者与恶意合约交互,则帐户的资金可能会完全丢失。因此,严格设置和验证插件可以访问的外部合约功能以及插件之间交互的权限至关重要。
4. 保证模块化
最后要考虑的是如何保证模块化。在可以自由安装和从帐户中删除各种插件的环境中,存在一些可能导致意外结果的边缘情况。例如,插件 A 和 B 可能具有具有相同选择器的功能。与 Diamond Proxy 一样,ERC-6900 使用选择器映射来调用插件内的函数,但如果这些选择器重叠,则存储在先前安装的插件的选择器中的信息可能会被删除,并替换为后来安装的插件中的信息。
这可能会导致帐户出现意外错误,因此需要加以预防。
为了解决这个问题,ERC-6900 引入了一项称为“依赖性”的功能。当想要从中借用函数或插件中的特定函数具有相同的选择器和功能时,此机制通过将已部署的插件设置为依赖项来防止冲突。
因此,ERC-6900的特点可以概括为四点:
它遵循 Diamond Proxy 的结构(但有显着差异,稍后将讨论)。
用户交互分为用户操作和运行时,每种交互都有不同的验证和执行过程。
它严格限制通过插件进行的操作权限。
为了防止冲突并确保插件安装和删除过程中的模块化,可以设置依赖关系。
在接下来的章节中,我们将仔细研究这四个方面。
ERC-6900的实施
Diamond Proxy
ERC-6900采用Diamond Proxy的结构,形成插件和合约账户的模块化结构。该帐户充当代理合约,每个插件充当一个方面。因此,当帐户发生用户操作或直接调用时,它会通过后备函数(当在合约中找不到被调用函数的选择器时执行的函数,通常在代理模式中使用)在插件内进行处理。在这个过程中,与前面提到的Diamond Proxy有一个至关重要的区别。
Diamond Proxy 用于delegatecall
调用构面内的函数。由于facet是只包含执行逻辑并且不需要存储(有时甚至部署为没有存储的库)的合约,因此delegatecall
被使用。然而,在 ERC-6900 中,插件内的函数被调用 using call
,并且插件有自己的存储。原因如下:
如果delegatecall
帐户允许插件,则插件的功能可以访问帐户的存储数据。这是非常危险的,因为恶意插件可能会删除或操纵帐户的存储信息。这在最初的 Diamond Proxy 中并不是一个主要问题,因为并非任何人都可以添加构面。然而,由于 ERC-6900 的目标是一个任何人都可以自由构建插件的环境,因此delegatecall
可能会带来重大风险。
因此,在 ERC-6900 中,call
使用 代替delegatecall
,允许数据也存储在插件的存储中。
MSCA 内部的调用流程
在 ERC-6900 中,合约账户被称为模块化智能合约账户 (MSCA)。ERC-6900 定义的 MSCA 内部的调用流程可以概括如下:
我们来看看用户操作部分。在ERC-4337中,用户操作的验证和执行是分离的,因此流程分为验证和执行,如上图左侧所示。另一方面,对于MSCA内的直接调用,流程通过运行时验证功能进行验证,如右图所示。每个流程都会经历几个阶段,包括验证函数、挂钩和执行函数。
这里使用的函数大致可以分为三种类型。首先,在帐户或插件内执行某些操作的函数称为执行函数。随后,对于每个执行函数,都有一个验证函数对其调用进行验证。此外,可以在每个函数调用之前和之后应用挂钩。让我们更详细地研究每一个。
验证功能
该函数对账户调用者进行权限验证。如前所述,如果任何人都可以调用账户内的函数,则该账户可能容易受到通过 Gas 消耗耗尽资金的攻击。因此,消耗gas或访问存储的函数必须通过验证函数进行控制。
有两种类型的验证函数:用于从入口点传入的调用的用户操作验证函数,以及当 EOA 直接调用帐户内的函数时执行的运行时验证函数。
这些验证功能并不存在于账户本身;它们都可以在插件中找到。因此,对帐户的所有调用都会通过插件中的验证函数。根据用例,可以有各种类型的验证函数,例如:
验证签名并仅允许所有者的地址通过的功能。
验证签名并仅允许指定地址通过的功能。
允许任何地址通过的函数。
2. 执行函数
这些是实际资金转移和与外部合约交互发生的功能。主要有两种类型:
1) 标准执行函数
指与ERC-4337参考实现的接口execute
和executeBatch
功能兼容。IAccount
这些函数可以根据账户的gas费执行所有类型的交互,因此需要严格的验证函数(例如,只允许所有者调用)。
2)执行函数
这些是每个插件中存在的功能以及可以从帐户执行的常用功能。例如,考虑recoverOwner
在社交恢复插件中调用的函数。由于只有分配用于恢复的监护人才能调用此函数,因此它必须具有适当的验证函数。在ERC-6900中,这应用于执行函数,如下所示:
通过将此信息存储在帐户的存储中,当recoverOwner
调用该函数时,可以通过onlyGuardiansValidationFunction
.
执行函数的选择器存储在帐户存储中,在插件安装时映射到插件地址。
3. 挂钩
还有一个称为钩子的函数,它在其他函数之前和之后运行。与Uniswap V4的Hook类似,它定义了特定任务之前和之后需要执行的操作。例如,可能有一个DailyGasSpendingLimit
限制每日燃气消耗的 Hook。该钩子可以在执行函数之前和之后使用该函数检查gas消耗情况gasleft()
,如果一天内消耗了一定量的gas,则阻止该函数的执行。
验证函数之前还可以有一个预验证钩子,主要用于需要多次验证的情况。例如,如果会话密钥插件中的会话密钥是多重签名钱包,则它需要同时通过多重签名和会话密钥验证。在这种情况下,多重签名的验证可以作为预验证挂钩来执行,而会话密钥的验证可以作为验证函数来执行。
更详细的调用流程可以用下图表示:
每个过程总结如下:
封装并调用要执行的插件的执行函数calldata
。如果帐户中没有与此函数匹配的选择器,fallback
则执行该函数(这适用于除标准执行函数(如 、 或稍后提到的 、 )之外的execute
所有executeBatch
情况executeFromPlugin
)executionFromPluginExternal
。
解析出哪个插件地址包含 中的执行函数后calldata
,就会执行关联的预验证钩子和验证函数。如果调用通过 ERC-4337 入口点,则验证逻辑已经执行,因此会被跳过。
执行与 Execution 函数相关的预执行钩子。此挂钩执行的结果返回有关要运行哪个执行后挂钩的信息。
执行函数被执行。
根据步骤3返回的结果,执行执行后挂钩。
这里重要的一点是,msg.sender
对插件的调用始终是帐户。因此,在插件中存储或查询帐户相关信息时,msg.sender
使用如下:
这意味着该插件的功能是建立在从账户调用的前提下的。但是,这可能会导致以下问题:
如果从另一个帐户或插件调用特定插件的函数,msg.sender
则将其设置为调用者,引用与从帐户调用时不同的存储,从而导致不同的结果。如果某个特定插件需要调用另一个插件中的函数,如何解决这个问题?
插件权限
executeFromPlugin
为了解决这个问题,ERC-6900 定义了和 等函数executeFromPluginExternal
。这些功能不仅解决了上述问题,还可以防止账户受到各种攻击场景。
executeFromPlugin
和executeFromPluginExternal
是定义和限制可以通过插件执行的任务的函数,每个函数都具有以下功能:
executeFromPlugin
当一个插件想要调用另一个插件中的函数时使用。它设置msg.sender
为帐户,允许在另一个插件中执行执行函数,而不会引用错误的存储或丢失调用的上下文。此外,为了防止恶意插件的攻击,如果该函数不是安装时预先指定的插件的函数,则该函数会恢复调用。
另一方面,executeFromPluginExternal
更加注重安全问题。当插件想要调用外部实体中的函数时使用它。一个典型的用例是会话密钥插件。如果具有会话密钥的地址可以调用任何外部函数,则会带来重大的安全风险。因此,有必要预先指定会话密钥可以访问的外部合约和函数,并在尝试访问未列出的函数时恢复调用。根据 ERC-6900 标准实现这一点如下所示:
首先,在插件内指定可以访问的外部合约和函数。这在插件部署期间被硬编码到合约中,使其以后不可变。
然后,在同一个插件中定义以下执行函数:
executeFromPluginExternal
当会话密钥通过调用帐户内的函数发送令牌时,使用此函数。在executeFromPluginExternal
帐户内执行以下步骤:
从存储中检索预定义的允许呼叫信息。
将此信息与输入的呼叫信息进行比较,如果不允许呼叫,则恢复。
执行调用。如果有执行钩子,它也会被执行。
因此,executeFromPlugin
并executeFromPluginExternal
限制插件可以执行的功能。msg.sender
这不仅可以防止从插件内直接调用另一个插件时调用 () 的上下文发生更改,而且还可以限制对外部合约的访问。
然而,不可能完全禁止插件通过executeFromPluginExternal
. 它无法阻止插件内的执行函数对地址进行硬编码并从外部调用。因此,只有已完成审核的插件才应安装在帐户上,以防止黑客攻击等问题。
依赖性
最后,一个插件可以对其他插件有依赖性(dependency)。这主要在从其他插件借用函数(验证函数、执行函数)时使用。一个典型的例子是SingleOwnerPlugin
参考实现中的。它包含一个只允许所有者调用的函数,如下所示:
由于这是一个非常通用的验证函数,其他插件可能会借用并使用它。在这种情况下,如果在安装插件时将其作为依赖项输入,则可以在插件中使用验证功能,而无需单独实现该功能。
当特定执行函数的验证函数重叠时,此功能极大地有助于减少代码的大小并提高可读性。具有只有所有者才能访问的功能的插件可以将上述 SingleOwnerPlugin 设置为依赖项,并对其功能使用其验证功能。
因此,可以从其他插件接收所有 Hook、验证函数和执行函数,如上所示。换句话说,可以基于依赖关系来实现插件的模块化架构。
这方面的一个例子可以在上面提到的类似代理的结构中看到。基本验证函数和必要的数据位于“父插件”中,多个“子插件”仅包含逻辑并依赖于父插件中的验证函数。这允许安全地添加、删除或替换逻辑,同时保留父插件合约中的数据和上下文。
Decipher Open Source Warriors 团队利用插件的模块化架构和上述功能,实现了符合 ERC-6900 标准的会话密钥插件,executeFromPluginExternal
并向官方 ERC-6900 实现 GitHub 请求拉取请求。目前,我们正在根据 PR 后收到的反馈对其进行修改,并计划为基于社区的插件提供一个单独的位置。
会话密钥插件由一个名为 的父插件BaseSessionKeyPlugin
和一个名为 的子插件组成TokenSessionKeyPlugin
。与现有的会话密钥实现相比,该插件具有以下两个优点:
通过使用依赖关系,它可以管理单个父插件中的所有会话密钥信息。
通过使用executeFromPluginExternal
,它严格限制子插件可以访问的外部合约,从而即使在会话密钥被恶意或被黑客攻击的情况下也可以防止帐户内的资金外流。
显现
最后,为了确保安全安装而不与其他安装的插件发生冲突,插件有一个名为manifest
. 它包括有关插件的执行函数、验证函数、挂钩以及依赖项、允许的调用等的信息。在安装过程中,会验证此信息,并将插件内函数的所有选择器存储在帐户的存储中。
ERC-6900的挑战与前景
ERC-6900仍有许多问题需要解决,原因如下:
1.合约规模
参考实现中智能账户UpgradeableModularAccount.sol的合约大小约为33KB。以太坊将可在主网上部署的合约的最大大小限制为 24KB,这是 2016 年Spurious Dragon硬分叉中设置的限制。因此,该合约尚无法部署在主网或测试网上。
原因之一是因为 ERC-6900 使用call
而不是delegatecall
. 通过在外部库或合约中实现一些函数并通过调用它们delegatecall
,可以在缩小合约大小的同时修改帐户存储。然而,由于ERC-6900出于安全原因限制了使用delegatecall
,访问存储的功能必须在合约内实现,从而增加了合约的大小。
因此,需要努力减少合约的规模,例如重构冗余部分。
2. Gas Consumption
从调用流程图中可以看出,ERC-6900 包括账户和插件之间的多次调用,这增加了 Gas 成本。根据实现情况,每次外部合约调用大约消耗 2,000 GAS。虽然如果只涉及一个插件(热/冷地址),这一成本可能会略有降低,但如果涉及多个插件,成本就会变得昂贵,增加用户的负担。然而,通过gas优化来降低用户成本至关重要,例如在账户和插件合约的功能中使用组装块。实现 ERC-6900 的 Alchemy 团队正在考虑根据各种选项修改架构,例如应用 Dencun 更新附带的 EIP-1153 的临时存储或将多个验证步骤捆绑到多重调用中。
同时,当前的参考实现部分牺牲了可读性的优化。Alchemy 团队计划在明年初将其更新为可部署在主网上,届时,第 1 点和第 2 点提到的问题预计将大部分得到解决。
3. 插件实现的复杂性
从开发人员的角度来看,实现插件需要考虑许多因素。虽然有很多复杂性,但一些例子包括:
决定是直接定义与每个执行函数关联的验证函数,还是通过其他插件的依赖设置接收它们。
当需要对调用者进行多次验证时,确定如何划分和设置预验证钩子和验证函数。
确保与现有插件的兼容性。例如,如果要在插件中实现的功能已经存在于现有插件中,则需要通过调用它executeFromPlugin
或将其设置为依赖项。
这些复杂性可以从不同的角度来解决。首先,架构本身可以添加某种形式的接口或方法来抽象复杂性。需要为插件开发人员提供文档,并且可以开发一个组织现有插件的功能和方法的仪表板,以方便管理依赖项和executeFromPlugin
.
Alchemy 团队正在致力于更新以降低所指出的复杂性,以响应通过 Telegram 和每两周一次的社区电话收到的社区反馈。
ERC-6900 允许从帐户安装和删除插件,类似于安装和卸载 Android 应用程序。这使得用户能够根据自己的需求和偏好个性化他们的钱包并添加或删除功能,从而创建定制的用户环境。它将解决ERC-4337兼容钱包之间的兼容性问题,允许在不同钱包平台之间自由移动。
此外,ERC-6900提出了一个可以被合约账户广泛接受的通用标准,同时融入了各种元素以促进实施并增强账户安全性。
修改后的 Diamond 代理结构,用于安全安装和删除插件。
函数(验证函数、执行函数、挂钩)和调用流程结构可有效、安全地管理帐户和插件之间的交互。
权限设置保证插件的灵活性和安全性。
依赖关系可防止插件之间功能重叠引起的冲突并帮助代码优化。
通过该标准,预计以太坊和EVM兼容链的钱包生态系统将得到发展,用户体验将得到显着改善。
参考
ERC-6900 官方文档
ERC-6900 参考实现 GitHub
ERC-6900 社区通话