Safful最近审计了钻石标准的一份实现代码,这一标准是一种新的可升级合约模式。撰写标准是一项值得赞许的事业,但钻石标准及其实现有许多引人担忧的地方。这份代码是过度工程的产物,附带了许多不必要的复杂性,所以现在我们不能推荐使用。
当然,钻石标准提议还处在草稿阶段,有成长和改进的空间。一套有用的可升级合约保证应该包含:
不幸的是,钻石提案没有满足所有这些要求。这实在太糟糕了,因为我们想看到的是一个可以解决、至少是减轻可升级合约的主要安全陷阱的标准。从根本上来说,标准的撰写人必须明确假设开发者会犯错,并且以开发出能缓解错误的标准为目标。
不过,我们 还是能从钻石提案中学到很多。请继续往下读:
钻石标准是由 EIP 2535 定义的、还在开发中的工作。提案的草稿声称要给予 delegatecall 方法提出合约升级的一种新范式。(我们曾撰写过一份关于合约如何升级的概述,仅供参考。)EIP 2535 提议使用:
基于 delegatecall 的升级方法主要使用两个组件:一个代理合约和一个实现合约
图 1. 单一实现合约的基于 delegatecall 的升级方法
用户与代理合约交互,代理合约向实现合约发送 delegatecall 调用实现合约内的函数。执行的是实现合约内的代码,但整套合约的 storage 保存在代理合约内。
使用了查找表,代理合约就可以向多个实现合约发起 delegatecall 调用,可根据要调用的函数来选择合适的实现合约:
图 2. 多实现合约的基于 delegatecall 的升级方法
这种模式不是什么新东西。之前也有其他项目使用过这样的查找表来实现可升级性。ColonyNetwork 就是一个例子。
钻石提案还建议使用 Solidity 最近引入的一个功能:任意的存储指针(arbitrary storage pointer)。这个功能名副其实,就是允许你把一个 storage 的指针指向任意一个位置。
因为 storage 都存储在代理合约里,实现合约的 storage 布局必须与代理合约的 storage 布局保持一致。在升级的时候,很难跟踪这种布局(此处有一个例子)。
这个 EIP 提议,每个实现合约都要有一个相关联的结构体(structure)来保管实现合约的变量(variables),然后用一个指针指向存储该结构体的 storage 位置。这类似于 “