GnosisSafe是以太坊区块链上最流行的多签钱包!它的最初版本叫 MultiSigWallet,现在新的钱包叫Gnosis Safe,意味着它不仅仅是钱包了。它自己的介绍为:以太坊上的最可信的数字资产管理平台(The most trusted platform to manage digital assets on Ethereum)。

1. OwnerManager.sol

1.1 源码及状态变量

这里我们接着学习GnosisSafe.sol。上次我们学到了ModuleManager,本次我们学习OwnerManager,这个OwnerManager顾名思义,是管理钱包owner(多签名单)的,我们先看它的源码:

// SPDX-License-Identifier: LGPL-3.0-onlypragma solidity >=0.7.0 <0.9.0;import "../common/SelfAuthorized.sol";/// @title OwnerManager - Manages a set of owners and a threshold to perform actions./// @author Stefan George - /// @author Richard Meissner - contract OwnerManager is SelfAuthorized {event AddedOwner(address owner);event RemovedOwner(address owner);event ChangedThreshold(uint256 threshold);address internal constant SENTINEL_OWNERS = address(0x1);mapping(address => address) internal owners;uint256 internal ownerCount;uint256 internal threshold;/// @dev Setup function sets initial storage of contract./// @param _owners List of Safe owners./// @param _threshold Number of required confirmations for a Safe transaction.function setupOwners(address[] memory _owners, uint256 _threshold) internal {// Threshold can only be 0 at initialization.// Check ensures that setup function can only be called once.require(threshold == 0, "GS200");// Validate that threshold is smaller than number of added owners.require(_threshold <= _owners.length, "GS201");// There has to be at least one Safe owner.require(_threshold >= 1, "GS202");// Initializing Safe owners.address currentOwner = SENTINEL_OWNERS;for (uint256 i = 0; i < _owners.length; i++) {// Owner address cannot be null.address owner = _owners[i];require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this) && currentOwner != owner, "GS203");// No duplicate owners allowed.require(owners[owner] == address(0), "GS204");owners[currentOwner] = owner;currentOwner = owner;}owners[currentOwner] = SENTINEL_OWNERS;ownerCount = _owners.length;threshold = _threshold;}/// @dev Allows to add a new owner to the Safe and update the threshold at the same time.///This can only be done via a Safe transaction./// @notice Adds the owner `owner` to the Safe and updates the threshold to `_threshold`./// @param owner New owner address./// @param _threshold New threshold.function addOwnerWithThreshold(address owner, uint256 _threshold) public authorized {// Owner address cannot be null, the sentinel or the Safe itself.require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this), "GS203");// No duplicate owners allowed.require(owners[owner] == address(0), "GS204");owners[owner] = owners[SENTINEL_OWNERS];owners[SENTINEL_OWNERS] = owner;ownerCount++;emit AddedOwner(owner);// Change threshold if threshold was changed.if (threshold != _threshold) changeThreshold(_threshold);}/// @dev Allows to remove an owner from the Safe and update the threshold at the same time.///This can only be done via a Safe transaction./// @notice Removes the owner `owner` from the Safe and updates the threshold to `_threshold`./// @param prevOwner Owner that pointed to the owner to be removed in the linked list/// @param owner Owner address to be removed./// @param _threshold New threshold.function removeOwner(address prevOwner,address owner,uint256 _threshold) public authorized {// Only allow to remove an owner, if threshold can still be reached.require(ownerCount - 1 >= _threshold, "GS201");// Validate owner address and check that it corresponds to owner index.require(owner != address(0) && owner != SENTINEL_OWNERS, "GS203");require(owners[prevOwner] == owner, "GS205");owners[prevOwner] = owners[owner];owners[owner] = address(0);ownerCount--;emit RemovedOwner(owner);// Change threshold if threshold was changed.if (threshold != _threshold) changeThreshold(_threshold);}/// @dev Allows to swap/replace an owner from the Safe with another address.///This can only be done via a Safe transaction./// @notice Replaces the owner `oldOwner` in the Safe with `newOwner`./// @param prevOwner Owner that pointed to the owner to be replaced in the linked list/// @param oldOwner Owner address to be replaced./// @param newOwner New owner address.function swapOwner(address prevOwner,address oldOwner,address newOwner) public authorized {// Owner address cannot be null, the sentinel or the Safe itself.require(newOwner != address(0) && newOwner != SENTINEL_OWNERS && newOwner != address(this), "GS203");// No duplicate owners allowed.require(owners[newOwner] == address(0), "GS204");// Validate oldOwner address and check that it corresponds to owner index.require(oldOwner != address(0) && oldOwner != SENTINEL_OWNERS, "GS203");require(owners[prevOwner] == oldOwner, "GS205");owners[newOwner] = owners[oldOwner];owners[prevOwner] = newOwner;owners[oldOwner] = address(0);emit RemovedOwner(oldOwner);emit AddedOwner(newOwner);}/// @dev Allows to update the number of required confirmations by Safe owners.///This can only be done via a Safe transaction./// @notice Changes the threshold of the Safe to `_threshold`./// @param _threshold New threshold.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);}function getThreshold() public view returns (uint256) {return threshold;}function isOwner(address owner) public view returns (bool) {return owner != SENTINEL_OWNERS && owners[owner] != address(0);}/// @dev Returns array of owners./// @return Array of Safe owners.function getOwners() public view returns (address[] memory) {address[] memory array = new address[](ownerCount);// populate return arrayuint256 index = 0;address currentOwner = owners[SENTINEL_OWNERS];while (currentOwner != SENTINEL_OWNERS) {array[index] = currentOwner;currentOwner = owners[currentOwner];index++;}return array;}}

可以看到,它的源码结构和ModuleManager很类似,都是继承了SelfAuthorized以限定自调用,都提供了初始化和增减及查询功能。实现起来稍微有些不同。

在Solidity中,有两种数据结构用保存元素个数为动态的的集合,通常为mappingarray。一般来讲,无序用mapping,有序用array,按道理讲我们的owners应该使用一个数组(最初始版本的就是),但mappig内的元素操作肯定优于数组(数组内元素需要遍历或者移动等),因此合约采用了一个技巧,使用了一个类似地址链的mapping来管理地址集合。同样,ModuleManager中使用地址链也是使用了这个技巧。

这个技巧也是挺实用的,相当于实现了一个支持查询,遍历,插入,删除和交换的列表,并且只使用了一个简单的mapping

合约定义中我们可以看到,OwnerManager 也继承了SelfAuthorized,而我们在前面的学习中得知,ModuleManager也继承了SelfAuthorized。那么我们的GnosisSafe会不会继承SelfAuthorized两次呢?实际不会,它的源码中只会保存一个副本。

我们仍然跳过事件定义。

常量哨兵地址定义为地址1,当然你也可以定义为地址666。

这里哨兵地址的意思我想应该是到这个地址就截止(放哨的作用)了。因为owners之间都是平等的,不存在谁先谁后的问题,虽然它通常指向最后添加的owner地址,注意我说的这里是通常。

1.2 setupOwners 函数

从require需求就可以看到,该函数为初始化函数,只能调用一次,注意它的可见性是internal的。其两个参数_owners_threshold分别为初始owner列表和最小门槛人数(这个最小门槛不能超过owners的数量,也不能为0)。

三个require则是检查这些条件。

接下来使用了一个for循环来遍历赋值owners,注意这里哨兵地址指向的是第一个添加的owner,而不是最后添加的owner。所以我上面提到了一个通常。

我们模拟一下,假定初始owner列表为[0x2,0x3,0x4],初始门槛为3签2,那么两个参数分别为[0x2,0x3,0x4]2

执行setupOwners后的结果应该为:

owners[0x1]=0x2owners[0x2]=0x3owners[0x3]=0x4owners[0x4]=0x1ownerCount = 3threshold = 2

可以看到他们形成了一个闭环地址链。

注意For循环中有require来验证地址不能被添加2次,也不能添加零地址和哨兵地址(添加哨兵地址会使哨兵作用失效)。

最后记录了当前owners的数量,因为owners是个mapping,如果想得到它的记录数量必须使用一个新的状态变量来记录。

1.3 addOwnerWithThreshold 函数

我们学过了ModuleManager之后,这个函数就显得相当简单,注意它是限定自调用(authorized)的。包括下面接下来的removeOwnerswapOwnerchangeThreshold都是限定自调用函数。

第一步:验证条件

第二步:添加新owner到owners链中

第三步:更新owners的数量和最小门槛

注意,这里添加之后哨兵地址就指向了最新添加的地址(因为这里一次只能添加一个)。

我们模拟执行一下 add 0x5 和 add 0x6,得到的结果如下:

owners[0x1]=0x6owners[0x2]=0x3owners[0x3]=0x4owners[0x4]=0x1owners[0x5]=0x2owners[0x6]=0x5ownerCount = 5threshold = 2

这里有5个owner地址,因为哨兵地址0x1不算。它们和哨兵地址形成了一个闭环。

1.4 removeOwner 函数

这个和前面ModuleManager学过的类似,断开链中间某个节点,然后两端连接起来。

不同的是前面多了一些条件验证,后面多了更新owners的数量和最小门槛。

我们模拟执行一下 remove 0x03 0x04 3 。得到的结果如下:

owners[0x1]=0x6owners[0x2]=0x3owners[0x3]=0x1owners[0x5]=0x2owners[0x6]=0x5ownerCount = 4threshold = 3

这里owner少了一个0x4,所以只有4个了,它们仍然和哨兵地址形成了一个闭环。

1.5 swapOwner 函数

相对于ModuleManager而言,这是一个新增加的一个函数,用来交换owner,也就是同时进行减小owner和增加owner的操作。那为什么ModuleManager没有呢?这个笔者也不得而之。

先增后减操作没有什么,如果先减后增,则有可能减之后不满足threshold,所以可以使用一个swap来避免此问题,同时简化操作。

ModuleManager没有threshold,所以随便先加后减和先减后加都行,所以我想就不需要swap了吧(-_-)。

这个函数的内容就是在地址链中将某个旧值更新为新的值, 同时更新新的值的指向和它前一个值指向,使闭环仍然连通。

最后触发了两个事件。

1.6 changeThreshold 函数

很简单,检查并更新threshold的值,注意它是外部函数。也就是我们可以直接发起一个交易仅改变threshold而不改变任何owner,比如将3签2改成3签3。

从检查条件我们可以得知,最少需要一个owner,无法将owner减小至0,因为门槛最小值为1。

1.7 getThreshold 函数

返回 threshold的值。

1.8 isOwner 函数

也很简单,返回是否owner

1.9 getOwners 函数

从哨兵指向的地址遍历整个闭环,返回整个owner数组。其实可以从闭环中任意一个节点遍历,但是因为只有哨兵地址是已知的,是固定的,所以这里是从哨兵地址开始遍历,否则还需要输入一个起始地址作为参数。

因为owner的数量不可能非常多,所以这里不需要分页。

又因为没有分页,而又知道owner的具体数量,所以可以返回确切的owner数组,不用改变数组大小。