ZK Stack 初学者指南
作者:@0xDogan.eth、@lumi、@ZkSync Chinese Community ∎ 在 Rollup SDK 出现之前,从头开始构建 Rollup 几乎是不可能的。主要原因有两个:缺乏开发人员可以构建的开源代码库。构建 Rollup 需要最先进的技术,而实施 Rollup 的技术复杂性需要一支资金充足的世界级构建团队,并且需要花费很多年。这使得构建者无法专注于他们自己的产品和垂直领域。如今,情况发生了巨大变化,Rollup SDK 和 Stacks 为更简化和更易于访问的 Rollup 创建过程铺平了道路。在这个领域最突出和广泛使用的框架包括 Op Stack 和 Polygon CDK。 尽管它们最早出现,但这些框架也并非没有缺点。zkSync 的 ZK Stack 应运而生,旨在建立一个解决现有框架主要问题的超链生态系统。目前 Rollup 框架面临的主要挑战是什么?缺乏实战检验 + 运行工作证明机制: Rollup 通过其“完整性证明”机制(例如零知识证明和欺诈证明),引入了一种用于无信任链下计算的方法。确保这些机制经过审计和实战检验,对于 Rollup 框...
![Cover image for [Paymaster] - 大规模采用的关键功能之一](https://img.paragraph.com/cdn-cgi/image/format=auto,width=3840,quality=85/https://storage.googleapis.com/papyrus_images/21254c35f649c8a52d4bbfe1aafa2502f1d41f07ee672bdba55a4f233066acc5.png)
[Paymaster] - 大规模采用的关键功能之一
ZkSync 继续推进以太坊扩展的使命,而这一进展的最前沿和关键的是 Paymaster 功能,同时,这也是 AA 最有用的用例之一。 Paymaster (付款大师)就像一个虚拟钱包,可以代替用户支付操作费用。这就像一个数字助理,可以根据某些规则决定是否进行付款,也可以让用户免费执行操作或使用特定类型的加密货币支付Gas Paymaster 功能的引入是帐户抽象 EIP 的增强计划的一部分。 zkSync 是原生AA账户集成,主要重点是确保用户安全、提供无缝体验、为开发人员提供灵活性以及阻止可能破坏系统的潜在攻击。该战略旨在创建一个安全且用户友好的环境,同时促进生态系统内的创新和弹性。 * 注意:在这篇博文中不会涉及太多技术性内容,因此提到的一些内容有点过于简单化。如果您想深入了解它的技术部分,请访问优秀的zkSync 文档。 https://docs.zksync.io/ 低 Gas 费用(未来进一步降低)、和原生账户抽象/Paymaster 支持是zkSync 两个显著优势关于第一个优势,Matter Labs团队最近取得了一些有趣的进展。在 zkSync Era 中,几乎...
「zkSync 中文」社区致力于 zkSync 在华人世界发展 让更多的中文用户快速、便捷地了解zkSync的巨大技术潜力和生态系统效益
ZK Stack 初学者指南
作者:@0xDogan.eth、@lumi、@ZkSync Chinese Community ∎ 在 Rollup SDK 出现之前,从头开始构建 Rollup 几乎是不可能的。主要原因有两个:缺乏开发人员可以构建的开源代码库。构建 Rollup 需要最先进的技术,而实施 Rollup 的技术复杂性需要一支资金充足的世界级构建团队,并且需要花费很多年。这使得构建者无法专注于他们自己的产品和垂直领域。如今,情况发生了巨大变化,Rollup SDK 和 Stacks 为更简化和更易于访问的 Rollup 创建过程铺平了道路。在这个领域最突出和广泛使用的框架包括 Op Stack 和 Polygon CDK。 尽管它们最早出现,但这些框架也并非没有缺点。zkSync 的 ZK Stack 应运而生,旨在建立一个解决现有框架主要问题的超链生态系统。目前 Rollup 框架面临的主要挑战是什么?缺乏实战检验 + 运行工作证明机制: Rollup 通过其“完整性证明”机制(例如零知识证明和欺诈证明),引入了一种用于无信任链下计算的方法。确保这些机制经过审计和实战检验,对于 Rollup 框...
![Cover image for [Paymaster] - 大规模采用的关键功能之一](https://img.paragraph.com/cdn-cgi/image/format=auto,width=3840,quality=85/https://storage.googleapis.com/papyrus_images/21254c35f649c8a52d4bbfe1aafa2502f1d41f07ee672bdba55a4f233066acc5.png)
[Paymaster] - 大规模采用的关键功能之一
ZkSync 继续推进以太坊扩展的使命,而这一进展的最前沿和关键的是 Paymaster 功能,同时,这也是 AA 最有用的用例之一。 Paymaster (付款大师)就像一个虚拟钱包,可以代替用户支付操作费用。这就像一个数字助理,可以根据某些规则决定是否进行付款,也可以让用户免费执行操作或使用特定类型的加密货币支付Gas Paymaster 功能的引入是帐户抽象 EIP 的增强计划的一部分。 zkSync 是原生AA账户集成,主要重点是确保用户安全、提供无缝体验、为开发人员提供灵活性以及阻止可能破坏系统的潜在攻击。该战略旨在创建一个安全且用户友好的环境,同时促进生态系统内的创新和弹性。 * 注意:在这篇博文中不会涉及太多技术性内容,因此提到的一些内容有点过于简单化。如果您想深入了解它的技术部分,请访问优秀的zkSync 文档。 https://docs.zksync.io/ 低 Gas 费用(未来进一步降低)、和原生账户抽象/Paymaster 支持是zkSync 两个显著优势关于第一个优势,Matter Labs团队最近取得了一些有趣的进展。在 zkSync Era 中,几乎...
「zkSync 中文」社区致力于 zkSync 在华人世界发展 让更多的中文用户快速、便捷地了解zkSync的巨大技术潜力和生态系统效益
Share Dialog
Share Dialog

Subscribe to ZkSync |Chinese Community

Subscribe to ZkSync |Chinese Community
<100 subscribers
<100 subscribers
原文:Quarkslab
翻译:zkSync CN Community
校对:Berri、Derivatives
这篇博客文章介绍了在 zkSync Era 上交易的完整工作流程。zkSync Era 是一个 Zk Rollup Layer 2 区块链,它使用零知识证明在以太坊区块链上执行交易并证明其执行。
特别感谢 Matter Labs 团队的 @vladbochok1 在发布前对这篇博客文章进行了审核。
zkSync Era 是一种使用零知识加密来扩展以太坊吞吐量的 Layer 2 协议。它被称为 Zk Rollup。该协议旨在使去中心化应用程序更易于访问和加速区块链技术的大规模采用。截至今天,已有超过 4 亿美元的资金被桥接到 zkSync Era。
在这篇博文中,我们将介绍第 2 层交易的整个工作流程。交易的不同状态将被详细说明,从生成到最终确定。为了说明该过程,我们将重点关注和理解一个示例交易。

为了便于阅读,本博文中将使用以下缩写:

自 2015 年正式推出以来,以太坊已成为智能合约的区块链领导者,这主要得益于其通用性、安全性和去中心化。然而,这些强大的品质是以有限的扩展性为代价的。
这种限制如此重要,以至于为此创造了一个特殊的概念:区块链三角困难。区块链三角困难是说,设计同时具有去中心化、安全性和可扩展性的区块链是非常困难(甚至不可能)的。在过去,以太坊牺牲了可扩展性,将网络限制在每秒约 12 笔交易。多年来,区块链一直处于满负荷状态,导致用户之间争夺打包权,这意味着费用更高(在最拥塞的时期每笔交易高达 100 美元)。
其中一种解决方案是 Layer 2 区块链,利用以太坊的安全性和去中心化来提出高度可扩展的区块链。
Zk Rollups 是利用零知识 (ZK) 技术来扩展以太坊吞吐量而不损害以太坊安全性的 Layer 2 解决方案。

以下是示例交易的场景:当 Alice 想向著名的 Bob 转账 1 ETH。以太坊区块链上的交易费用对 Alice 来说太高了,她想降低这些费用。她决定使用 Zk Rollup 来转移价值,同时节省费用,因此她使用了 zkSync Era。
首先,与所有区块链一样,用户负责创建自己的交易。Alice 根据目标区块链格式生成她的交易,并使用她的密钥对对其进行签名。
zkSync Era 交易基于以太坊格式。因此,Alice 将创建一个包含以下字段的交易:

注意:最后 3 个值与 gas 相关,并取决于网络状态。如果这些值设置得太低,交易要么失败,要么直到网络费用变得更负担得起才被验证者纳入。
Alice 生成交易后,她将其发送到 L2 节点。在 zkSync Era 中,L2 节点称为 “operator”。通过调用 eth_sendRawTransaction 端点将交易发送到 L2 operator。zkSync Era 支持标准的 Ethereum JSON-RPC API,并提供自己的 L2 特有功能。
一旦 Alice 生成了交易,她就会将其发送到 L2 节点。在zkSync时代的背景下,L2节点被称为“运营商”。交易通过调用端点发送到 L2 操作员eth_sendRawTransaction。zkSync Era 支持标准的以太坊 JSON-RPC API,并且还提供了自己的L2 特定功能。
zkSync Era 使用自己的特定字段来表示交易。以下是查询 zkSync 节点 JSON-RPC API 中的 L2 交易详细信息时提供的字段:

根据交易类型,可以向交易添加其他字段,但我们在此不予考虑。我们将在整个博客文章中使用 status 字段来跟踪 Alice 的交易演变。
在经典的 EVM 中,帐户由地址表示(160 位标识符,通常以十六进制形式表示)。 0x29DF43F75149D0552475A6f9B2aC96E28796ed0b是以太坊上使用的真实地址的示例。并存在两种类型的账户:外部拥有账户(EOA)和智能合约账户。
在以太坊上,EOA 地址是公钥创建的。它对应于 secp256k1 公钥的 Keccak-256 哈希的后 20 个字节。可以用以下公式表示: address = keccak256(secp256k1_pubkey)[12:] zkSync Era 使用相同的 EOA 地址。这样,大多数钱包(例如 MetaMask)可以直接开箱即用地支持 zkSync Era。
在以太坊上,智能合约是一个部署了字节码的地址,这意味着该账户的代码大小非零。所有非零代码大小的账户都被视为智能合约,所有零代码大小的账户都被视为 EOA。智能合约地址是根据字节码哈希、部署者地址和其他数据(例如随机数或非随机数和静态标识符)确定的。
在 zkSync Era 生态系统中,所有账户都被定义为智能合约。对于所有大于 2^16 -1=0xFFFF 且未定义字节码的地址,都附加了一个默认账户代码。这个默认账户代码主要用于验证和执行 EOA 的交易。小于 2^16 的地址用于系统合约。它们是内置的一部分,并启用了一些高级功能,例如向 L1 发送消息和管理 L2 ETH 余额。在下一节中将详细介绍一些系统合约,因为它们被 Alice 的交易使用。
用户可以在 zkSync Era 上部署自己的智能合约。该解决方案旨在提供对现有 Solidity 代码库的兼容性。zkEVM 字节码可以像在以太坊上部署 EVM 字节码一样在 zkSync Era 上部署,但有一些细微的差别。智能合约地址的创建方式也不同,尤其是为了避免跨链攻击。zkSync Era 的 zkEVM 的内部工作原理与 EVM 非常不同,但使用抽象层和专用工具允许大多数 Solidity 代码库开箱即用。此外,zkSync Era( Harhat、Foundry )支持最常用的 Solidity 开发工具。
操作员收到 Alice 的交易后,会将其添加到交易内存池中。交易的状态被设置pending。此状态在操作员收到交易后几乎立即设置。

操作员将交易内存池切分成名称为“L2区块”的区块。L2区块是一个包含特定元数据的交易列表。在 zkSync Era 的情况下,这些 L2 块只用于 L2 区块链,它们不会以这种形式包含在 L1 以太坊区块链中。L2 块背后的原理是为用户体验提供快速的验证,因为钱包会在交易被包含在 L2 块中后立即向用户显示验证。当前的 L2 块生产平均时间不到一秒。
一旦有多个 L2 块可用,它们就会合并成一个“L1 批处理”。L1 批处理包含所有交易,从第一个 L2 块到批处理中的最后一个 L2 块。L1 批处理稍后会在以太坊上提交和证明,这允许将提交 gas 成本分摊到所有分批交易中。这是降低 gas 成本和提高以太坊吞吐量的主要机制。

一旦操作员将交易包含在 L2 块中,交易的状态就会被设置为已包含。这目前在操作员收到交易后不到一秒钟的时间内就可以实现。大多数钱包使用此状态向用户确认 L2 交易的执行。

L1 批处理中的所有交易都在 zkSync Era 的专用虚拟机中执行:一个称为 EraVM 的 zkEVM。这个 zkEVM 的内部工作原理很复杂,所以我们只关注 Alice 的交易使用的组件。
L1 批处理被提供给引导加载程序 L2 合约。这个智能合约是一个特殊的合约:它是 EraVM 的入口点,负责执行提供的所有 L1 批处理中的交易。在执行之前,它的内存映射被初始化。根据 zkSync,这种内存初始化是为了方便和效率。它是唯一的不确定性点:“引导加载程序从其预先填充了操作员想要的所有数据的内存开始 ”。引导加载程序从调用 SystemContext 系统合约开始,设置多个上下文变量,如 L1 批处理时间戳、其索引和上一个 L1 批处理的哈希值。一旦变量被设置,它就会执行交易。对于每个交易,工作流程如下:
引导加载程序检查交易是否格式正确。
引导加载程序使用 validateTransaction 函数调用发送者(即 Alice)的账户来验证交易。
调用系统NonceHolder合约来增加账户随机数。
附加到发送者账户的默认账户代码检查交易签名的有效性。
如果交易有效,引导加载程序将调用发送者的帐户,使用该payForTransaction函数来支付交易执行费用。
为了便于阅读,下图中未包含此步骤。
如果交易费用已支付,引导加载程序使用 executeTransaction 函数调用发送者账户来执行交易。
默认帐户代码执行交易。
引导加载程序将未消耗的 Gas 退还给发送者的帐户。
为了便于阅读,下图中未包含此步骤。
在 Alice 的交易中,交易的执行将从 Alice 转账给 Bob。在以太坊上,这将通过直接调用 Bob 的地址来完成,value = 1_000_000_000_000_000_000 (即 1 ETH),input data = ""。 但 EraVM 的工作方式有所不同。要使用某个值调用地址,将使用 MsgValueSimulator 系统合约。该系统合约将修改 L2EthToken 系统合约中 Alice 和 Bob 的 Ether 余额。一旦余额更新,它将调用 Bob 的账户,同时将 Alice 账户设置为发送者。这是使用 EraVM 特有的 opcode mimic_call 完成的,该 opcode 允许通过更改任何交易的 msg.sender 值来模拟其他账户。幸运的是,这个危险的 opcode 只能由位于内核空间中的系统合约使用。它不适用于用户智能合约,因为它将是一个严重的安全漏洞。

EraVM 中交易的执行会产生公开数据。这些数据存储在 L1 (以太坊) 上,并允许重建 zkSync Era 的完整状态。zkSync 团队开发了自己的新解决方案来管理公开数据,该解决方案是 Boojum 升级中引入的证明系统的一部分。该解决方案允许在将公开数据存储到 L1 区块链之前对其进行压缩。公开数据压缩主要用于在以太坊上存储数据时优化 gas 费用。
zkSync 的公开数据分为 4 类:
L2 到 L1 日志:从 L2 到 L1 通信的“可证明”部分。它们与 EraVM 执行证明相关联(将在本博客文章后面解释)。
L2 到 L1 消息:不能在日志中发送的长消息。每个消息都与 L2 到 L1 日志相关联。
智能合约字节码:部署在 L2 智能合约中的字节码。每个字节码都与 L2 到 L1 日志相关联。
存储写入:这些数据涉及 L2 智能合约的存储槽状态(请记住,zkSync 上的所有账户都是智能合约)。
回到 Alice 的交易,pubdata 中有趣的类别是Storage writes,也称为storage diffs。L2EthToken更准确地说,我们知道当Alice向Bob发送1个ETH时,修改后的存储槽位是系统合约中Alice的余额和Bob的余额。
zkSync Era 被设计为“基于statediff”的汇总。因此,它以确保数据可用性的方式在 L1 上发布状态更改。众所周知,以太坊有一个2 层树 ,它将存储槽数据附加到帐户地址(树的第一层),然后附加到存储槽编号(第二层)。与以太坊不同,zkSync 的树被定义为“扁平”。它是一棵 1 层树,将数据存储到槽的派生密钥。派生密钥是通过对存储槽号和该槽的账户地址进行散列计算得出的:H(Slot number, Account)。

在 zkSync 的公开数据解决方案中,初始写入和重复写入的处理方式不同。初始写入将创建派生键,并将一个顺序 ID 附加到该派生键上。该对derived key, value将在 L1 上发布,一旦顺序 ID 已附加到派生键上,它将用于未来的写入,称为重复写入。因此,对于重复写入(即对现有存储槽值的修改),将使用附加到该槽的顺序 ID。此 ID 称为 enumeration_index,每个 ID 都永久分配给一个存储槽。与以太坊一样,在 zkSync Era 上读取空的存储槽将返回一个零值。
一旦所有 L1 批处理公开数据被压缩,它就会被提交到 L1Messenger 系统合约。该合约验证公开数据是否一致且已正确压缩。如果数据有效,则使用 EraVM 特有的 opcode to_l1 将压缩数据存储在 zkSync 的 L1 智能合约中 。
此步骤称为“L1 批处理提交”,它是通过调用 L1 上的 commitBatches 函数执行的。此 L1 交易的哈希将由 zkSync 的节点报告为与 L2 交易详细信息(如前)相关的 eth_commit_tx_hash 字段的值。一旦 L1 批处理数据存储在 L1 上,批处理中的所有交易将保持已包含状态,并且 L1 批处理将设置为已提交状态。

Alice 交易工作流程的下一步是交易验证。当包含交易的 L1 批次已在 L1 智能合约上得到验证时,交易就被称为“已验证”。

zkSync Era 是一个 ZK Rollup。因此,它使用零知识证明 (Zero-Knowledge Proof, ZKP) 系统来简洁地证明 L2 交易的执行。简洁性是 ZK Rollup 中使用 ZKP 的主要原因。ZKP 的验证过程允许验证成千上万笔交易的正确计算。有几种 ZKP 系统。zkSync Era 开发了自己的新 ZKP 系统,它是 Boojum 升级的一部分。
Boojum证明系统可以被视为实现ZK电路(ZK circuit) 工具箱。这些 ZK 电路用于执行任意计算。它们对于 Boojum 等证明系统生成计算证明是必不可少的。这些证明可以被非交互式地验证,证明 ZK 电路被正确地执行了。
EraVM 是一个 zkEVM。为 EraVM 开发了特定的 ZK 电路以适应 EVM 行为。它们用于证明VM的正确执行。每次处理 L1 批次时,EraVM 都会生成一个 ZK 见证。
该见证人允许操作员(即证明者)证明 L1 批次的计算正确。一旦 L1 批次提交给 zkSync 的 L1 智能合约,就可以向该智能合约提供见证人,以证明 EraVM 正确计算了 L1 批次。然后,L1 智能合约将验证委托给专门的智能合约(部署在 L1 上)充当 ZKP 系统中的验证者。

此验证步骤是通过调用proveBatchesL1 智能合约上的函数来完成的。此 L1 交易的哈希值将由 zkSync 的节点报告为与 L2 交易详细信息关联的字段的值eth_prove_tx_hash(如前所述)。L1批次数据在L1上验证完成后,该批次的所有交易都会被设置为已验证状态。L1 批次也设置为已验证状态。
zkSync 的 L1 智能合约是一个钻石代理。该系统用于允许渐进式智能合约更新,并消除智能合约附带的 24 KB 字节码大小限制。
核心思想是将功能与存储分开,并将其分成几个相关的智能合约。在 zkSync Era 的当前状态下,这些是:

请注意,治理和 DiamondCut 功能在部署时是分开的。
要了解 Alice 的交易是如何在 L1 上发布和验证的,我们将重点关注 Executor facet。
在此级别上,zkSync 仅处理整个批次的 L2 交易。这使得能够在 L2 上对交易进行压缩,并通过在批次的交易之间共享交易费用来降低交易费用。因此,Alice 的交易被压缩并与其他交易捆绑在一起。该批次不包含完整的 L2 交易列表,而只包含重建 L2 区块链状态所需的变化列表,以确保数据可用性(请参阅“公共数据输出”部分)。
该批次首先使用 commitBatches 函数提交到 L1 智能合约,该函数仅供验证者组访问。该函数确保批次有效、按顺序提交且基于有效的已知状态,但它不保证更改集的有效性。此时,Alice 的交易被简化为其影响,并简单地发布到 L1。这对应于“已包含”状态。
接下来是证明,当调用 proveBatches 函数(仅供验证者调用)时。验证者提供批次的零知识证明,证明 EraVM 在公共输入集(包括 Alice 的交易)上的良好执行。一个称为验证器的专用智能合约负责验证证明。该特殊合约应使用协议升级与 EraVM 的升级一起更新。
一旦批次被证明,需要执行跨链操作(存储在可通过 Mailbox facet 访问的优先级队列中)。由于 Alice 转移的资金仍保留在 L2 上,因此此交易不需要任何操作。在执行所有操作后,批次可以在调用 executeBatches 时转换为“已执行”状态。Alice 的交易现在被视为已最终确定。

一旦一批被验证,其中的所有交易的状态都会被验证。在交易最终确定之前,还有最后一步需要执行。
多个L1批次已在L1智能合约上提交和验证。工作流程的最后一步是执行 L1 批次。这意味着L1批次之后获得的状态必须成为L1合约中L2的正式状态。
为了优化以太坊上的 Gas 成本,单个 L1 交易将用于执行多个 L1 批次。这些交易由 zkSync 运营商发送。截至目前,L2 高度中心化,zkSync 运营商在 L2 拥有很大的权力。

最后一步是通过调用 executeBatches L1 菱形中的函数来完成的。此 L1 交易的哈希值将由 zkSync 的节点报告为与 L2 交易详细信息关联的字段的值 eth_execute_tx_hash(如前所述)。实际上,状态最终确定是通过为每个 L1 批次导入 L2 日志 Merkle 树来完成的。
完成此步骤后,L1 批次将标记为Finalized。此时,无法取消 L2 交易。L2 交易可以被认定为最终确定。
zkSync Era 上的 L2 交易会经过多个步骤。交易会在 L2 层快速确认,并且用户会收到交易有效的通知。但是,这些给用户钱包的确认只能确保交易在 L2 上的执行。为了确保协议值得信赖,还需要进行更低级别的检查。例如,需要证明 L2 虚拟机的执行是正确的,以确保执行的交易是有效的。
通过使用零知识证明,zkSync Era 能够证明其 EraVM 的正确执行以及该执行的输出是有效的。在链上生成和验证一个正确执行的证明,使 zkSync Era 能够将其 L2 提升到“以太坊的安全性”。
ZKP 是解决以太坊可扩展性问题的强大工具。可扩展性通过将许多交易捆绑在一起并只提交一个证明来提高。验证者不需要计算整个交易集来验证它们的正确执行,而可以只关注交易的影响。在 zkSync Era 的情况下,数百笔交易被验证为正确的,只需使用一个验证证明的以太坊交易。根据 EIP-4844,ZK Rollup 的费用比以太坊的费用低约 40-100 倍。这种降低是通过 ZKP 实现的。
但 EraVM 的实现可能存在漏洞。例如,它可能将无效交易执行为有效交易,反之亦然。这就是为什么 zkSync 团队组织了一个奖金池为 110 万美元的 Code4rena 比赛。这场审计比赛旨在寻找 zkSync Era 中的漏洞,以便在发布之前修复这些漏洞,并避免在区块链生态系统中经常造成重大影响的生产漏洞。
现在我们已经在 Alice 的交易生命周期中说明了 zkSync,让我们讨论一下我们之前忽略的一些兴趣点。
作为提醒,Diamond 智能合约是专门为颗粒度的可升级性和无限的功能而设计的。
要实现这一点,外部可访问的函数使用其 Solidity 风格的选择器进行注册1 每个选择器都映射到包含相关逻辑的 Facet 。当调用时,Diamond 合约的 fallback 函数会检索适当的 Facet,使用其 calldata 执行 DELEGATECALL 并转发 Facet 的返回状态。使用 DELEGATECALL 意味着 Facet 对 Diamond 智能合约存储具有完全的控制权,因此需要仔细控制注册的 Facet 和函数。
要更新 Diamond 的注册函数,必须使用 diamondCut 内部函数2 。在 zkSync 的当前设计中,这只有在部署时(在构造函数中)和使用 Admin Facet 的 executeUpgrade 函数(由 onlyGovernor 保护)才可能。
Facet 没有关联的存储。相反,它通过 DELEGATECALL 直接访问 Diamond 的存储空间。为了实现 Facet 之间的互操作性并防止存储空间冲突,该标准提出了使用两种模式:Diamond Storage 和 AppStorage。zkSync 使用两种模式:
一个DiamondStorage 结构,负责与 Diamond 相关的功能,位于keccak256("diamond.standard.diamond.storage") - 1,
一个 AppStorage 结构,保存应用程序的状态并位于 Base 智能合约的第一个槽中,由所有 Facet 作为第一个父级继承。
总之,zkSync 对 Diamond 标准的实现使用了一组包含相关功能的 Facet,并将它们聚合在一个智能合约中。存储仅包含在两个结构中:DiamondStorage和AppStorage。功能分布在 4 个方面。
尽管 EIP-2535 允许 Diamond 智能合约具有原生的外部可访问函数,但 zkSync 目前没有这个功能,它将所有内容委托给其 fallback 函数中的 Facet,包括 Diamond 相关的功能。这就是为什么它被称为 Diamond Proxy 的原因。实际的 Diamond 相关功能位于只由 Proxy、Admin Facet 和 Getters Facet 使用的 Diamond 库中。
Diamond 的核心对象是 selectorToFacet 映射。它用于知道哪个 Facet 应该被调用来处理特定的函数选择器。
尽管理论上只有 mapping(bytes4 => address) 就足够了(因此在标准的示例中使用了它),但 zkSync 选择使用更复杂的数据结构来允许更高效的内省和更新,以及冻结功能。作为交换,该库需要特别注意确保内部数据的一致性和准确的簿记。
Diamond 实现了一个 Facet 地址和选择器之间双向的一对多映射:
每个 Facet 地址都映射到 facetToSelectors 中的选择器列表,和
每个选择器都映射到 selectorToFacet 中的单个 Facet 地址。
此外,它还在 address[] facets 中跟踪已用 Facet 地址的列表,并在 bool isFrozen 中跟踪全局冻结状态。
zkSync 的 Diamond 实现的概念实体关系图

主映射使用专门的对象来保存其元素的额外信息:
SelectorToFacet 有以下字段(请注意,所有这些值都由 Solidity 打包到一个槽中,从而提高了整体的 gas 消耗):
SelectorToFacet有以下字段(请注意,所有这些值都由 Solidity 打包到一个槽中,从而提高了整体的 gas 消耗)3:
address facetAddress (data),
bool isFreezable (metadata) and
uint16 selectorPosition (bookkeeping).
FacetToSelectors has these fields:
bytes4[] selectors (data), and
uint16 facetPosition (bookkeeping)
双向映射中的一对多关系在 FacetToSelectors 中由选择器数组表示,在 SelectorToFacet 中由选择器在相应数组中的位置表示。这意味着这些值需要由库保持同步,并且每个 Facet 最多可以注册 2^16 个选择器(这应该足够了)。这种簿记的优势在于,库不需要执行(可能代价高昂的)搜索来找到如何更新其内部数据结构,但代价是更多的预先计算和稍微更高的存储空间占用。
选择器的哈希值是 4 字节长的,可以用于快速查找选择器对应的 Facet。
EIP-2535 建议此函数为 external,但不要求这样做。这意味着需要在此函数的级别实现访问控制。
zkSync 原本可以使用 uint88 并在单个槽中保留结构。
函数签名的 4 字节长哈希值。请参阅官方文档 和4byte.directory以获得在线彩虹表。 ↩
zkSync 可以使用最多uint88,同时将结构保留在单个插槽内。 ↩
原文:Quarkslab
翻译:zkSync CN Community
校对:Berri、Derivatives
这篇博客文章介绍了在 zkSync Era 上交易的完整工作流程。zkSync Era 是一个 Zk Rollup Layer 2 区块链,它使用零知识证明在以太坊区块链上执行交易并证明其执行。
特别感谢 Matter Labs 团队的 @vladbochok1 在发布前对这篇博客文章进行了审核。
zkSync Era 是一种使用零知识加密来扩展以太坊吞吐量的 Layer 2 协议。它被称为 Zk Rollup。该协议旨在使去中心化应用程序更易于访问和加速区块链技术的大规模采用。截至今天,已有超过 4 亿美元的资金被桥接到 zkSync Era。
在这篇博文中,我们将介绍第 2 层交易的整个工作流程。交易的不同状态将被详细说明,从生成到最终确定。为了说明该过程,我们将重点关注和理解一个示例交易。

为了便于阅读,本博文中将使用以下缩写:

自 2015 年正式推出以来,以太坊已成为智能合约的区块链领导者,这主要得益于其通用性、安全性和去中心化。然而,这些强大的品质是以有限的扩展性为代价的。
这种限制如此重要,以至于为此创造了一个特殊的概念:区块链三角困难。区块链三角困难是说,设计同时具有去中心化、安全性和可扩展性的区块链是非常困难(甚至不可能)的。在过去,以太坊牺牲了可扩展性,将网络限制在每秒约 12 笔交易。多年来,区块链一直处于满负荷状态,导致用户之间争夺打包权,这意味着费用更高(在最拥塞的时期每笔交易高达 100 美元)。
其中一种解决方案是 Layer 2 区块链,利用以太坊的安全性和去中心化来提出高度可扩展的区块链。
Zk Rollups 是利用零知识 (ZK) 技术来扩展以太坊吞吐量而不损害以太坊安全性的 Layer 2 解决方案。

以下是示例交易的场景:当 Alice 想向著名的 Bob 转账 1 ETH。以太坊区块链上的交易费用对 Alice 来说太高了,她想降低这些费用。她决定使用 Zk Rollup 来转移价值,同时节省费用,因此她使用了 zkSync Era。
首先,与所有区块链一样,用户负责创建自己的交易。Alice 根据目标区块链格式生成她的交易,并使用她的密钥对对其进行签名。
zkSync Era 交易基于以太坊格式。因此,Alice 将创建一个包含以下字段的交易:

注意:最后 3 个值与 gas 相关,并取决于网络状态。如果这些值设置得太低,交易要么失败,要么直到网络费用变得更负担得起才被验证者纳入。
Alice 生成交易后,她将其发送到 L2 节点。在 zkSync Era 中,L2 节点称为 “operator”。通过调用 eth_sendRawTransaction 端点将交易发送到 L2 operator。zkSync Era 支持标准的 Ethereum JSON-RPC API,并提供自己的 L2 特有功能。
一旦 Alice 生成了交易,她就会将其发送到 L2 节点。在zkSync时代的背景下,L2节点被称为“运营商”。交易通过调用端点发送到 L2 操作员eth_sendRawTransaction。zkSync Era 支持标准的以太坊 JSON-RPC API,并且还提供了自己的L2 特定功能。
zkSync Era 使用自己的特定字段来表示交易。以下是查询 zkSync 节点 JSON-RPC API 中的 L2 交易详细信息时提供的字段:

根据交易类型,可以向交易添加其他字段,但我们在此不予考虑。我们将在整个博客文章中使用 status 字段来跟踪 Alice 的交易演变。
在经典的 EVM 中,帐户由地址表示(160 位标识符,通常以十六进制形式表示)。 0x29DF43F75149D0552475A6f9B2aC96E28796ed0b是以太坊上使用的真实地址的示例。并存在两种类型的账户:外部拥有账户(EOA)和智能合约账户。
在以太坊上,EOA 地址是公钥创建的。它对应于 secp256k1 公钥的 Keccak-256 哈希的后 20 个字节。可以用以下公式表示: address = keccak256(secp256k1_pubkey)[12:] zkSync Era 使用相同的 EOA 地址。这样,大多数钱包(例如 MetaMask)可以直接开箱即用地支持 zkSync Era。
在以太坊上,智能合约是一个部署了字节码的地址,这意味着该账户的代码大小非零。所有非零代码大小的账户都被视为智能合约,所有零代码大小的账户都被视为 EOA。智能合约地址是根据字节码哈希、部署者地址和其他数据(例如随机数或非随机数和静态标识符)确定的。
在 zkSync Era 生态系统中,所有账户都被定义为智能合约。对于所有大于 2^16 -1=0xFFFF 且未定义字节码的地址,都附加了一个默认账户代码。这个默认账户代码主要用于验证和执行 EOA 的交易。小于 2^16 的地址用于系统合约。它们是内置的一部分,并启用了一些高级功能,例如向 L1 发送消息和管理 L2 ETH 余额。在下一节中将详细介绍一些系统合约,因为它们被 Alice 的交易使用。
用户可以在 zkSync Era 上部署自己的智能合约。该解决方案旨在提供对现有 Solidity 代码库的兼容性。zkEVM 字节码可以像在以太坊上部署 EVM 字节码一样在 zkSync Era 上部署,但有一些细微的差别。智能合约地址的创建方式也不同,尤其是为了避免跨链攻击。zkSync Era 的 zkEVM 的内部工作原理与 EVM 非常不同,但使用抽象层和专用工具允许大多数 Solidity 代码库开箱即用。此外,zkSync Era( Harhat、Foundry )支持最常用的 Solidity 开发工具。
操作员收到 Alice 的交易后,会将其添加到交易内存池中。交易的状态被设置pending。此状态在操作员收到交易后几乎立即设置。

操作员将交易内存池切分成名称为“L2区块”的区块。L2区块是一个包含特定元数据的交易列表。在 zkSync Era 的情况下,这些 L2 块只用于 L2 区块链,它们不会以这种形式包含在 L1 以太坊区块链中。L2 块背后的原理是为用户体验提供快速的验证,因为钱包会在交易被包含在 L2 块中后立即向用户显示验证。当前的 L2 块生产平均时间不到一秒。
一旦有多个 L2 块可用,它们就会合并成一个“L1 批处理”。L1 批处理包含所有交易,从第一个 L2 块到批处理中的最后一个 L2 块。L1 批处理稍后会在以太坊上提交和证明,这允许将提交 gas 成本分摊到所有分批交易中。这是降低 gas 成本和提高以太坊吞吐量的主要机制。

一旦操作员将交易包含在 L2 块中,交易的状态就会被设置为已包含。这目前在操作员收到交易后不到一秒钟的时间内就可以实现。大多数钱包使用此状态向用户确认 L2 交易的执行。

L1 批处理中的所有交易都在 zkSync Era 的专用虚拟机中执行:一个称为 EraVM 的 zkEVM。这个 zkEVM 的内部工作原理很复杂,所以我们只关注 Alice 的交易使用的组件。
L1 批处理被提供给引导加载程序 L2 合约。这个智能合约是一个特殊的合约:它是 EraVM 的入口点,负责执行提供的所有 L1 批处理中的交易。在执行之前,它的内存映射被初始化。根据 zkSync,这种内存初始化是为了方便和效率。它是唯一的不确定性点:“引导加载程序从其预先填充了操作员想要的所有数据的内存开始 ”。引导加载程序从调用 SystemContext 系统合约开始,设置多个上下文变量,如 L1 批处理时间戳、其索引和上一个 L1 批处理的哈希值。一旦变量被设置,它就会执行交易。对于每个交易,工作流程如下:
引导加载程序检查交易是否格式正确。
引导加载程序使用 validateTransaction 函数调用发送者(即 Alice)的账户来验证交易。
调用系统NonceHolder合约来增加账户随机数。
附加到发送者账户的默认账户代码检查交易签名的有效性。
如果交易有效,引导加载程序将调用发送者的帐户,使用该payForTransaction函数来支付交易执行费用。
为了便于阅读,下图中未包含此步骤。
如果交易费用已支付,引导加载程序使用 executeTransaction 函数调用发送者账户来执行交易。
默认帐户代码执行交易。
引导加载程序将未消耗的 Gas 退还给发送者的帐户。
为了便于阅读,下图中未包含此步骤。
在 Alice 的交易中,交易的执行将从 Alice 转账给 Bob。在以太坊上,这将通过直接调用 Bob 的地址来完成,value = 1_000_000_000_000_000_000 (即 1 ETH),input data = ""。 但 EraVM 的工作方式有所不同。要使用某个值调用地址,将使用 MsgValueSimulator 系统合约。该系统合约将修改 L2EthToken 系统合约中 Alice 和 Bob 的 Ether 余额。一旦余额更新,它将调用 Bob 的账户,同时将 Alice 账户设置为发送者。这是使用 EraVM 特有的 opcode mimic_call 完成的,该 opcode 允许通过更改任何交易的 msg.sender 值来模拟其他账户。幸运的是,这个危险的 opcode 只能由位于内核空间中的系统合约使用。它不适用于用户智能合约,因为它将是一个严重的安全漏洞。

EraVM 中交易的执行会产生公开数据。这些数据存储在 L1 (以太坊) 上,并允许重建 zkSync Era 的完整状态。zkSync 团队开发了自己的新解决方案来管理公开数据,该解决方案是 Boojum 升级中引入的证明系统的一部分。该解决方案允许在将公开数据存储到 L1 区块链之前对其进行压缩。公开数据压缩主要用于在以太坊上存储数据时优化 gas 费用。
zkSync 的公开数据分为 4 类:
L2 到 L1 日志:从 L2 到 L1 通信的“可证明”部分。它们与 EraVM 执行证明相关联(将在本博客文章后面解释)。
L2 到 L1 消息:不能在日志中发送的长消息。每个消息都与 L2 到 L1 日志相关联。
智能合约字节码:部署在 L2 智能合约中的字节码。每个字节码都与 L2 到 L1 日志相关联。
存储写入:这些数据涉及 L2 智能合约的存储槽状态(请记住,zkSync 上的所有账户都是智能合约)。
回到 Alice 的交易,pubdata 中有趣的类别是Storage writes,也称为storage diffs。L2EthToken更准确地说,我们知道当Alice向Bob发送1个ETH时,修改后的存储槽位是系统合约中Alice的余额和Bob的余额。
zkSync Era 被设计为“基于statediff”的汇总。因此,它以确保数据可用性的方式在 L1 上发布状态更改。众所周知,以太坊有一个2 层树 ,它将存储槽数据附加到帐户地址(树的第一层),然后附加到存储槽编号(第二层)。与以太坊不同,zkSync 的树被定义为“扁平”。它是一棵 1 层树,将数据存储到槽的派生密钥。派生密钥是通过对存储槽号和该槽的账户地址进行散列计算得出的:H(Slot number, Account)。

在 zkSync 的公开数据解决方案中,初始写入和重复写入的处理方式不同。初始写入将创建派生键,并将一个顺序 ID 附加到该派生键上。该对derived key, value将在 L1 上发布,一旦顺序 ID 已附加到派生键上,它将用于未来的写入,称为重复写入。因此,对于重复写入(即对现有存储槽值的修改),将使用附加到该槽的顺序 ID。此 ID 称为 enumeration_index,每个 ID 都永久分配给一个存储槽。与以太坊一样,在 zkSync Era 上读取空的存储槽将返回一个零值。
一旦所有 L1 批处理公开数据被压缩,它就会被提交到 L1Messenger 系统合约。该合约验证公开数据是否一致且已正确压缩。如果数据有效,则使用 EraVM 特有的 opcode to_l1 将压缩数据存储在 zkSync 的 L1 智能合约中 。
此步骤称为“L1 批处理提交”,它是通过调用 L1 上的 commitBatches 函数执行的。此 L1 交易的哈希将由 zkSync 的节点报告为与 L2 交易详细信息(如前)相关的 eth_commit_tx_hash 字段的值。一旦 L1 批处理数据存储在 L1 上,批处理中的所有交易将保持已包含状态,并且 L1 批处理将设置为已提交状态。

Alice 交易工作流程的下一步是交易验证。当包含交易的 L1 批次已在 L1 智能合约上得到验证时,交易就被称为“已验证”。

zkSync Era 是一个 ZK Rollup。因此,它使用零知识证明 (Zero-Knowledge Proof, ZKP) 系统来简洁地证明 L2 交易的执行。简洁性是 ZK Rollup 中使用 ZKP 的主要原因。ZKP 的验证过程允许验证成千上万笔交易的正确计算。有几种 ZKP 系统。zkSync Era 开发了自己的新 ZKP 系统,它是 Boojum 升级的一部分。
Boojum证明系统可以被视为实现ZK电路(ZK circuit) 工具箱。这些 ZK 电路用于执行任意计算。它们对于 Boojum 等证明系统生成计算证明是必不可少的。这些证明可以被非交互式地验证,证明 ZK 电路被正确地执行了。
EraVM 是一个 zkEVM。为 EraVM 开发了特定的 ZK 电路以适应 EVM 行为。它们用于证明VM的正确执行。每次处理 L1 批次时,EraVM 都会生成一个 ZK 见证。
该见证人允许操作员(即证明者)证明 L1 批次的计算正确。一旦 L1 批次提交给 zkSync 的 L1 智能合约,就可以向该智能合约提供见证人,以证明 EraVM 正确计算了 L1 批次。然后,L1 智能合约将验证委托给专门的智能合约(部署在 L1 上)充当 ZKP 系统中的验证者。

此验证步骤是通过调用proveBatchesL1 智能合约上的函数来完成的。此 L1 交易的哈希值将由 zkSync 的节点报告为与 L2 交易详细信息关联的字段的值eth_prove_tx_hash(如前所述)。L1批次数据在L1上验证完成后,该批次的所有交易都会被设置为已验证状态。L1 批次也设置为已验证状态。
zkSync 的 L1 智能合约是一个钻石代理。该系统用于允许渐进式智能合约更新,并消除智能合约附带的 24 KB 字节码大小限制。
核心思想是将功能与存储分开,并将其分成几个相关的智能合约。在 zkSync Era 的当前状态下,这些是:

请注意,治理和 DiamondCut 功能在部署时是分开的。
要了解 Alice 的交易是如何在 L1 上发布和验证的,我们将重点关注 Executor facet。
在此级别上,zkSync 仅处理整个批次的 L2 交易。这使得能够在 L2 上对交易进行压缩,并通过在批次的交易之间共享交易费用来降低交易费用。因此,Alice 的交易被压缩并与其他交易捆绑在一起。该批次不包含完整的 L2 交易列表,而只包含重建 L2 区块链状态所需的变化列表,以确保数据可用性(请参阅“公共数据输出”部分)。
该批次首先使用 commitBatches 函数提交到 L1 智能合约,该函数仅供验证者组访问。该函数确保批次有效、按顺序提交且基于有效的已知状态,但它不保证更改集的有效性。此时,Alice 的交易被简化为其影响,并简单地发布到 L1。这对应于“已包含”状态。
接下来是证明,当调用 proveBatches 函数(仅供验证者调用)时。验证者提供批次的零知识证明,证明 EraVM 在公共输入集(包括 Alice 的交易)上的良好执行。一个称为验证器的专用智能合约负责验证证明。该特殊合约应使用协议升级与 EraVM 的升级一起更新。
一旦批次被证明,需要执行跨链操作(存储在可通过 Mailbox facet 访问的优先级队列中)。由于 Alice 转移的资金仍保留在 L2 上,因此此交易不需要任何操作。在执行所有操作后,批次可以在调用 executeBatches 时转换为“已执行”状态。Alice 的交易现在被视为已最终确定。

一旦一批被验证,其中的所有交易的状态都会被验证。在交易最终确定之前,还有最后一步需要执行。
多个L1批次已在L1智能合约上提交和验证。工作流程的最后一步是执行 L1 批次。这意味着L1批次之后获得的状态必须成为L1合约中L2的正式状态。
为了优化以太坊上的 Gas 成本,单个 L1 交易将用于执行多个 L1 批次。这些交易由 zkSync 运营商发送。截至目前,L2 高度中心化,zkSync 运营商在 L2 拥有很大的权力。

最后一步是通过调用 executeBatches L1 菱形中的函数来完成的。此 L1 交易的哈希值将由 zkSync 的节点报告为与 L2 交易详细信息关联的字段的值 eth_execute_tx_hash(如前所述)。实际上,状态最终确定是通过为每个 L1 批次导入 L2 日志 Merkle 树来完成的。
完成此步骤后,L1 批次将标记为Finalized。此时,无法取消 L2 交易。L2 交易可以被认定为最终确定。
zkSync Era 上的 L2 交易会经过多个步骤。交易会在 L2 层快速确认,并且用户会收到交易有效的通知。但是,这些给用户钱包的确认只能确保交易在 L2 上的执行。为了确保协议值得信赖,还需要进行更低级别的检查。例如,需要证明 L2 虚拟机的执行是正确的,以确保执行的交易是有效的。
通过使用零知识证明,zkSync Era 能够证明其 EraVM 的正确执行以及该执行的输出是有效的。在链上生成和验证一个正确执行的证明,使 zkSync Era 能够将其 L2 提升到“以太坊的安全性”。
ZKP 是解决以太坊可扩展性问题的强大工具。可扩展性通过将许多交易捆绑在一起并只提交一个证明来提高。验证者不需要计算整个交易集来验证它们的正确执行,而可以只关注交易的影响。在 zkSync Era 的情况下,数百笔交易被验证为正确的,只需使用一个验证证明的以太坊交易。根据 EIP-4844,ZK Rollup 的费用比以太坊的费用低约 40-100 倍。这种降低是通过 ZKP 实现的。
但 EraVM 的实现可能存在漏洞。例如,它可能将无效交易执行为有效交易,反之亦然。这就是为什么 zkSync 团队组织了一个奖金池为 110 万美元的 Code4rena 比赛。这场审计比赛旨在寻找 zkSync Era 中的漏洞,以便在发布之前修复这些漏洞,并避免在区块链生态系统中经常造成重大影响的生产漏洞。
现在我们已经在 Alice 的交易生命周期中说明了 zkSync,让我们讨论一下我们之前忽略的一些兴趣点。
作为提醒,Diamond 智能合约是专门为颗粒度的可升级性和无限的功能而设计的。
要实现这一点,外部可访问的函数使用其 Solidity 风格的选择器进行注册1 每个选择器都映射到包含相关逻辑的 Facet 。当调用时,Diamond 合约的 fallback 函数会检索适当的 Facet,使用其 calldata 执行 DELEGATECALL 并转发 Facet 的返回状态。使用 DELEGATECALL 意味着 Facet 对 Diamond 智能合约存储具有完全的控制权,因此需要仔细控制注册的 Facet 和函数。
要更新 Diamond 的注册函数,必须使用 diamondCut 内部函数2 。在 zkSync 的当前设计中,这只有在部署时(在构造函数中)和使用 Admin Facet 的 executeUpgrade 函数(由 onlyGovernor 保护)才可能。
Facet 没有关联的存储。相反,它通过 DELEGATECALL 直接访问 Diamond 的存储空间。为了实现 Facet 之间的互操作性并防止存储空间冲突,该标准提出了使用两种模式:Diamond Storage 和 AppStorage。zkSync 使用两种模式:
一个DiamondStorage 结构,负责与 Diamond 相关的功能,位于keccak256("diamond.standard.diamond.storage") - 1,
一个 AppStorage 结构,保存应用程序的状态并位于 Base 智能合约的第一个槽中,由所有 Facet 作为第一个父级继承。
总之,zkSync 对 Diamond 标准的实现使用了一组包含相关功能的 Facet,并将它们聚合在一个智能合约中。存储仅包含在两个结构中:DiamondStorage和AppStorage。功能分布在 4 个方面。
尽管 EIP-2535 允许 Diamond 智能合约具有原生的外部可访问函数,但 zkSync 目前没有这个功能,它将所有内容委托给其 fallback 函数中的 Facet,包括 Diamond 相关的功能。这就是为什么它被称为 Diamond Proxy 的原因。实际的 Diamond 相关功能位于只由 Proxy、Admin Facet 和 Getters Facet 使用的 Diamond 库中。
Diamond 的核心对象是 selectorToFacet 映射。它用于知道哪个 Facet 应该被调用来处理特定的函数选择器。
尽管理论上只有 mapping(bytes4 => address) 就足够了(因此在标准的示例中使用了它),但 zkSync 选择使用更复杂的数据结构来允许更高效的内省和更新,以及冻结功能。作为交换,该库需要特别注意确保内部数据的一致性和准确的簿记。
Diamond 实现了一个 Facet 地址和选择器之间双向的一对多映射:
每个 Facet 地址都映射到 facetToSelectors 中的选择器列表,和
每个选择器都映射到 selectorToFacet 中的单个 Facet 地址。
此外,它还在 address[] facets 中跟踪已用 Facet 地址的列表,并在 bool isFrozen 中跟踪全局冻结状态。
zkSync 的 Diamond 实现的概念实体关系图

主映射使用专门的对象来保存其元素的额外信息:
SelectorToFacet 有以下字段(请注意,所有这些值都由 Solidity 打包到一个槽中,从而提高了整体的 gas 消耗):
SelectorToFacet有以下字段(请注意,所有这些值都由 Solidity 打包到一个槽中,从而提高了整体的 gas 消耗)3:
address facetAddress (data),
bool isFreezable (metadata) and
uint16 selectorPosition (bookkeeping).
FacetToSelectors has these fields:
bytes4[] selectors (data), and
uint16 facetPosition (bookkeeping)
双向映射中的一对多关系在 FacetToSelectors 中由选择器数组表示,在 SelectorToFacet 中由选择器在相应数组中的位置表示。这意味着这些值需要由库保持同步,并且每个 Facet 最多可以注册 2^16 个选择器(这应该足够了)。这种簿记的优势在于,库不需要执行(可能代价高昂的)搜索来找到如何更新其内部数据结构,但代价是更多的预先计算和稍微更高的存储空间占用。
选择器的哈希值是 4 字节长的,可以用于快速查找选择器对应的 Facet。
EIP-2535 建议此函数为 external,但不要求这样做。这意味着需要在此函数的级别实现访问控制。
zkSync 原本可以使用 uint88 并在单个槽中保留结构。
函数签名的 4 字节长哈希值。请参阅官方文档 和4byte.directory以获得在线彩虹表。 ↩
zkSync 可以使用最多uint88,同时将结构保留在单个插槽内。 ↩
No activity yet