AccessControlDefaultAdminRules合约是对AccessControl合约的增强,主要是对默认管理员身份组(默认管理员身份组id就是全0的bytes32)的相关操作进行了控制,具体体现在:
1、“默认管理员”角色组(该角色组具有授权、取消授权其他角色组账户的权力,前提是其他角色组的管理员身份id是默认管理员,如果其他身分组设置了指定的管理员身份组,那就跟默认管理元没有关系了)中最多只能有一个账户;
2、进行“默认管理员”角色组中账户变更时,通过两步完成,原始账户先发起账户变更,同时会设置一个时间延迟,新的账户需要在时间延迟后发起接受,才能完成管理员账户转移。在新账户发起接受之前,原始帐户可以取消账户变更;
3、”默认管理员”角色组中账户变更时的延迟参数,支持实时修改;
4、其他任何身份组无权对“默认管理员”
可用实例如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {AccessControlDefaultAdminRules} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol";
contract MyAccessControlDefaultAdminRules is AccessControlDefaultAdminRules {
constructor() AccessControlDefaultAdminRules(
3 days,
msg.sender // Explicit initial `DEFAULT_ADMIN_ROLE` holder
) {}
}
在初始化阶段,指定了延迟时间(3天)和管理员身份组账户(合约创建账户)。
编译后对外暴露接口为:
在解释各个接口之前,大家要先有延迟生效的概念,即发起了账户变更、延迟参数变更,都是需要等待一段时间后才能生效的,保留了一个可以撤回错误操作的窗口期。
按照逻辑先后顺序先对管理员身份组账户变更操作的接口如下:
1、 beginDefaultAdminTransfer:将“默认管理员”身份组的账户变更为新账户,同时设置生效的时间点,只能由当前“默认管理员”身份组账户发起;
2、cancelDefaultAdminTransfer:在新账户接受成为“默认管理员”身份组账户之前,取消上一步的账户变更,只能由当前“默认管理员”身份组账户发起;
3、acceptDefaultAdminTransfer:接受“默认管理员”身份组的账户变更,只有在第一步设置的生效时间之后才能成功发起,且只能由发起变更时指定的新账户发起;
对”默认管理员”身份组的账户变更中使用到的延迟参数操作接口如下:
1、changeDefaultAdminDelay:修改账户变更时“时间延迟”参数,设置本次修改的生效时间点,只能由当前“默认管理员”身份组账户发起,“时间延迟”参数修改后生效也有延迟;
2、rollbackDefaultAdminDelay;取消上一步“时间延迟”参数的修改,只能由当前“默认管理员”身份组账户发起,该操作只有在前一步设置的生效时间点之前才有效;
权限管理相关的接口如下:
1、grantRole:身份组授权,不允许对“默认管理员”身份组操作;
2、revokeRole:身份组解除授权,不允许对“默认管理员”身份组操作;
3、renounceRole:解除账户对某身份组的授权,只能由解除账户自身发起;如果是对“默认管理员”身份组操作,则也是要结合beginDefaultAdminTransfer进行两步操作,通过beginDefaultAdminTransfer将账户变更为address(0),然后由原始的“默认管理员”身份组账户调用renounceRole进行接受,同样只能在设置的生效时间点之后才能才成功发起,要注意因为没有其他角色组能对“默认管理员”管理,在renounceRole“默认管理员”中唯一的有效账户后,没有版本再为“默认管理员”身份组指定新账户(除非还实现了其他方法调用了内部函数_grantRole);
状态变量查看函数如下:
1、DEFAULT_ADMIN_ROLE:查看“默认管理员”角色id
2、defaultAdmin、owner:查看“默认管理员”中的账户;
3、defaultAdminDelay:查看生效中的延迟参数,该参数会参与生效时间点的计算;
4、defaultAdminDelayIncreaseWait:查看增大延迟参数,默认的最大的等待时间,当前设置的是5 days;
5、pendingDefaultAdmin:获取待生效“默认管理员”身份组账户,和生效时间点;
6、pendingDefaultAdminDelay:获取待生效的“时间延迟”参数,和该参数的生效时间点;
最后就是权限管理接口grantRole、revokeRole,除了不能操作“默认管理员”身份组,其他同AccessControl。
举例如下:
用A表示当前“默认管理员”角色组账户,A想要发起管理员账户转移,目标账户为B,则可以用账户A调用beginDefaultAdminTransfer(B);账户B想要立刻接受成为管理员账户,所以当天账户B发起了acceptDefaultAdminTransfer(),但是发现接受失败了,因为账户B想要接受成功,必须要等待3天;在第2天的时候,A突然发现其实应该要转移给账户C,于是调用了cancelDefaultAdminTransfer(),将向B转移管理员身份这个动作撤销,然后立刻调用beginDefaultAdminTransfer(C),将管理员身份转移给C;B在等待了3天后,再次发起acceptDefaultAdminTransfer(),仍然失败,因为他已经不是潜在管理员账户;A在发起向C转移管理员的第2天,想让C再多等几天,于是发起了changeDefaultAdminDelay(10 days),但是这个修改时间延迟的动作本身也会延迟生效,延迟时间为5天(系统预设的延迟时间增加时最大等待时间),所以C在beginDefaultAdminTransfer(C)发起的3天后发起acceptDefaultAdminTransfer(),交易能够成功,此刻时间延迟参数修改为10天还没有生效,时间延迟仍然为3天,C成为新的“默认管理员”身份组账户;如果A在发起向C转移管理员的当天,想让C少等几天,于是发起了changeDefaultAdminDelay(1 days),但是这个修改时间延迟的动作本身也会延迟生效,延迟时间为2天(需要等待新的时间延迟域旧的时间延迟之间的差值),所以C在beginDefaultAdminTransfer(C)发起的2天后发起acceptDefaultAdminTransfer(),交易失败,此刻时间延迟参数修改为1天还没有生效,时间延迟仍然为3天;
代码详解
状态变量与构造函数如下:
// pending admin pair read/written together frequently
//待生效“默认管理员”身份组账户,在没有账户变更情况下,为address(0)
address private _pendingDefaultAdmin;
//管理员账户变更生效时间点,在没有账户变更情况下,为0
uint48 private _pendingDefaultAdminSchedule; // 0 == unset
//有效的延迟时间参数,在计算生效时间点时,会参与计算
uint48 private _currentDelay;
//当前有效的“默认管理员”身份组账户
address private _currentDefaultAdmin;
// pending delay pair read/written together frequently
//待生效的延迟时间参数
uint48 private _pendingDelay;
//待生效的延迟时间参数的生效时间点,即_pendingDelaySchedule时间点之后,_currentDelay在逻辑上(有可能_currentDelay的内容还没有被替换为_pendingDelay的值)的取值是就是_pendingDelay
uint48 private _pendingDelaySchedule; // 0 == unset
/**
* @dev Sets the initial values for {defaultAdminDelay} and {defaultAdmin} address.
*/
constructor(uint48 initialDelay, address initialDefaultAdmin) {
if (initialDefaultAdmin == address(0)) {
revert AccessControlInvalidDefaultAdmin(address(0));
}
_currentDelay = initialDelay;
_grantRole(DEFAULT_ADMIN_ROLE, initialDefaultAdmin);
}
综合看来,核心参数只有两个,一个是“默认管理员”身份组账户,一个是延迟参数,其他都是围绕这两个参数的待生效参数和生效时间点参数。
先看下“默认管理员”账户变更beginDefaultAdminTransfer的逻辑:
//账户变函数
function beginDefaultAdminTransfer(address newAdmin) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_beginDefaultAdminTransfer(newAdmin);
}
/*账户变更函数具体实现逻辑:
首先生成生效时间点,在当前时间基础上加上“延迟时间”参数;
然后将待生效的新账户和待生效的时间点设置到账户待生效相关字段中;
*/
function _beginDefaultAdminTransfer(address newAdmin) internal virtual {
uint48 newSchedule = SafeCast.toUint48(block.timestamp) + defaultAdminDelay();
_setPendingDefaultAdmin(newAdmin, newSchedule);
emit DefaultAdminTransferScheduled(newAdmin, newSchedule);
}
/*计算“时间延迟”参数:
如果“时间延迟”参数被发起过修改(判定方式是,“时间延迟”参数的待生效时间节
点(_pendingDelaySchedule)已经设置,即:_isScheduleSet(schedule)=true);然后判断如果当前
时间已经过了生效时间点,即_hasSchedulePassed(schedule)=true,那么说明待生效的“时间延迟”参数
已经可以起作用了,所以这时返回待生效的“时间延迟”参数作为真实的延迟时间。
如果上述条件没有同时满足,则说明目前的“时间延迟”参数_currentDelay是有效的,则返回作为真实的延迟时间
*/
function defaultAdminDelay() public view virtual returns (uint48) {
uint48 schedule = _pendingDelaySchedule;
return (_isScheduleSet(schedule) && _hasSchedulePassed(schedule)) ? _pendingDelay : _currentDelay;
}
再看下接受“默认管理员”账户变更acceptDefaultAdminTransfer的逻辑:
//控制了该方法只能由待生效账户调用
function acceptDefaultAdminTransfer() public virtual {
(address newDefaultAdmin, ) = pendingDefaultAdmin();
if (_msgSender() != newDefaultAdmin) {
// Enforce newDefaultAdmin explicit acceptance.
revert AccessControlInvalidDefaultAdmin(_msgSender());
}
_acceptDefaultAdminTransfer();
}
//先判定账户待生效的时间点是否已经满足
//满足之后,进行“默认管理员”身份组账户变更具体操作,包括:
//解除旧帐户在“默认管理员”身份组账户中的授权
//向“默认管理员”身份组进行新账户授权
//初始化账户变更待生效相关参数
//可能有人会疑问为何没有将newAdmin赋值给_currentDefaultAdmin,其实在_grantRole中已经包含了这个步骤
function _acceptDefaultAdminTransfer() internal virtual {
(address newAdmin, uint48 schedule) = pendingDefaultAdmin();
if (!_isScheduleSet(schedule) || !_hasSchedulePassed(schedule)) {
revert AccessControlEnforcedDefaultAdminDelay(schedule);
}
_revokeRole(DEFAULT_ADMIN_ROLE, defaultAdmin());
_grantRole(DEFAULT_ADMIN_ROLE, newAdmin);
delete _pendingDefaultAdmin;
delete _pendingDefaultAdminSchedule;
}
//重写解除授权内部函数,在操作“默认管理员”身份组账户时,还要进行当前管理员账户的初始化
function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) {
delete _currentDefaultAdmin;
}
return super._revokeRole(role, account);
}
//重写授权内部函数,在操作“默认管理员”身份组账户时,控制只有现在有效管理员账户为初始化状态时,才能进行后续操作,有效管理员账户为初始化状态只有两种情况,一种是新账户调用了_revokeRole解除了旧帐户授权,一种是旧帐户解除自身授权:将账户比那更为address(0),然后调用了renounceRole进行生效,renounceRole中调用了super.renounceRole(role, account),然后super.renounceRole中有调用内部函数_revoke,因为_revoke在当前合约被重写了,所以会采用当前合约的版本,所以进行delete _currentDefaultAdmin;
//将当前有效管理账户赋值为传入的新账户地址
function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
if (role == DEFAULT_ADMIN_ROLE) {
if (defaultAdmin() != address(0)) {
revert AccessControlEnforcedDefaultAdminRules();
}
_currentDefaultAdmin = account;
}
return super._grantRole(role, account);
}
然后看下取消账户变更cancelDefaultAdminTransfer的逻辑:
//撤销已经发起的“默认管理员”身份组账户变更操作,只有当前有效管理员账户才能发起,这个操作在逻辑上只有新账户还没有接受之前才是有效的,否则因为onlyRole(DEFAULT_ADMIN_ROLE)限制,旧账户不再有权限调用该方法
function cancelDefaultAdminTransfer() public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_cancelDefaultAdminTransfer();
}
//通过将待生效账户和待生效的时间点均初始化为0实现撤销变更,即刻生效
function _cancelDefaultAdminTransfer() internal virtual {
_setPendingDefaultAdmin(address(0), 0);
}
//设置待生效的“默认管理员”身份组新账户、新账户生效的时间点
function _setPendingDefaultAdmin(address newAdmin, uint48 newSchedule) private {
//获取存量的待生效时间点参数
(, uint48 oldSchedule) = pendingDefaultAdmin();
_pendingDefaultAdmin = newAdmin;
_pendingDefaultAdminSchedule = newSchedule;
// An `oldSchedule` from `pendingDefaultAdmin()` is only set if it hasn't been accepted.
//如果待生效时间点有设置内容,说明之前发起过账户变更,只是还未生效(包括时间未到和新账户还未接受两种情况),因此抛出账户变更撤销事件
if (_isScheduleSet(oldSchedule)) {
// Emit for implicit cancellations when another default admin was scheduled.
emit DefaultAdminTransferCanceled();
}
}
最后看下解除自身授权renounceRole的逻辑:
//解除账户对某个身份组的授权,必须要由被解除账户自身发起,发起该操作后,默认管理员账户为0,所有默认管理账户账户能发起的方法均无效
//如果操作的是“默认管理员”身份组,那么则需要通过两步来完成,第一步,通过beginDefaultAdminTransfer将管理员地址变更为address(0),第二步,在有效时间点之后调用renounceRole进行确认
function renounceRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
//如果是“默认管理员”身份组,则要求目的账户得是原始的管理员账户
if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) {
//获取待生效的新账户和待生效时间点
(address newDefaultAdmin, uint48 schedule) = pendingDefaultAdmin();
//必须同时满足待生效账户是0、待生效时间点已经设置且待生效时间点已经过去
if (newDefaultAdmin != address(0) || !_isScheduleSet(schedule) || !_hasSchedulePassed(schedule)) {
revert AccessControlEnforcedDefaultAdminDelay(schedule);
}
//在上述条件都满足后,初始化待生效时间点
delete _pendingDefaultAdminSchedule;
}
//调用父类renounceRole,这里要注意:父类的renounceRole实现了两个功能,一个是进行对当前方法调用地址的控制,保证只能由解除授权的账户自身发起renounceRole操作;另一个是调用了_revokeRole,因为当前合约重写了_revokeRole,所以要看当前_revokeRole的实现逻辑:在父类的_revokeRole的基础上,增加了对当前默认管理员账户初始化为0的操作
super.renounceRole(role, account);
}
上面是“默认管理员”身份组账户管理相关操作,下面看下“时间延迟参数”相关操作:
修改“时间延迟参数”changeDefaultAdminDelay的逻辑如下:文章来源:https://www.toymoban.com/news/detail-825105.html
//修改“时间延迟参数”,即状态变量_currentDelay和_pendingDelay
function changeDefaultAdminDelay(uint48 newDelay) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_changeDefaultAdminDelay(newDelay);
}
//内部方法
function _changeDefaultAdminDelay(uint48 newDelay) internal virtual {
//设置“时间延迟参数”的待生效时间点,在当前时间基础上加上等待时间,等待时间通过_delayChangeWait获得
uint48 newSchedule = SafeCast.toUint48(block.timestamp) + _delayChangeWait(newDelay);
_setPendingDelay(newDelay, newSchedule);
emit DefaultAdminDelayChangeScheduled(newDelay, newSchedule);
}
//计算“时间延迟参数”变更时需要延迟的时间
function _delayChangeWait(uint48 newDelay) internal view virtual returns (uint48) {
//获取当前生效的延迟时间
uint48 currentDelay = defaultAdminDelay();
// When increasing the delay, we schedule the delay change to occur after a period of "new delay" has passed, up
// to a maximum given by defaultAdminDelayIncreaseWait, by default 5 days. For example, if increasing from 1 day
// to 3 days, the new delay will come into effect after 3 days. If increasing from 1 day to 10 days, the new
// delay will come into effect after 5 days. The 5 day wait period is intended to be able to fix an error like
// using milliseconds instead of seconds.
//
// When decreasing the delay, we wait the difference between "current delay" and "new delay". This guarantees
// that an admin transfer cannot be made faster than "current delay" at the time the delay change is scheduled.
// For example, if decreasing from 10 days to 3 days, the new delay will come into effect after 7 days.
//如果新设置的“时间延迟参数”大于当前生效中的“时间延迟参数”,那么返回新设置“时间延迟参数”和系统预设的延迟参数增大时的默认最大值两者中的较小值;
//如果新设置的“时间延迟参数”小于等于当前生效中的“时间延迟参数”,则需要等待新参数和旧参数的时间差值再生效“时间延迟参数”,这保证延迟参数的生效不会影响已经在进行中的账户转移等待时间,比如旧的延时时间为10天,发起账户转移后,新账户要等待10天才能发起接受,这是基于旧的“时间延迟参数”达成的共识。举个例子来看为何能够保证如何保证上述逻辑:在发起账户转移后,立即修改延迟参数为1天,因为要等待9天这个新参数才能生效,所以新账户等待时间在9天内还是保持为旧的“时间延迟参数”,第十天虽然已经变更新为参数,但新参数仍然要求等待一天,所以总的等待时间仍不短与10天,如果在账户转移的第二天修改延迟参数为1天,新账户等待时间子账户转移发起的10天内还是保持为旧的“时间延迟参数”,也不影响总延时时间为10天
return
newDelay > currentDelay
? uint48(Math.min(newDelay, defaultAdminDelayIncreaseWait())) // no need to safecast, both inputs are uint48
: currentDelay - newDelay;
}
//设置待生效的“时间延迟参数”和生效时间点,
function _setPendingDelay(uint48 newDelay, uint48 newSchedule) private {
//先判断旧的生效时间是否有配置,如果有配置,后续要判断是否要先生效旧的待生效“时间延迟参数”
uint48 oldSchedule = _pendingDelaySchedule;
if (_isScheduleSet(oldSchedule)) {
if (_hasSchedulePassed(oldSchedule)) {
// Materialize a virtual delay
//如果当前时间晚于生效时间点,则先生效旧的待生效“时间延迟参数”
_currentDelay = _pendingDelay;
} else {
// Emit for implicit cancellations when another delay was scheduled.
emit DefaultAdminDelayChangeCanceled();
}
}
_pendingDelay = newDelay;
_pendingDelaySchedule = newSchedule;
}
取消“时间延迟参数”rollbackDefaultAdminDelay的逻辑为:文章来源地址https://www.toymoban.com/news/detail-825105.html
//只有有效管理员账户才能发起,即刻生效
function rollbackDefaultAdminDelay() public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_rollbackDefaultAdminDelay();
}
//修改待生效时间延迟参数和时间延迟参数生效时间点为0
function _rollbackDefaultAdminDelay() internal virtual {
_setPendingDelay(0, 0);
}
到了这里,关于Openzeppelin库详解-AccessControlDefaultAdminRules的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!