GnosisSafe.sol 学习(二)
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-only
pragma 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 - <stefan@gnosis.pm>
/// @author Richard Meissner - <richard@gnosis.pm>
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 array
uint256 index = 0;
address currentOwner = owners[SENTINEL_OWNERS];
while (currentOwner != SENTINEL_OWNERS) {
array[index] = currentOwner;
currentOwner = owners[currentOwner];
index++;
}
return array;
}
}
可以看到,它的源码结构和ModuleManager
很类似,都是继承了SelfAuthorized
以限定自调用,都提供了初始化和增减及查询功能。实现起来稍微有些不同。
在Solidity中,有两种数据结构用保存元素个数为动态的的集合,通常为mapping
和array
。一般来讲,无序用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]=0x2
owners[0x2]=0x3
owners[0x3]=0x4
owners[0x4]=0x1
ownerCount = 3
threshold = 2
可以看到他们形成了一个闭环地址链。
注意For循环中有require
来验证地址不能被添加2次,也不能添加零地址和哨兵地址(添加哨兵地址会使哨兵作用失效)。
最后记录了当前owners
的数量,因为owners
是个mapping
,如果想得到它的记录数量必须使用一个新的状态变量来记录。
1.3 addOwnerWithThreshold 函数
我们学过了ModuleManager
之后,这个函数就显得相当简单,注意它是限定自调用(authorized
)的。包括下面接下来的removeOwner
,swapOwner
与changeThreshold
都是限定自调用函数。
第一步:验证条件
第二步:添加新owner到owners
链中
第三步:更新owners的数量和最小门槛
注意,这里添加之后哨兵地址就指向了最新添加的地址(因为这里一次只能添加一个)。
我们模拟执行一下 add 0x5 和 add 0x6,得到的结果如下:
owners[0x1]=0x6
owners[0x2]=0x3
owners[0x3]=0x4
owners[0x4]=0x1
owners[0x5]=0x2
owners[0x6]=0x5
ownerCount = 5
threshold = 2
这里有5个owner地址,因为哨兵地址0x1不算。它们和哨兵地址形成了一个闭环。
1.4 removeOwner 函数
这个和前面ModuleManager
学过的类似,断开链中间某个节点,然后两端连接起来。
不同的是前面多了一些条件验证,后面多了更新owners
的数量和最小门槛。
我们模拟执行一下 remove 0x03 0x04 3
。得到的结果如下:
owners[0x1]=0x6
owners[0x2]=0x3
owners[0x3]=0x1
owners[0x5]=0x2
owners[0x6]=0x5
ownerCount = 4
threshold = 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数组,不用改变数组大小。