概述
读者可以在前往我的博客获得更好的阅读体验。
在上一篇博客中,我们已经讨论了safe
合约的代理部署和核心的GnosisSafe
合约。在此博客内,我们主要讨论在上一篇文章内没有介绍的safe
合约内各个模块的概念和代码。我们会按照各模块在GnosisSafe
合约内出现的顺序进行解释。
OwnerManager
在GnosisSafe.sol
的setUp
函数中,我们使用了此模块中的setupOwners
函数。
此模块主要涉及签名者的管理等功能。
setupOwners
此函数的功能为初始化签名者(owner
)和需要签名的数量(threshold
)变量。
在函数体的开始,我们看到一系列使用require
的条件检查代码。代码中的注释已经较为详细的介绍了每个条件限制检查的目的,我们在此不再赘述。
为初始化owners
映射,合约使用了for
循环。在研究for
循环前,我们首先给出owners
的定义:
mapping(address => address) internal owners;
与我们直观上认为owners
应该为一个列表不同,Gnosis
使用了映射来管理owners
,实现了近似于单向列表的功能。使用映射的一大好处是映射底层的存储使用了哈希进行存储可以实现快速的寻址查找,大幅降低了时间复杂度。
mapping
底层的存储逻辑可以参考此文
由于使用了链表作为数据结构,这也导致初始化等步骤较为繁琐。我们首先以一种较为直观的方式介绍如何进行初始化。假设address[]
由A
、B
、C
三部分构成。当我们进行初始化时,映射的变化如下:
第一次循环:
0x1 => A
第二次循环
0x1 => A
A => B
第三次循环
0x1 => A
A => B
B => C
跳出循环后
0x1 => A
A => B
B => C
C => 0x1
在代码中,我们使用require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this) && currentOwner != owner, "GS203");
要求owner
符合以下条件:
- 不为地址
0
和0x1
。前者属于特殊地址,后者属于构建owners
结构的核心地址 owner
不能是合约自身owner
不能与address[]
中的上一个元素相同,此处的currentOwner
即上一个循环中的owner
。
除此之外,我们又使用了require(owners[owner] == address(0), "GS204");
确保此地址没有在owners
中出现,避免重复。
在循环的最后使用owners[currentOwner] = owner;
完成与上一节点的链接工作,也使用了currentOwner = owner;
重置currentOwner
为下一循环进行准备。
在循环结束后,我们使用owners[currentOwner] = SENTINEL_OWNERS;
完成最后地址与0x1
的链接。也初始化了一些其他变量。
addOwnerWithThreshold
此函数用于增加在owners
中增加owner
,同时也修改threshold
。
我们假设在上述链表后增加D
地址。结果如下:
0x1 => D
D => A
A => B
B => C
C => 0x1
其中,owners[owner] = owners[SENTINEL_OWNERS];
完成D => A
的链接,而owners[SENTINEL_OWNERS] = owner;
完成0X1 => D
的链接。
剩余代码完成了对ownerCount
和使用changeThreshold
完成对threshold
的修正。
此处使用的changeThreshold
函数较为简单,代码如下:
function changeThreshold(uint256 _threshold) public authorized {
// Validate that threshold is smaller than number of owners.
require(_threshold <= ownerCount, "GS201");
// There has to be at least one Safe owner.
require(_threshold >= 1, "GS202");
threshold = _threshold;
emit ChangedThreshold(threshold);
}
相信读者可以直接读懂其含义,我们不再进行解释。
我们可以看到在函数定义中加入了authorized
修饰符,此修饰符定义位于src/common/SelfAuthorized.sol
中,代码如下:
contract SelfAuthorized {
function requireSelfCall() private view {
require(msg.sender == address(this), "GS031");
}
modifier authorized() {
// This is a function call as it minimized the bytecode size
requireSelfCall();
_;
}
}
显然,此修饰符的作用是保证此函数只能被自己调用。这是为了保证安全,因为增删修改owners
也应该通过多签名实现,即我们应该通过execTransaction
间接调用这些函数。所以函数的msg.sender
应该为address(this)
。
removeOwner
顾名思义,用于删除owner
,也可以使用changeThreshold
修改threshold
变量。
与增加成员不同,删除成员需要三个参数:
- prevOwner 链表中指向需删除元素的地址
- owner 需要删除的元素
- _threshold 新的
threshold
变量
假设我们要删除以下链表中的D
,则需要将prevOwner
设置为0x1
,将owner
设置为D
。
0x1 => D
D => A
A => B
B => C
C => 0x1
上述链表修改后的结果:
0x1 => A
A => B
B => C
C => 0x1
D => 0
在具体代码实现中,我们首先使用require(ownerCount - 1 >= _threshold, "GS201");
检查删除一位owner
后数量是否能满足_threshold
的要求。然后使用require(owner != address(0) && owner != SENTINEL_OWNERS, "GS203");
进行常规的地址校验。最后使用require(owners[prevOwner] == owner, "GS205");
确保prevOwner
位于链表内。
在进行具体的链表修改时,我们使用owners[prevOwner] = owners[owner];
修改prevOwner
的指向使链表跳过owner
,然后我们使用