钻石合约简介
什么是钻石合约?
钻石合约是 EIP-2535 提案中用到的术语,这个 EIP-2535 是一个模块化智能合约系统的标准,可以在部署后扩展。
钻石合约的优势
钻石合约的优势如下
- 单个地址可以管理无限的合约方法。主要是方便调用者调用,以往的包含多个智能合约的项目,调用者需要维护多个地址去和合约交互,使用钻石合约的话,调用者只需要和一个地址交互就 OK 了。
- 合约可以突破 24KB 大小的合约大小限制。以往如果合约大小超过 24KB 的话我们需要优化合约大小,让合约尽可能在 24KB 以下,比如拆分合约等等。但是这样会引入更多的合约地址,维护边的更加麻烦。
- 钻石合约提供了一种组织合约代码和数据的方法。面对比较复杂的合约,钻石合约提供了相对比较好的结构设计,让不同的功能组合链接的时候更加有效率。
- 钻石合约也支持合约升级。因为钻石合约可以管理几乎无限的合约方法,实现钻石合约升级也就不是难事。
理解钻石合约机制
术语理解
Facet: 其实就是逻辑合约,就是我们的业务合约
Diamond: 是代理合约
DiamondCut: 是维护代理合约逻辑合约里的方法和地址关系的一个类,我觉得可以把 DiamondCut 理解成一个 HUB,负责插拔我们业务合约的一个类。
DiamondLoupe: 基本上提供的都是 view 方法,查询逻辑合约提供什么方法,以及方法对应的逻辑合约地址.
这些术语可以参考登链社区的这篇文章的说明,这个比喻能很好的建立对这些概念的更加直观的理解。
此外,术语“钻石”用来建立一个形象的概念,用于了解其如何工作。真正的钻石有不同的侧面,叫做切面(facet)。可以设想,一个在以太坊钻石合约也有不同的切面。每一个钻石借用功能的合约都是不同的侧面或切面(facet)。 钻石标准使用类比的方式扩展了“钻石切割”的功能 ,用于增加,替换,或删除切面和功能。这类似于给予一个真正的钻石新切面,而是通过合约来切割。 此外,钻石标准提供了称为“放大镜(The Loupe)”的功能,返回关于切面的信息和钻石存在的功能。在钻石行业,“放大镜”是一种用来检查钻石的工具。
钻石合约本质上还是代理模式,代理去解决合约升级拓展的时候通常需要解决两个问题,第一个就是方法转发的问题,第二个就是数据存储冲突的问题。
方法转发
具体的实现官方文档如下
A diamond stores within it a mapping of function selector to facet address, for example selectorToFacet. When an external function is called on a diamond its fallback function is executed. The fallback function finds in the selectorToFacet mapping which facet has the function that has been called and then executes that function from the facet using delegatecall.
A diamond can use a
diamondCutfunction to add/replace/remove any number of functions from any number of facets in a single transaction.diamondCutupdates the mapping of function selector to facet address. Other such functions can be used.
简单翻译
钻石合约存储了逻辑合约地址和方法的映射,当通过代理合约调用方法的事后,代理合约的 fallback 方法执行,具体逻辑是 fallback 会根据之前维护的映射关系去查找具体的方法对应的逻辑合约的地址,找到后通过 delegatecall 调用对应逻辑合约的方法。
钻石合约使用
diamondCut合约去增加/替换/移除逻辑合约的方法(这儿有个不明白的地方是逻辑合约肯定部署好是不能改变的,我不明白什么场景会增加逻辑合约的方法,除非是刚开始不想暴露出来方法,现在想要暴露出来了),diamondCut会更新映射关系。
所以整体看下来的话,钻石合约就是比普通的合约增加了一个中间的插槽层。官方的文档基本上把这些机制说的还挺明白了,我之前看别的文章都没有官方的这个解释说的明白。
存储冲突
钻石合约这种设计方案是如何解决存储冲突的问题呢?钻石合约集中中,每个逻辑合约使用一个结构体进行数据存储,然后根据不同的 hash 值作为这个结构体存储的 slot,只要每个逻辑合约都使用自己的 hash 值的话,那就不会发生存储冲突的问题。
// A contract that implements Diamond Storage.
library LibA {
// This struct contains state variables we care about.
struct DiamondStorage {
address owner;
bytes32 dataA;
}
// Returns the struct from a specified position in contract storage
// ds is short for DiamondStorage
function diamondStorage() internal pure returns(DiamondStorage storage ds) {
// Specifies a random position in contract storage
// This can be done with a keccak256 hash of a unique string as is
// done here or other schemes can be used such as this:
// bytes32 storagePosition = keccak256(abi.encodePacked(ERC1155.interfaceId, ERC1155.name, address(this)));
bytes32 storagePosition = keccak256("diamond.storage.LibA");
// Set the position of our struct in contract storage
assembly {ds.slot := storagePosition}
}
}总结
钻石合约通过对合约进行结构化设计,将合约的实现不断拓展,突破了以往传统合约的开发限制,但也增加了合约结构的复杂性。目前(2022-06-06)这个钻石合约还在审查阶段,对于钻石合约的具体实现方案,目前官方给出了三种实现方案,根据不同的场景去使用,比如有的是简化整体实现设计,有的是节省 Gas。
我自己的想法是,如果不是对钻石合约非常理解的话,还是慎重使用,因为相对于 Openzeppelin 提供的代理方案来说,钻石合约机制并没有提供完善的脚手架以及安全检查机制,很可能因为不小心出现的问题造成损失,相对比的话 Openzeppelin 对代理升级方案提供了完善的安全检查机制和部署脚手架,防止开发者出现不小心造成的安全漏洞。如果之后 Openzepplin 提供了对钻石合约的实现的话,我觉得那将会是比较好的时机采用钻石机制来实现我们复杂的合约机制。但是 Openzeppeplin 目前并没有支持钻石合约的计划。
参考地址:
- EIP-2535: Diamonds, Multi-Facet Proxy
- 登链社区-[译]通过钻石标准解决以太坊合约大小限制
- Solidity-Layout of State Variables in Storage
- Solidity-Access to External Variables, Functions and Libraries
- Github-Diamond Implementation
关注我的微信公众号,我在上面会分享我的日常所思所想。

