EIP2535 Diamonds 如何降低智能合约系统的Gas费用

本文探讨了智能合约之间交互所导致的高gas成本,并提出EIP2535 Diamonds框架作为减少external function calls和gas成本的解决方案。通过将多个合约组合为一个“diamond”,可以显著降低合约之间的调用成本,同时避免单个合约的大小限制。

Gas成本对于用户来说,会随着一个已部署的智能合约的扩展而增加,如果该合约变成两个需要通信的已部署合约时,就会增加。当需要更多功能时,就会部署更多合约并使其相互交互。需要通信或交互的智能合约越多,用户的Gas费用就越高。

合同之间交互时Gas费用增加的原因是一个合约调用另一个合约的函数,这种情况称为外部函数调用 — 是昂贵的。在撰写本文时,典型的外部函数调用大约需要2600多个Gas。

为什么不将所有功能整合到一个大型智能合约中呢?那样可以消除由于合约调用其他合约的外部函数而产生的所有Gas费用。这样做的问题之一是单个合约的大小是有限制的。智能合约的代码被编译成称为字节码的数据,相应的字节码被部署并存储在区块链上。智能合约字节码的最大大小为24.5KB。因此,这限制了智能合约中可以存在的功能数量。

因此,对于开发者来说,开发一个智能合约时往往会触及限制,于是创建另一个与第一个合约通信的智能合约。或许这种情况会不断重复,部署更多合约并产生越来越多的合约交互,而用户需要为此支付费用。

同时请记住,当一个合约调用另一个合约时,通常需要一个权限系统。你不希望任何人或任何物体调用智能合约的某些函数,只有获得批准的合约可以调用。权限系统需要读取状态变量,这样的操作是昂贵的。这进一步增加了调用外部函数的Gas费用。

如何减少多合约的Gas费用?

是否有可能减少许多外部函数调用的方式?。存在一个通用的轻量级智能合约框架,用于实现智能合约系统。它处理许多事情,比如24.5KB字节码大小限制、可完全或部分不变的细粒度升级、升级的标准化和透明度、如何以组织化的方式系统性地扩展现有的已部署智能合约系统、以及用户界面的自动化可视化等。 并且它可以帮助你减少合约之间的外部函数调用数量,从而降低Gas费用。

这个智能合约框架是EIP2535 Diamonds,也有一些人称之为钻石标准。它是如何工作的?

我承认,这需要一些理解。但任何软件框架也是如此。其核心是一种代理智能合约,利用任意数量其他智能合约的代码。这里有一个介绍:Introduction to EIP-2535 Diamonds

需要知道的一件事是,在钻石上调用外部函数会导致它读取一个状态变量并从另一个合约检索代码,这会消耗Gas。在钻石上调用外部函数基本上需要2次外部函数调用和1次状态读取。注意,任何支持升级的代理合约模式都具有这种Gas成本

在单个普通智能合约上调用外部函数的运行时Gas成本小于在钻石上调用时的成本。但我们并不是在谈论单个智能合约。我们讨论的是一个需要相互调用才能工作的更大项目。这个钻石是如何超越的?

简单而可能不透明的答案是,钻石可以将外部函数调用转换为内部函数调用内部函数调用便宜得多,与外部函数调用不可同日而语,并且内部函数不需要权限系统(与外部函数不同),这进一步降低了Gas费用。

一个示例

如果你想跳过示例并查看如何将外部函数转换为内部函数,请访问 此处

为了说明钻石如何在智能合约系统中节省Gas,这里有一个简单的虚构示例:

  1. 假设我们实现并部署了一个名为CryptoKitties的ERC721合约,并包含我们自定义的函数 `attachHat(uint256 _kittyId, uint256 _hatId) external`。

  2. 我们实现并部署了一个名为KittyHats的ERC1155智能合约。

  3. 我们实现并部署了一个名为Marketplace的合约,该项目用于列出和销售KittyHats和CryptoKitties。

  4. 我们实现了一个公会合约。

在我们的示例中,这四个合约无法在单个合约中实施,因其违反最大合约大小限制,并且如果它们都适合在一个合约中,没有一个好的方法来组织代码,也会变得难以维护。如果它们都在一个合约中,升级将需要重新部署整个合约。

在CryptoKitties上调用 `attachHat`函数将在特定的ERC721猫中添加特定的ERC1155帽子。

以下是当调用 `attachHat`时所进行的外部函数调用:

  1. 在CryptoKitties合约上调用 `attachHat`。这是第一个外部函数调用。

  2. `attachHat` 函数对Marketplace合约进行外部函数调用,以确保特定的帽子未被列为出售。因为禁止将帽子附加到待售的猫上。这是第二个外部函数调用。

  3. `attachHat` 函数在KittyHats合约上调用一个名为 `transferToKitty` 的外部函数,以将特定帽子的所有权转移到CryptoKitties合约地址。这是第三个外部函数调用。

  4. `transferToKitty` 读取一个状态变量,以检查是否只有CryptoKitties合约在调用它,因为我们不想让任何人用这个函数转移帽子。 `transferToKitty` 函数只允许将帽子转移给属于公会的猫。因此,`transferToKitty` 函数对公会合约进行了外部函数调用,以了解该猫是否属于某个公会。这是第四个外部函数调用和一个状态读取。

在上述示例中,共需要4个外部函数调用和1次状态读取。

如果这是一个钻石

让我们看看如果这是一个钻石时所需的外部函数调用,并看看这个钻石的构成。此处还有一个作为钻石的示例源代码链接:Source Code Example

与之前一样,实现并部署了一个ERC721的CryptoKitties合约、ERC1155的KittyHats合约、Marketplace合约和公会合约。

一个钻石代理合约被部署。我将其简称为“钻石”。

这四个合约(CryptoKitties、KittyHats、Marketplace、公会)被添加到这个钻石中。这意味着有一个以太坊地址(钻石地址),它具有所有四个合约的外部函数。如果需要,你可以向这个钻石添加更多合约。可以添加多少个?无论你想添加多少都可以,没有限制。不过,函数名称和参数类型在钻石中是唯一的。钻石中不能有两个具有相同名称和参数类型的函数。在这种情况下,钻石为猫实现了ERC721,为帽子实现了ERC1155,这能够工作,因为ERC1155函数被设计为不会与ERC721发生冲突。

让我们在钻石上调用 `attachHat`时,查看所需的外部函数调用:

  1. 在钻石上调用 `attachHat`。这产生2个外部函数调用和1次状态读取。因为这是所有外部函数调用在钻石上所需的固定成本。

就是这样。CryptoKitties、KittyHats、Marketplace和公会之间的其余函数调用被转换为内部函数调用,消耗的Gas非常少。

所以这是4个外部调用和1次状态读取,转换为仅2个外部调用和1次状态读取。

但如果有更多的内容,并且在常规合约中,一个单一的函数需要5、6或10个外部函数调用?在钻石中,如果它们来自与合同和钻石中添加的合约之间的通信,这些外部函数调用都将消失。因此,钻石对其控制下的任何交叉合约交互都有固定的Gas成本。

在此查看此钻石的示例源代码:Gist Source Code

如何将外部函数调用转换为钻石的内部函数调用?

有几种方法。

我最喜欢的方法是在Solidity库中编写内部函数,并在将添加到钻石的合约中导入这些库。

重要的是要理解,Solidity库中的内部函数与Solidity库中的外部函数工作方式非常不同。拥有外部函数的Solidity库与使用它的合约是分开部署的。合约给拥有外部函数的Solidity库进行外部函数调用。Solidity库中的内部函数不是单独部署的,而是添加到使用它们的合约的字节码中。

功能可以在Solidity库的内部函数中写一次,并导入到任何添加到钻石的合约中。只有实际在特定合约中使用的内部函数才会添加到该合约的字节码中。这使得可以编写较大的Solidity库,而不会使导入它们的合约的大小膨胀。

在钻石中共享内部函数的另一种方式是编写只包含内部函数的合约,并在添加到钻石的合约中继承它们。SolidState Solidity智能合约库使用了这种方法。

这一切之所以能够实现,是因为添加到钻石的合约读取和写入的是钻石代理的合约存储,而不是它们自己的存储。

共享合约间的内部函数虽然会导致在不同合约间重复相同的字节码,这会在部署上消耗Gas。但通常还是应该优化运行时Gas费用,而不是部署Gas费用。

并且应该知道的是,合同可以被写成在部署后可重复使用。它们可以通过不同的钻石在链上重复使用。

顺便提一下,添加到钻石的合约被称为`facets`,以将它们与普通或其他种类的合约区分开。

如果你想查看一个正在运行的、持有数百万美元资产的钻石的源代码,该钻石在各个Facet之间共享内部函数,请查看Aavegotchi 钻石Beanstalk 钻石

使用EIP2535钻石节省Gas的第二种方式

由于EIP2535钻石打破了合约最大大小限制,任何数量的外部函数都可以添加到钻石中,因此更可能为特殊用例或为特定用例能够优化Gas的函数添加提供可能。一个很好的例子是将ERC721批量转移函数添加到钻石中,以减少一次性交易中传输多个NFT的Gas费用。

Diamond Discord

使用EIP2535钻石节省Gas的第三种方式

将Solidity优化器设置在高运行设置下,减少调用外部函数的Gas费用,但增加合约的字节码大小。

在较大的合约接近24.5KB最大合约大小限制时,无法使用高运行设置,因为这会导致合约超出大小限制。但借助钻石,一个大型合约可以被拆分成多个小合约,而这些合约可以使用高运行设置(例如2000)以进一步减少外部函数调用的Gas费用。

  • 原文链接: eip2535diamonds.substack...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
eip2535diamonds
eip2535diamonds
江湖只有他的大名,没有他的介绍。