OpenZeppelin Contracts (last updated v5.1.0) (access/manager/AccessManager.sol)
contract AccessManager is Context, Multicall, IAccessManager {
他繼承了 Multicall 代表可以在一筆交易里面同時(shí)調(diào)用它的多個(gè)方法
合約數(shù)據(jù)的主要結(jié)構(gòu)
// Structure that stores the details for a target contract.
struct TargetConfig {
mapping(bytes4 selector => uint64 roleId) allowedRoles;
Time.Delay adminDelay;
bool closed;
}
// Structure that stores the details for a role/account pair. This structures fit into a single slot.
struct Access {
// Timepoint at which the user gets the permission.
// If this is either 0 or in the future, then the role permission is not available.
uint48 since;
// Delay for execution. Only applies to restricted() / execute() calls.
Time.Delay delay;
}
// Structure that stores the details of a role.
struct Role {
// Members of the role.
mapping(address user => Access access) members;
// Admin who can grant or revoke permissions.
uint64 admin;
// Guardian who can cancel operations targeting functions that need this role.
uint64 guardian;
// Delay in which the role takes effect after being granted.
Time.Delay grantDelay;
}
// Structure that stores the details for a scheduled operation. This structure fits into a single slot.
struct Schedule {
// Moment at which the operation can be executed.
uint48 timepoint; // 可以執(zhí)行的最小時(shí)間戳
// Operation nonce to allow third-party contracts to identify the operation.
uint32 nonce;
}
mapping(address target => TargetConfig mode) private _targets; // 合約 -> 方法 -> 需要的權(quán)限
mapping(uint64 roleId => Role) private _roles; // 權(quán)限相關(guān)
mapping(bytes32 operationId => Schedule) private _schedules; //需要延遲執(zhí)行的 先加入當(dāng)任務(wù)
AccessManager 是一個(gè)幫助第三方合約來(lái)管理自己合約方法調(diào)用控制的 權(quán)限管理合約。
AccessManager 作為第三方合約的Authority, 第三方合約在具體執(zhí)行某一個(gè)方法時(shí)先調(diào)用 AccessManager的canCall方法檢查調(diào)用者是否有調(diào)用權(quán)限,以及是否需要延遲等等
第三方合約 可以通過(guò)繼承 AccessManaged 合約來(lái)加入管理
// 需要權(quán)限控制的方法 上面 加上這個(gè) modifier
modifier restricted() {
_checkCanCall(_msgSender(), _msgData());
_;
}
// 設(shè)置 AccessManager 地址
function setAuthority(address newAuthority) public virtual {
address caller = _msgSender();
if (caller != authority()) {
revert AccessManagedUnauthorized(caller);
}
if (newAuthority.code.length == 0) {
revert AccessManagedInvalidAuthority(newAuthority);
}
_setAuthority(newAuthority);
}
// 檢查調(diào)用權(quán)限
function _checkCanCall(address caller, bytes calldata data) internal virtual {
(bool immediate, uint32 delay) = AuthorityUtils.canCallWithDelay(
authority(),
caller,
address(this),
bytes4(data[0:4])
);
// immediate -> true 表示有權(quán)限 沒(méi)有延遲
// immediate -> false delay -> 不為零 表示有權(quán)限 但是需要延遲
// immediate -> false delay -> 為零 表示 沒(méi)有權(quán)限
if (!immediate) {
if (delay > 0) { // 有權(quán)限但是需要延遲 檢查延遲時(shí)間, 這里就需要先再 AccessManager 調(diào)用 schedule,如果延遲時(shí)間沒(méi)有到或者過(guò)期 consumeScheduledOp會(huì)報(bào)錯(cuò)
_consumingSchedule = true;
IAccessManager(authority()).consumeScheduledOp(caller, data);
_consumingSchedule = false;
} else { // 沒(méi)有權(quán)限
revert AccessManagedUnauthorized(caller);
}
}
// 有權(quán)限 不需要延遲 直接放行
}
//AuthorityUtils
function canCallWithDelay(
address authority,
address caller,
address target,
bytes4 selector
) internal view returns (bool immediate, uint32 delay) {
(bool success, bytes memory data) = authority.staticcall(
abi.encodeCall(IAuthority.canCall, (caller, target, selector))
);
if (success) {
if (data.length >= 0x40) {
(immediate, delay) = abi.decode(data, (bool, uint32));
} else if (data.length >= 0x20) {
immediate = abi.decode(data, (bool));
}
}
return (immediate, delay);
}
canCall 是AccessManager 的一個(gè)主要方法
function canCall(
address caller,
address target,
bytes4 selector
) public view virtual returns (bool immediate, uint32 delay) {
if (isTargetClosed(target)) { // closed 表示 不能被調(diào)用了
return (false, 0);
} else if (caller == address(this)) { // 如果調(diào)用者是 AccessManager 自己
// Caller is AccessManager, this means the call was sent through {execute} and it already checked
// permissions. We verify that the call "identifier", which is set during {execute}, is correct.
return (_isExecuting(target, selector), 0);
} else {
uint64 roleId = getTargetFunctionRole(target, selector); //獲取被調(diào)用的合約的方法需要什么role
(bool isMember, uint32 currentDelay) = hasRole(roleId, caller); // 檢查調(diào)用者是否有調(diào)用權(quán)限, 是否需要 延遲, 以及延遲的時(shí)間是多少
return isMember ? (currentDelay == 0, currentDelay) : (false, 0); // 沒(méi)有權(quán)限直接返回 (false, 0),有權(quán)限沒(méi)有延遲返回(true, 0),有權(quán)限但是需要延遲(返回 false, 延遲時(shí)間)
}
}
通過(guò) AccessManager setTargetFunctionRole 方法 設(shè)置 第三方合約的方法的調(diào)用權(quán)限 加入權(quán)限管理
通過(guò) AccessManager schedule方法 把需要延遲執(zhí)行的任務(wù)加入到任務(wù)欄, 等待延遲到來(lái)。
通過(guò) AccessManager execute方法 執(zhí)行延遲時(shí)間已經(jīng)到來(lái)的任務(wù), 或者執(zhí)行不需要延遲的任務(wù)。
通過(guò) AccessManager cancel 方法取消等待延遲時(shí)間到來(lái)的任務(wù)。
還有 AccessManager 也可以管理之前 Ownable 或者 AccessManager的合約 就是把這些合約的owner權(quán)限轉(zhuǎn)移給此合約。
調(diào)用具體合約的onlyOwner或者onlyRole的方法時(shí) 需要通過(guò) 調(diào)用AccessManager的execute(address target, bytes calldata data) 方法, 在execute 方法里面去調(diào)用具體合約的方法
如果需要延遲調(diào)用 應(yīng)該先調(diào)用AccessManager ->schedule方法 把任務(wù)加入到延遲任務(wù)欄,等延遲時(shí)間到的時(shí)候調(diào)用AccessManager -> execute 方法執(zhí)行。
AccessManager 相當(dāng)于之前的 權(quán)限管理合約加上 timeLock。 可以把多個(gè)合約的權(quán)限管理放到一個(gè)合約 。
這里面 延遲時(shí)間的管理用到了 Time library 里面的Delay
Delay本身是一個(gè) uint112 它分為3部分 從左到右 前 6個(gè)字節(jié)表示最新設(shè)置的延遲時(shí)間的生效時(shí)間,接著4個(gè)字節(jié)是原來(lái)設(shè)置的延遲時(shí)間,最后4個(gè)字節(jié)是最新設(shè)置的延遲時(shí)間,當(dāng)通過(guò)get()方法來(lái)獲取延遲時(shí)間的時(shí)候, 先比較當(dāng)前時(shí)間與最新設(shè)置的延遲時(shí)間的生效時(shí)間, 如果當(dāng)前時(shí)間大于這個(gè)生效時(shí)間 就返回最新設(shè)置的延遲時(shí)間,否則就返回原來(lái)設(shè)置的生效時(shí)間。
通過(guò)withUpdate方法來(lái)修改。每次修改先通過(guò)get()方法拿到當(dāng)前的延遲時(shí)間當(dāng)作原來(lái)的延遲時(shí)間 ,要修改的延遲時(shí)間當(dāng)作最新需要設(shè)置的值, 最小的生效延遲+加上當(dāng)前時(shí)間當(dāng)作這一次修改的生效時(shí)間。按照前面說(shuō)的順序?qū)懭搿?/p>