深入解析Safe多签钱包智能合约:模块

概述

读者可以在前往我的博客获得更好的阅读体验。

在上一篇博客中,我们已经讨论了safe合约的代理部署和核心的GnosisSafe合约。在此博客内,我们主要讨论在上一篇文章内没有介绍的safe合约内各个模块的概念和代码。我们会按照各模块在GnosisSafe合约内出现的顺序进行解释。

OwnerManager

GnosisSafe.solsetUp函数中,我们使用了此模块中的setupOwners函数。

此模块主要涉及签名者的管理等功能。

setupOwners

此函数的功能为初始化签名者(owner)和需要签名的数量(threshold)变量。

在函数体的开始,我们看到一系列使用require的条件检查代码。代码中的注释已经较为详细的介绍了每个条件限制检查的目的,我们在此不再赘述。

为初始化owners映射,合约使用了for循环。在研究for循环前,我们首先给出owners的定义:

mapping(address => address) internal owners;

与我们直观上认为owners应该为一个列表不同,Gnosis使用了映射来管理owners,实现了近似于单向列表的功能。使用映射的一大好处是映射底层的存储使用了哈希进行存储可以实现快速的寻址查找,大幅降低了时间复杂度。

mapping底层的存储逻辑可以参考此文

由于使用了链表作为数据结构,这也导致初始化等步骤较为繁琐。我们首先以一种较为直观的方式介绍如何进行初始化。假设address[]ABC三部分构成。当我们进行初始化时,映射的变化如下:

第一次循环:
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符合以下条件:

  1. 不为地址00x1。前者属于特殊地址,后者属于构建owners结构的核心地址
  2. owner不能是合约自身
  3. 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,然后我们使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WongSSH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值