Overview
v0.8.5 introduces role admins — a delegation layer on top of SYMMIO's existing role-based access control.
Previously, the DEFAULT_ADMIN_ROLE holder was the only address that could grant and revoke roles, making it a
bottleneck for every role change. Now, default admins can delegate management of specific roles to dedicated addresses via
per-role admins. This allows operational teams to manage their domain (e.g., liquidator rotation, pause controls) without
requiring the default admin to sign every transaction.
How It Worked Before
In v0.8.4, the Diamond owner called setAdmin(address) to grant DEFAULT_ADMIN_ROLE to a single
wallet. That wallet could then grant and revoke any role via grantRole / revokeRole. There was no
way to delegate management of a specific role to someone else — every role change had to go through the one
DEFAULT_ADMIN_ROLE holder.
What Changed
A new mapping was added to GlobalAppStorage:
// contracts/core/storages/GlobalAppStorage.sol
/// @notice Who can grant/revoke each role
mapping(bytes32 => mapping(address => bool)) roleAdmins;
This maps each role to a set of addresses that can manage it. Combined with the existing hasRole mapping, the
system now supports a three-tier hierarchy:
Owner
└─ setAdmin(user) ──> DEFAULT_ADMIN_ROLE holders
├─ addRoleAdmin(role, admin) ──> Per-role admins
│ └─ grantRole / revokeRole (for that specific role only)
└─ grantRole / revokeRole (for any role)
- Owner calls
setAdmin()to grantDEFAULT_ADMIN_ROLE. -
Default admins can grant/revoke any role directly, and can appoint or remove per-role admins via
addRoleAdmin/removeRoleAdmin. - Per-role admins can grant/revoke only the role they were assigned to administer. They cannot delegate further.
Example
1. Owner calls setAdmin(multisig)
--> multisig now holds DEFAULT_ADMIN_ROLE
2. multisig calls addRoleAdmin(LIQUIDATOR_ROLE, liquidationOps)
--> liquidationOps can now grant/revoke LIQUIDATOR_ROLE
3. liquidationOps calls grantRole(bot1, LIQUIDATOR_ROLE)
--> bot1 can now perform liquidations
4. liquidationOps calls revokeRole(bot1, LIQUIDATOR_ROLE)
--> bot1 can no longer liquidate
5. multisig calls removeRoleAdmin(LIQUIDATOR_ROLE, liquidationOps)
--> liquidationOps loses management authority
Security Notes
-
Delegation is one level deep. Per-role admins cannot appoint other role admins — only
DEFAULT_ADMIN_ROLEholders can calladdRoleAdmin. -
No role enumeration on-chain. Both
hasRoleandroleAdminsare nested mappings, so listing all holders or all admins must be done off-chain via events. -
Proxy protection.
onlyRoleAdminblocks calls whenGlobalAppStorage.signeris set, preventing proxies from inheriting admin privileges.