MultiRolesAuthority.sol 在 256 位位图的角色权限基础上,去掉 target 维度实现与目标合约无关的一维权限映射,并新增自定义 Authority 委托机制,支持统一角色管理与个别合约特殊策略的灵活组合。
版本说明:[solmate]:main分支 [commit: 89365b8],[forge-std]:v1.15.0
源码链接:https://github.com/RevelationOfTuring/solmate/blob/main/src/auth/authorities/MultiRolesAuthority.sol
MultiRolesAuthority 是 solmate 权限体系中的 与目标合约无关的多角色访问控制(RBAC) 实现,是 RolesAuthority 的简化增强版。
解决的核心问题:如何用极低的 gas 成本,管理"谁可以调用任意合约的哪个函数",同时支持为特定目标合约设置完全独立的自定义权限策略。
与 RolesAuthority 的关键区别:
(target, functionSig) 二维键——权限绑定到特定目标合约(functionSig) 一维键——权限与目标合约无关,同一个函数签名对所有合约通用getTargetCustomAuthority——为特定目标合约设置独立的 Authority,优先级最高在 solmate 权限体系中的位置:
用户调用受保护函数
→ requiresAuth 修饰符
→ isAuthorized(msg.sender, msg.sig)
→ authority.canCall(user, target, sig)
→ MultiRolesAuthority.canCall() ← 就是这个合约
├── [优先] getTargetCustomAuthority[target].canCall() ← 自定义 Authority 委托
├── isCapabilityPublic[functionSig] ← 公开能力
└── getUserRoles[user] & getRolesWithCapability[sig] ← 角色位图 AND
| 适合 | 不适合 |
|---|---|
多个合约共享同一套函数级权限(如所有合约的 mint() 统一管理) |
需要为不同合约的同名函数设置不同角色权限 |
| 需要为个别目标合约设置完全独立的权限策略(自定义 Authority) | 需要超过 256 个角色的超大型系统 |
| 角色数量有限、权限关系清晰的场景 | 需要角色层级/继承关系(如 admin > manager > user) |
| 需要极致 gas 优化的链上权限判断 | 需要角色有效期/自动过期机制 |
| 需要公开函数(任何人可调用)的灵活配置 | 需要 (target, functionSig) 二维精细权限(应使用 RolesAuthority) |
| 权限策略需要灵活委托/组合(通过自定义 Authority) | 需要多签/时间锁等复杂治理 |
MultiRolesAuthority is Auth, Authority
│
├── Events(4 个事件)
│ ├── UserRoleUpdated ← 用户角色变更
│ ├── PublicCapabilityUpdated ← 公开能力变更
│ ├── RoleCapabilityUpdated ← 角色能力变更
│ └── TargetCustomAuthorityUpdated ← 目标合约自定义 Authority 变更
│
├── Constructor
│ └── constructor(_owner, _authority) → Auth(_owner, _authority)
│
├── Modifier(继承自 Auth)
│ └── requiresAuth ← 权限校验,保护所有管理函数
│
├── Storage(4 个映射)
│ ├── getTargetCustomAuthority ← address → Authority(目标合约自定义权限)
│ ├── getUserRoles ← address → bytes32(用户角色位图)
│ ├── isCapabilityPublic ← bytes4 → bool(函数是否公开)
│ └── getRolesWithCapability ← bytes4 → bytes32(函数角色位图)
│
├── View Functions(3 个查询)
│ ├── doesUserHaveRole() ← 查询用户是否拥有某角色
│ ├── doesRoleHaveCapability() ← 查询角色是否拥有某函数能力
│ └── canCall() ← Authority 接口:综合权限判断(核心)
│
└── Admin Functions(4 个管理,均需 requiresAuth)
├── setTargetCustomAuthority() ← 设置目标合约的自定义 Authority
├── setPublicCapability() ← 设置函数是否公开
├── setUserRole() ← 给用户分配/撤销角色
└── setRoleCapability() ← 设置角色对函数的能力
contract MultiRolesAuthority is Auth, Authority { ... }
| 父合约/接口 | 提供的能力 |
|---|---|
Auth |
owner 状态变量、authority 状态变量、requiresAuth 修饰符、isAuthorized() 函数、setAuthority()、transferOwnership() |
Authority |
canCall() 接口定义,本合约需要 override 实现 |
设计决策:与 RolesAuthority 完全相同的双重角色设计——既是"被管理的合约"(自身函数受 Auth 保护),又是"权限提供者"(为其他合约提供 canCall 查询)。
UserRoleUpdatedevent UserRoleUpdated(address indexed user, uint8 indexed role, bool enabled);
作用:记录用户角色变更,方便链下系统追踪谁被授予或撤销了哪个角色。
| 参数 | 类型 | indexed | 含义 |
|---|---|---|---|
user |
address |
✅ | 被修改角色的用户地址 |
role |
uint8 |
✅ | 角色编号(0~255) |
enabled |
bool |
❌ | true = 授予角色,false = 撤销角色 |
触发时机:setUserRole() 执行成功后。
PublicCapabilityUpdatedevent PublicCapabilityUpdated(bytes4 indexed functionSig, bool enabled);
作用:记录函数公开状态变更。
| 参数 | 类型 | indexed | 含义 |
|---|---|---|---|
functionSig |
bytes4 |
✅ | 函数选择器 |
enabled |
bool |
❌ | true = 设为公开,false = 取消公开 |
触发时机:setPublicCapability() 执行成功后。
设计决策:与 RolesAuthority 的关键区别——没有 target 参数。因为 MultiRolesAuthority 的公开能力是与目标合约无关的,只按函数签名生效。
RoleCapabilityUpdatedevent RoleCapabilityUpdated(uint8 indexed role, bytes4 indexed functionSig, bool enabled);
作用:记录角色能力变更。
| 参数 | 类型 | indexed | 含义 |
|---|---|---|---|
role |
uint8 |
✅ | 角色编号(0~255) |
functionSig |
bytes4 |
✅ | 函数选择器 |
enabled |
bool |
❌ | true = 授予能力,false = 撤销能力 |
触发时机:setRoleCapability() 执行成功后。
设计决策:同样没有 target 参数(对比 RolesAuthority 的 3 个 indexed 参数)。只用了 2 个 indexed,还有 1 个富余,但因为不需要 target 维度,保持极简。
TargetCustomAuthorityUpdatedevent TargetCustomAuthorityUpdated(address indexed target, Authority indexed authority);
作用:记录目标合约的自定义 Authority 变更,这是 MultiRolesAuthority 独有的事件。
| 参数 | 类型 | indexed | 含义 |
|---|---|---|---|
target |
address |
✅ | 目标合约地址 |
authority |
Authority |
✅ | 新的自定义 Authority 合约地址(address(0) 表示清除) |
触发时机:setTargetCustomAuthority() 执行成功后。
设计决策:两个参数都是 indexed,方便链下按目标合约或按 Authority 合约过滤日志。
constructor(address _owner, Authority _authority) Auth(_owner, _authority) {}
作用:初始化合约,设置初始 owner 和 authority,所有逻辑透传给父合约 Auth。
| 参数 | 类型 | 含义 |
|---|---|---|
_owner |
address |
合约所有者地址,拥有最高管理权限,可调用所有 requiresAuth 函数 |
_authority |
Authority |
外部权限合约地址,可传 Authority(address(0)) 表示不使用外部授权,仅依赖 owner |
设计决策:
AuthAuth 构造函数会设置 owner、authority,并触发 OwnershipTransferred 和 AuthorityUpdated 事件getTargetCustomAuthoritymapping(address => Authority) public getTargetCustomAuthority;
作用:为特定目标合约设置独立的权限判断合约。在 canCall 中优先级最高——如果目标合约有自定义 Authority,直接委托给它判断,完全绕过角色位图和公开能力逻辑。
| Key | Value | 含义 |
|---|---|---|
address(目标合约地址) |
Authority(自定义权限合约) |
非零地址表示已设置自定义 Authority |
示例:
getTargetCustomAuthority[vaultAddress] = specialAuthority
→ 对 vault 的所有调用,权限判断完全委托给 specialAuthority.canCall()
→ 角色位图、公开能力对 vault 均不生效
getTargetCustomAuthority[tokenAddress] = Authority(address(0))
→ token 没有自定义 Authority,走正常的角色/公开能力判断流程
设计决策:
getUserRolesmapping(address => bytes32) public getUserRoles;
作用:存储每个用户拥有的角色集合,用 bytes32 位图表示。
| Key | Value | 含义 |
|---|---|---|
address(用户地址) |
bytes32(256 位位图) |
第 N 位为 1 表示用户拥有角色 N |
示例:值为 0x...05(二进制 ...0101)
bit: ... bit3 bit2 bit1 bit0 ← 从右往左读
值: ... 0 1 0 1
→ 拥有角色 0(bit0=1)和角色 2(bit2=1)
设计决策:与 RolesAuthority 完全相同。1 个 storage slot 存 256 个角色。
isCapabilityPublicmapping(bytes4 => bool) public isCapabilityPublic;
作用:标记某个函数签名是否对所有人公开(无需角色即可调用)。
| Key | Value | 含义 |
|---|---|---|
bytes4(函数选择器) |
bool |
true = 该函数对所有人公开 |
示例:
isCapabilityPublic[bytes4(keccak256("balanceOf(address)"))] = true
→ 所有合约的 balanceOf 函数对所有人开放(target 无关)
设计决策:
mapping(address => mapping(bytes4 => bool)),即 (target, sig) 二维键;MultiRolesAuthority 是 mapping(bytes4 => bool),仅按函数签名——一旦公开,对所有目标合约生效getRolesWithCapabilitymapping(bytes4 => bytes32) public getRolesWithCapability;
作用:存储哪些角色可以调用某个函数,用 bytes32 位图表示。
| Key | Value | 含义 |
|---|---|---|
bytes4(函数选择器) |
bytes32(256 位位图) |
第 N 位为 1 表示角色 N 可调用该函数 |
示例:
getRolesWithCapability[bytes4(keccak256("mint(address,uint256)"))] = 0x...0000_0110
→ role 1(bit1=1)和 role 2(bit2=1)拥有 mint 能力
→ 适用于所有目标合约的 mint 函数(target 无关)
设计决策:
mapping(address => mapping(bytes4 => bytes32)),即 (target, sig) 二维键;MultiRolesAuthority 只按函数签名mint 能力,那么 role 1 可以对所有挂载了此 Authority 的合约调用 mintdoesUserHaveRolefunction doesUserHaveRole(address user, uint8 role) public view virtual returns (bool) {
return (uint256(getUserRoles[user]) >> role) & 1 != 0;
}
作用:查询某个用户是否拥有指定角色。通过位移取位操作,从用户角色位图中提取指定 bit 的值。
| 参数 | 类型 | 含义 |
|---|---|---|
user |
address |
要查询的用户地址 |
role |
uint8 |
要查询的角色编号(0~255) |
| 返回值 | bool |
true = 用户拥有该角色 |
位运算拆解(bit 位从右往左数,bit0 在最右边):
原始位图: bit3 bit2 bit1 bit0
0 1 0 1 (用户有角色 0 和 2)
查询 role=2:
原始: 0 1 0 1
>> 2: 0 0 0 1 ← bit2 移到 bit0 位置
& 1: 0 0 0 1 → != 0 → true ✅
查询 role=1:
原始: 0 1 0 1
>> 1: 0 0 1 0 ← bit1 移到 bit0 位置
& 1: 0 0 0 0 → == 0 → false ❌
设计决策:先转 uint256 再位移,因为 Solidity 的 >> 运算符需要数值类型,bytes32 不支持直接位移。
doesRoleHaveCapabilityfunction doesRoleHaveCapability(uint8 role, bytes4 functionSig) public view virtual returns (bool) {
return (uint256(getRolesWithCapability[functionSig]) >> role) & 1 != 0;
}
作用:查询某个角色是否有权调用指定函数。逻辑与 doesUserHaveRole 完全一致,只是数据源从用户角色位图变成了函数角色位图。
| 参数 | 类型 | 含义 |
|---|---|---|
role |
uint8 |
要查询的角色编号(0~255) |
functionSig |
bytes4 |
目标函数选择器 |
| 返回值 | bool |
true = 该角色有权调用该函数 |
设计决策:
target 参数,因为能力映射是 target agnostic 的(role, functionSig) 而非 (role, target, functionSig)canCall(核心函数)function canCall(
address user,
address target,
bytes4 functionSig
) public view virtual override returns (bool) {
// 路径 1:检查目标合约是否有自定义 Authority
Authority customAuthority = getTargetCustomAuthority[target];
// 若自定义 Authority 存在(非零地址),完全委托给它判断
if (address(customAuthority) != address(0)) return customAuthority.canCall(user, target, functionSig);
// 路径 2 || 路径 3:
// isCapabilityPublic[functionSig] — 该函数是否公开
// getUserRoles[user] & getRolesWithCapability[functionSig] — 用户角色与函数所需角色的位与
// bytes32(0) != ... — 位与结果非零说明至少有一个角色匹配
return
isCapabilityPublic[functionSig] || bytes32(0) != getUserRoles[user] & getRolesWithCapability[functionSig];
}
作用:Authority 接口的核心实现。判断某个用户是否有权调用某个合约的某个函数。三级判断逻辑,优先级递减。
| 参数 | 类型 | 含义 |
|---|---|---|
user |
address |
调用者地址(通常是 msg.sender) |
target |
address |
被调用的目标合约地址(通常是 address(this)) |
functionSig |
bytes4 |
被调用函数的选择器(通常是 msg.sig) |
| 返回值 | bool |
true = 允许调用,false = 拒绝调用 |
三级判断逻辑:
路径 1(自定义 Authority,优先级最高):
getTargetCustomAuthority[target] != address(0)
→ 完全委托给自定义 Authority 判断,直接 return
→ 后续的角色/公开能力逻辑对该 target 完全不生效
路径 2(公开能力,短路优先):
isCapabilityPublic[functionSig] == true
→ 该函数是公开的,任何人可调用,直接返回 true
路径 3(角色位图 AND):
getUserRoles[user] & getRolesWithCapability[functionSig] != bytes32(0)
→ 用户角色位图 AND 函数角色位图
→ 只要结果不全为 0,说明至少有一个角色匹配 → 放行
完整示例:
用户位图(Alice 有角色 0、2):
bit: bit3 bit2 bit1 bit0
0 1 0 1
函数位图(角色 1、2 可调用 withdraw):
bit: bit3 bit2 bit1 bit0
0 1 1 0
AND 运算:
0 1 0 1 ← Alice
& 0 1 1 0 ← withdraw 允许的角色
─────────────────
0 1 0 0 ← 角色 2 重叠!!= 0 → 有权限 ✅
设计决策:
getTargetCustomAuthority,命中后直接 return,不走位图逻辑。用 if 分支而非 || 短路,因为需要完全委托(不是"或"关系,而是"取代"关系)Authority customAuthority = getTargetCustomAuthority[target] 缓存 SLOAD 结果,避免在 address(customAuthority) 和 customAuthority.canCall() 中重复读取(节省 ~100 gas warm SLOAD)|| 短路:公开能力命中后不再做位图运算,节省 gas& 运算优先级高于 !=:bytes32(0) != A & B 无需额外括号& 运算同时检查 256 个角色(target, sig) 二维;MultiRolesAuthority 多了路径 1,且位图查询是 (sig) 一维setTargetCustomAuthorityfunction setTargetCustomAuthority(address target, Authority customAuthority) public virtual requiresAuth {
// 存储目标合约的自定义 Authority
getTargetCustomAuthority[target] = customAuthority;
emit TargetCustomAuthorityUpdated(target, customAuthority);
}
作用:为指定目标合约设置独立的自定义 Authority。设置后,canCall 中对该 target 的查询将完全绕过角色/公开能力逻辑,由自定义 Authority 全权决定。
| 参数 | 类型 | 含义 |
|---|---|---|
target |
address |
目标合约地址 |
customAuthority |
Authority |
自定义 Authority 合约地址,传 Authority(address(0)) 可清除,回退到角色/公开能力判断 |
设计决策:
address(0) 清除自定义 Authority 而非删除 mapping entry,gas 更优setPublicCapabilityfunction setPublicCapability(bytes4 functionSig, bool enabled) public virtual requiresAuth {
// 更新公开能力状态
isCapabilityPublic[functionSig] = enabled;
emit PublicCapabilityUpdated(functionSig, enabled);
}
作用:设置某个函数签名是否为公开可调用。设为公开后,canCall 会短路返回 true,任何人无需角色即可调用。
| 参数 | 类型 | 含义 |
|---|---|---|
functionSig |
bytes4 |
函数选择器(如 Vault.deposit.selector) |
enabled |
bool |
true = 任何人可调用(公开),false = 需要角色才能调用 |
设计决策:
target 参数,公开设置对所有挂载此 Authority 的合约生效setPublicCapability(mint.selector, true),所有合约的 mint 都公开了setUserRolefunction setUserRole(
address user,
uint8 role,
bool enabled
) public virtual requiresAuth {
if (enabled) {
// 授予角色:用按位或将对应 bit 置 1
getUserRoles[user] |= bytes32(1 << role);
} else {
// 撤销角色:用按位与 + 取反将对应 bit 置 0
getUserRoles[user] &= ~bytes32(1 << role);
}
emit UserRoleUpdated(user, role, enabled);
}
作用:给用户分配或撤销角色。通过位运算在用户角色位图中置位或清位。
| 参数 | 类型 | 含义 |
|---|---|---|
user |
address |
要配置的用户地址 |
role |
uint8 |
角色编号(0~255) |
enabled |
bool |
true = 分配角色,false = 撤销角色 |
位运算拆解:
授予角色 3(enabled = true):
1 << 3 = 1000 ← 生成掩码,只有 bit3 为 1
原始位图 = 0101
|= 1000 = 1101 ← OR:bit3 被置 1,其他不变
撤销角色 3(enabled = false):
1 << 3 = 1000
~1000 = 0111 ← 取反:只有 bit3 为 0
原始位图 = 1101
&= 0111 = 0101 ← AND:bit3 被清 0,其他不变
setRoleCapabilityfunction setRoleCapability(
uint8 role,
bytes4 functionSig,
bool enabled
) public virtual requiresAuth {
if (enabled) {
// 授予能力:用按位或将对应 bit 置 1
getRolesWithCapability[functionSig] |= bytes32(1 << role);
} else {
// 撤销能力:用按位与 + 取反将对应 bit 置 0
getRolesWithCapability[functionSig] &= ~bytes32(1 << role);
}
emit RoleCapabilityUpdated(role, functionSig, enabled);
}
作用:为指定角色授予或撤销指定函数的调用能力。通过位运算在函数角色位图中置位或清位。
| 参数 | 类型 | 含义 |
|---|---|---|
role |
uint8 |
角色编号(0~255) |
functionSig |
bytes4 |
函数选择器 |
enabled |
bool |
true = 授予能力,false = 撤销能力 |
位运算拆解:
授予 role 2 对 mint 的能力(enabled = true):
1 << 2 = 0100 ← 生成掩码,只有 bit2 为 1
原始位图 = 1001
|= 0100 = 1101 ← OR:bit2 被置 1,其他不变
撤销 role 2 对 mint 的能力(enabled = false):
1 << 2 = 0100
~0100 = 1011 ← 取反:只有 bit2 为 0
原始位图 = 1101
&= 1011 = 1001 ← AND:bit2 被清 0,其他不变
设计决策:
target 参数,能力设置对所有合约的同名函数生效setUserRole 完全一致,保持代码一致性外部合约调用受保护函数
│
▼
requiresAuth 修饰符(Auth 合约)
作用:校验 msg.sender 是否有权调用当前函数
│
▼
isAuthorized(msg.sender, msg.sig)
作用:核心授权判断,先查 authority 再查 owner
│
▼
authority 是否为零地址?
│
┌────┴────┐
YES NO
│ │
│ ▼
│ authority.canCall(user, target, sig)
│ 作用:调用 MultiRolesAuthority 进行权限判断
│ │
│ ▼
│ ┌──────────────────────────────────┐
│ │ 路径1: getTargetCustomAuthority │
│ │ [target] != address(0) ? │
│ └──────┬──────────────┬────────────┘
│ YES NO
│ │ │
│ ▼ ▼
│ customAuthority ┌──────────────────────┐
│ .canCall(...) │ 路径2: isCapability │
│ 完全委托判断 │ Public[functionSig]? │
│ │ └─────┬──────────┬─────┘
│ │ YES NO
│ │ │ │
│ │ ▼ ▼
│ │ return 路径3: getUserRoles[user]
│ │ true & getRolesWithCapability[sig]
│ │ │
│ │ ▼
│ │ ┌──────────┐
│ │ │ != 0x0 ? │
│ │ └──┬────┬──┘
│ │ YES NO
│ │ │ │
│ ▼ ▼ ▼
│ return result return return
│ true false
│ │
└────────────────┬───────────────────────┘
▼
canCall 返回 false(或 authority 为零地址)?
检查 user == owner?
│
┌──┴──┐
YES NO
│ │
▼ ▼
通过 revert("UNAUTHORIZED")
管理员(owner 或被 authority 授权者)
│
▼
requiresAuth 校验通过
│
┌────┴───────────────┬───────────────────┬────────────────────┐
▼ ▼ ▼ ▼
setTarget setPublic setUserRole() setRole
CustomAuthority() Capability() 作用:分配/ Capability()
作用:设置目标 作用:设置 撤销用户角色 作用:设置角色
合约自定义Authority 函数是否公开 对函数的能力
│ │ │ │
▼ ▼ ▼ ▼
直接赋值 直接设置 位运算修改 位运算修改
Authority地址 bool值 用户角色位图 函数角色位图
(getTarget (isCapability (getUserRoles) (getRolesWith
CustomAuthority) Public) Capability)
│ │ │ │
▼ ▼ ▼ ▼
emit emit emit emit
TargetCustom PublicCapability UserRole RoleCapability
AuthorityUpdated Updated Updated Updated
1. deploy MultiRolesAuthority(deployer, Authority(address(0)))
→ 创建权限管理合约,deployer 为 owner,不使用外部 Authority
│
2. deploy Vault(deployer, multiRolesAuthority)
deploy Token(deployer, multiRolesAuthority)
→ 创建业务合约,挂载统一的权限合约
│
3. authority.setRoleCapability(MINTER_ROLE, mint.selector, true)
authority.setRoleCapability(ADMIN_ROLE, withdraw.selector, true)
→ 配置:哪些角色可以调用哪些函数(适用于所有挂载的合约)
│
4. authority.setUserRole(alice, MINTER_ROLE, true)
authority.setUserRole(bob, ADMIN_ROLE, true)
→ 给用户分配角色
│
5.(可选)authority.setPublicCapability(deposit.selector, true)
→ 将 deposit 设为公开,所有合约的 deposit 均免权限
│
6.(可选)authority.setTargetCustomAuthority(specialVault, timelockAuthority)
→ 对特殊 Vault 使用时间锁 Authority,绕过角色体系
│
7. alice 调用 token.mint(bob, 100)
│
requiresAuth → isAuthorized → authority.canCall()
│
getTargetCustomAuthority[token] == address(0) → 不走自定义路径
isCapabilityPublic[mint.selector] == false → 不是公开函数
getUserRoles[alice] & getRolesWithCapability[mint.selector]
│
结果 != 0 → 通过 ✅
| 操作 | Gas 成本 | 原因 |
|---|---|---|
| canCall 判断(无自定义 Authority) | ~2 SLOAD + 1 AND | 位图一次性检查 256 个角色,O(1) |
| canCall 判断(有自定义 Authority) | ~1 SLOAD + 外部调用 | 自定义 Authority 优先,命中后直接返回 |
| 公开函数判断 | ~1 SLOAD | || 短路返回,跳过位图运算 |
| 角色分配/撤销 | ~1 SLOAD + 1 SSTORE | 单 slot 位运算 |
与 RolesAuthority 的 Gas 对比:
// RolesAuthority — 二维映射查询(2 个嵌套 mapping)
isCapabilityPublic[target][functionSig] // 2 次 keccak256 计算 slot
getRolesWithCapability[target][functionSig] // 2 次 keccak256 计算 slot
// MultiRolesAuthority — 一维映射查询(1 个 mapping)
isCapabilityPublic[functionSig] // 1 次 keccak256 计算 slot
getRolesWithCapability[functionSig] // 1 次 keccak256 计算 slot
// MultiRolesAuthority 在非自定义 Authority 路径上比 RolesAuthority 更省 gas
// 因为少了 target 维度的嵌套 mapping,减少了 keccak256 计算
Auth 的合约requiresAuth 保护uint8 role 范围 0~255,与 bytes32 的 256 位完美匹配,不存在越界&= ~mask,只影响目标位,不破坏其他角色设置address(0) 的外部调用| 风险 | 说明 | 建议 |
|---|---|---|
| Target agnostic 的双刃剑 | 同一函数签名的权限对所有合约生效,如果两个合约有同名函数但需要不同权限,无法直接区分 | 对需要独立权限的合约,使用 setTargetCustomAuthority 设置自定义 Authority |
| 自定义 Authority 完全接管 | 一旦设置了自定义 Authority,角色/公开能力对该 target 完全不生效 | 确保自定义 Authority 实现正确;清除时传入 address(0) |
| 自定义 Authority 外部调用风险 | canCall 中会外部调用自定义 Authority,若其恶意或有 bug 可能导致 revert 或消耗大量 gas |
仅信任经过审计的 Authority 合约;注意重入风险(虽然 canCall 是 view 函数) |
| owner 单点风险 | owner 可随意分配/撤销所有角色和权限 | 部署完成后将 owner 转移给多签钱包或 Timelock |
| 无零地址校验 | setUserRole 可给 address(0) 分配角色 |
业务层自行校验,或继承后 override 加校验 |
| 无过期机制 | 角色一旦分配永久有效 | 如需过期,需自行扩展 |
| 公开函数风险 | setPublicCapability 对所有合约生效,影响范围更大 |
比 RolesAuthority 更需谨慎,因为没有 target 限定 |
| Authority 死锁 | 若自身的 authority 合约异常,即使 owner 也可能无法调管理函数 | 参考 Auth.setAuthority 的特殊处理;或将 authority 设为 address(0) |
| 函数签名冲突 | 不同合约的不同函数可能有相同的 4 字节选择器(概率极低但存在) | 了解此风险,关键场景可用 setTargetCustomAuthority 隔离 |
| 特性 | solmate MultiRolesAuthority | solmate RolesAuthority | OpenZeppelin AccessControl |
|---|---|---|---|
| 权限维度 | (functionSig) 一维 + 可选自定义 Authority |
(target, functionSig) 二维 |
角色级别,无函数级别 |
| 角色存储 | bytes32 位图(1 slot / 用户) | bytes32 位图(1 slot / 用户) | mapping(role => mapping(user => bool)) |
| 角色数量 | 最多 256 | 最多 256 | 无限制(bytes32 哈希作 role ID) |
| 角色标识 | 纯数字 0~255 | 纯数字 0~255 | 可读哈希 keccak256("ROLE") |
| 权限判断 gas | ~1-3 SLOAD + 1 AND | ~2 SLOAD + 1 AND | ~1 SLOAD |
| 函数级权限 | ✅ 原生支持(target agnostic) | ✅ 原生支持(target specific) | ❌ 需自行 hasRole 检查 |
| 自定义 Authority 委托 | ✅ getTargetCustomAuthority |
❌ | ❌ |
| 公开函数 | ✅ 全局生效 | ✅ 按 target 生效 | ❌ |
| 角色层级 | ❌ | ❌ | ✅(adminRole 机制) |
| 适合场景 | 多合约共享权限 + 个别合约特殊处理 | 每个合约需独立权限配置 | 角色多、需层级、生态兼容 |
| 代码量 | ~80 行 | ~80 行 | ~150 行 |
如何选择:
需要为不同合约的同名函数设置不同权限?
→ 选 RolesAuthority(二维映射,精确到 target)
多个合约共享一套角色权限,个别合约需要特殊处理?
→ 选 MultiRolesAuthority(一维映射 + 自定义 Authority 委托)
需要角色层级、可读角色名、生态兼容性?
→ 选 OpenZeppelin AccessControl
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {Auth, Authority} from "solmate/auth/Auth.sol";
/*
* @title TokenVault — 使用 MultiRolesAuthority 做权限控制的代币金库合约
* @notice 展示 MultiRolesAuthority 的典型用法:多合约共享角色体系
*/
contract TokenVault is Auth {
// 角色常量
uint8 public constant ROLE_DEPOSITOR = 0;
uint8 public constant ROLE_WITHDRAWER = 1;
uint8 public constant ROLE_ADMIN = 2;
mapping(address => uint256) public balances;
constructor(address _owner, Authority _authority) Auth(_owner, _authority) {}
/// @notice 存款,需要 DEPOSITOR 角色或 owner
function deposit() external payable requiresAuth {
balances[msg.sender] += msg.value;
}
/// @notice 取款,需要 WITHDRAWER 角色或 owner
function withdraw(uint256 amount) external requiresAuth {
require(balances[msg.sender] >= amount, "INSUFFICIENT");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
/// @notice 紧急提取全部资金,需要 ADMIN 角色或 owner
function emergencyWithdraw() external requiresAuth {
payable(msg.sender).transfer(address(this).balance);
}
}
import {MultiRolesAuthority} from "solmate/auth/authorities/MultiRolesAuthority.sol";
// 1. 部署权限合约
MultiRolesAuthority authority = new MultiRolesAuthority(deployer, Authority(address(0)));
// 2. 部署多个业务合约,共享同一个权限合约
TokenVault vault1 = new TokenVault(deployer, authority);
TokenVault vault2 = new TokenVault(deployer, authority);
// 3. 配置角色能力(对所有合约的同名函数生效)
authority.setRoleCapability(0, TokenVault.deposit.selector, true);
authority.setRoleCapability(1, TokenVault.withdraw.selector, true);
authority.setRoleCapability(2, TokenVault.emergencyWithdraw.selector, true);
// 4. 分配角色
authority.setUserRole(alice, 0, true); // Alice 可对所有 vault 存款
authority.setUserRole(alice, 1, true); // Alice 也可对所有 vault 取款
authority.setUserRole(bob, 0, true); // Bob 只能对所有 vault 存款
// 5.(可选)将 deposit 设为公开
authority.setPublicCapability(TokenVault.deposit.selector, true);
// → vault1 和 vault2 的 deposit 都公开了
// 6.(可选)vault2 有特殊需求,设置自定义 Authority
authority.setTargetCustomAuthority(address(vault2), timelockAuthority);
// → vault2 的权限完全由 timelockAuthority 管理,角色体系对 vault2 不生效
// 7. 生产环境:将 owner 转给多签
authority.transferOwnership(multisigAddress);
Alice 调用 vault1.withdraw(100):
→ requiresAuth
→ authority.canCall(alice, vault1, withdraw.selector)
→ getTargetCustomAuthority[vault1] == address(0) → 不走自定义路径
→ isCapabilityPublic[withdraw.selector] == false → 不是公开函数
→ getUserRoles[alice] = ...011(角色 0、1)
→ getRolesWithCapability[withdraw.selector] = ...010(角色 1)
→ AND = ...010 != 0 → 通过 ✅
Alice 调用 vault2.withdraw(100)(vault2 有自定义 Authority):
→ requiresAuth
→ authority.canCall(alice, vault2, withdraw.selector)
→ getTargetCustomAuthority[vault2] == timelockAuthority → 走自定义路径
→ timelockAuthority.canCall(alice, vault2, withdraw.selector)
→ 结果由 timelockAuthority 决定(角色体系不参与)
Bob 调用 vault1.withdraw(100):
→ requiresAuth
→ authority.canCall(bob, vault1, withdraw.selector)
→ getTargetCustomAuthority[vault1] == address(0) → 不走自定义路径
→ isCapabilityPublic[withdraw.selector] == false
→ getUserRoles[bob] = ...001(角色 0)
→ getRolesWithCapability[withdraw.selector] = ...010(角色 1)
→ AND = ...000 == 0 → 拒绝
→ bob != owner → revert("UNAUTHORIZED") ❌
目标合约:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {Auth, Authority} from "solmate/auth/Auth.sol";
/// @dev 被保护的目标合约,用于端到端验证
contract Target is Auth {
constructor(address _owner, Authority _authority) Auth(_owner, _authority) {}
function protectedFunc() external view requiresAuth returns (bool) {
return true;
}
function anotherFunc() external view requiresAuth returns (bool) {
return true;
}
}
全部foundry测试合约:https://github.com/RevelationOfTuring/foundry-solmate/blob/main/test/auth/authorities/MultiRolesAuthority.t.sol
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!
