仔细看了遍GUSD的白皮书,把白皮书里的功能和代码结合起来分析下。
合约分离
分成几个合约,并且可以升级,有几个好处:
- 解决漏洞;
- 扩展系统新功能;
- 完善和优化系统;
- 暂停、阻止或撤销Token传输,如灾难性安全事件,或者法院或其他政府机构有法律要求时。
Proxy调用Impl,Proxy实现ERC20的标准接口,合约内部不保存逻辑和token数据;
Impl调用Store,Impl是ERC20的真正实现,token数量等都保存在Store合约中。
合约都发布到以太坊上,虽然这三个合约一旦发布就不能修改,但是可以通过修改Proxy中指定的Impl地址,实现Impl和Store合约的更新。
实际部署图在GUSD详解中也分析过,再看下部署图:
合约管理权限
对于高风险行为,需要离线审批。
Custodian实现了多重签名。
合约升级
上图是Impl(1)被Impl(2)合约替换后的关系图,Proxy指向了Impl(2),Impl(2)调用Store,而Store也只接收Impl(2)的合约调用。
Impl(1)合约仍然存在,在GUSD系统中已经没有作用,相当于作废了。
合约升级代码分析
在GUSD详解中分析过,有三个高风险操作需要通过Custodian授权,其中有一个就是升级Impl。
看看修改Impl地址的函数调用流程:
1、创建ERC20Proxy合约,而ERC20Proxy继承于ERC20ImplUpgradeable,在ERC20ImplUpgradeable构造函数中会设置erc20Impl地址为0x0
function ERC20ImplUpgradeable(address _custodian) CustodianUpgradeable(_custodian) public {
erc20Impl = ERC20Impl(0x0);
}
2、ERC20Proxy发布后,请求修改Impl的地址
function requestImplChange(address _proposedImpl) public returns (bytes32 lockId) {
require(_proposedImpl != address(0));
lockId = generateLockId();
implChangeReqs[lockId] = ImplChangeRequest({
proposedNew: _proposedImpl
});
emit ImplChangeRequested(lockId, msg.sender, _proposedImpl);
}
3、通过Custodian确认Impl的变化
function confirmImplChange(bytes32 _lockId) public onlyCustodian {
erc20Impl = getImplChangeReq(_lockId);
delete implChangeReqs[_lockId];
emit ImplChangeConfirmed(_lockId, address(erc20Impl));
}
发行Token
通过增加PrintLimiter实现在线和离线双重审核机制,离线Custodian可以授权PrintLimiter可以发行的数量,在这个数量内PrintLimiter指定的地址可以直接发行,超过这个数量后需要通过Custodian机制再次授权。
合约安全
- 离线Keys:高风险操作需要的Keys可以冷存储
- Key生成:硬件(HSMs)生成、存储和管理Keys
- 多重签名:高风险操作需要至少两个身份签名
- 时间锁:每个高风险操作即使批准后,也会有一段安全锁定期才会执行,可以在这安全锁定期内检测风险
- 撤销:未执行的操作可以撤销,在执行前错误和恶意行为可以被撤销。
多重签名、时间锁、撤销这些功能都在Custodian合约中实现,具体分析另见。
Hello! Your post has been resteemed and upvoted by @ilovecoding because we love coding! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!
Reply !stop to disable the comment. Thanks!