GnosisSafe.sol 学习

Posted MateZero

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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中,有两种数据结构用保存元素个数为动态的的集合,通常为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]=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)的。包括下面接下来的removeOwnerswapOwnerchangeThreshold都是限定自调用函数。

第一步:验证条件

第二步:添加新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数组,不用改变数组大小。

以上是关于GnosisSafe.sol 学习的主要内容,如果未能解决你的问题,请参考以下文章

GnosisSafe.sol 学习

GnosisSafe.sol 学习

GnosisSafe.sol 学习

GnosisSafe.sol 学习

GnosisSafe.sol 学习

GnosisSafe.sol 学习