--- eip: 8315 title: Contract Role Semantics description: Rules for deriving security semantics from role naming in smart contracts. author: Shepherd Shu (@baishuo13) , Jeff Fei (@77eff) , Kenny Kung (@kennyk10) discussions-to: https://ethereum-magicians.org/t/erc-8315-contract-role-semantics/28757 status: Draft type: Standards Track category: ERC created: 2026-06-10 requires: 5982 --- ## Abstract This standard defines rules for mapping contract role naming to security semantics, enabling authorization sources and role purposes to be derived directly from role names. Building on the `role.{category}.{action}` naming syntax of the Contract Role Naming Standard, this standard specifies two semantic mechanisms — the Governor authorization and revocation pattern and the Category-Action registry — exposed through NatSpec annotations for off-chain consumption and the `IERCRoleSemantics` interface for on-chain queries. ## Motivation Smart contract role naming currently lacks a unified standard — protocols name roles freely, and role identifiers generally do not encode security information such as hierarchy, dependencies, purpose, and authorization relationships. This naming limitation prevents critical security semantics from being read directly from role names: 1. **Opaque authorization relationships**: Authorization relationships between roles (who can grant or revoke whose roles) are configured in a scattered manner within contract code, with no explicit association between role names and authorization relationships. The admin relationship of admin roles cannot be derived from naming to determine authorization sources. 2. **Indistinguishable hierarchy**: In existing RBAC implementations, admin roles (typically using `bytes32(0)` as identifier) and single-operation roles carry no hierarchy information in their naming, requiring per-contract source code review during cross-protocol audits. For example, `role.token.mint` is an atomic permission for executing operations, while `role.token.govern` is a governance permission for managing authorization — the latter can grant the former, making its permissions far greater. The common thread across these two problems is that critical security information is encoded in contract implementations and cannot be directly derived from role identifiers. Building on the Contract Role Naming Standard's `role.{category}.{action}` naming syntax, this standard defines derivation rules so that role names themselves carry hierarchy, authorization, and dependency information, enabling security semantics to be read directly from naming. ## Specification The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. Contracts implementing this standard **MUST** first conform to the naming convention requirements of the Contract Role Naming Standard: `role.{category}.{action}`. ### Action Lexical Classification `role.{category}.{action}` — action identifiers typically carry multiple semantic meanings, with danger levels and authorization implications varying across business scenarios. This standard classifies them into three types, serving as the foundation for subsequent derivation rules: | **Category** | **Semantics** | **Examples** | **Classification Necessity** | | --- | --- | --- | --- | | atomic | Execute a single business operation | `role.token.mint` (mint), `role.bridge.relay` | Can only execute operations, cannot grant roles | | scope | Jurisdiction over multiple operations within a domain | `role.treasury.operator` (manage daily treasury operations) | Broader jurisdiction than a single atomic | | govern | Manage granting and revoking of roles within the same domain | `role.token.govern` (manage token domain role assignments) | Governance authority, can grant atomic and scope roles under the same category | **Constraints for each action type**: + atomic: Newly defined roles **SHOULD** prefer this type. + scope: **MUST** be used with implied action context, currently reserved for future extension. + govern: **MUST** only be used for role governance roles, naturally covering both granting and revocation operations. **SHOULD NOT** be granted to the same address as same-domain atomic roles (risk of overlapping governor and operator identities). Runtime isolation policies are defined by the security baseline framework. ### Governor Authorization and Revocation Pattern `role.{category}.govern` is the authorization management role for each category — holders can grant and revoke atomic roles within that category. Granting and revocation follow a symmetric principle: the roles a governor can grant, it can also revoke. This implements a separation between role assignment authority and role usage authority: the governor determines "**who can use**" and "**who cannot use**", while atomic roles only handle "**what to do**". Authorization sources can be derived without lookup tables. ```plain role.admin.root ── role.token.govern ── role.token.mint │ ├── role.token.burn │ ├── role.token.freeze │ └── role.token.unfreeze ├── role.system.govern ── role.system.pause │ └── role.system.upgrade └── role.bridge.govern ── role.bridge.relay ``` The govern role under the same category can grant all atomic and scope roles within that category. This mapping is simultaneously defined by each row of the Category-Action registry for two things: (1) valid action combinations, and (2) the corresponding governor role. **Two Implementation Paths**: 1. **Explicit Governor Role** (recommended): Define `role.{category}.govern` constants and configure role admin relationships so that governors manage atomic roles. 2. **Implicit Governor** (compatible with existing contracts): Continue using `role.admin.root` as the admin for all roles, but declare the logical governor relationship through NatSpec `@custom:role-governor` annotations. No contract code modification needed — semantic derivation remains valid, and static analysis tools read authorization semantics from annotations. ### NatSpec Semantic Annotations Role naming does not encode hierarchy and authorization sources. Contracts **RECOMMENDED** use standardized NatSpec annotations at role constant declarations for static analysis tool off-chain verification: ```solidity /// @custom:role-type atomic /// @custom:role-governor role.token.govern bytes32 public constant ROLE_TOKEN_MINT = keccak256("role.token.mint"); /// @custom:role-type governor /// @custom:role-grants role.token.mint,role.token.burn,role.token.freeze,role.token.unfreeze /// @custom:role-governor role.admin.root bytes32 public constant ROLE_TOKEN_GOVERN = keccak256("role.token.govern"); /// @custom:role-type root bytes32 public constant ROLE_ADMIN_ROOT = keccak256("role.admin.root"); /// @custom:role-type atomic /// @custom:role-governor role.system.govern bytes32 public constant ROLE_SYSTEM_UPGRADE = keccak256("role.system.upgrade"); ``` **Annotation Field Specification**: | **Field** | **Type** | **Semantics** | | --- | --- | --- | | `@custom:role-type` | root/governor/atomic/scope | Role type, corresponds to Action Lexical Classification (atomic/scope/govern) and the root exception | | `@custom:role-governor` | Role string | Governor, i.e., who granted this role (root roles have no this field) | | `@custom:role-grants` | Comma-separated role strings | Governor type only: list of roles that can be granted and revoked, forming bidirectional verification with role-governor | **Extension Mechanism**: Implementers **MAY** define additional `@custom:role-*` annotation fields. New fields **MUST** follow the `role-{dimension-name}` naming prefix and declare their semantics in project documentation. Future standard versions may incorporate widely-adopted community extension fields into the core set. ## Rationale ### Basis for Governor Pattern Existing RBAC implementations typically support setting an admin role for each role, but there is no explicit association between role naming and admin relationships. This standard makes authorization relationships derivable from naming through the `role.{category}.govern` naming pattern: the govern role under the same category is naturally the admin of that category's atomic roles. ### NatSpec Annotation Design NatSpec annotations use the `@custom:` prefix following Solidity ecosystem convention. Field selection follows the principle of minimal sufficiency: + `role-type`: Encodes role type information, the most important security dimension that naming cannot express. + `role-governor`: Encodes authorization source, addressing the audit need of "who granted this role." + `role-grants`: Only needed for governor types, encoding "the list of roles that can be granted and revoked," forming bidirectional verification with `role-governor`. Annotations are designed as off-chain conventions (RECOMMENDED), while providing structured semantic information for static analysis tools. ### Category-Action Registry The registry defines which category-action combinations belong to known safe patterns. Combinations not in the registry are not necessarily unsafe, but when undefined category-actions appear, they **SHOULD** be flagged for manual review. | **Category** | **Atomic** | **Govern** | **Scope** | **Description** | | --- | --- | --- | --- | --- | | admin | — | — | root (exception) | Highest privilege role, not managed by any governor | | token | mint, burn, freeze, unfreeze | govern | — | Token lifecycle management: mint, burn, freeze, unfreeze | | system | pause, upgrade | govern | — | System operations: global pause, contract upgrade | | treasury | withdraw | govern | — | Treasury: withdraw on-chain funds | | bridge | relay | govern | — | Cross-chain relay: verify and forward cross-chain messages | **Registry Constraints**: + category-action pairs not in the registry should be flagged for manual review. + The registry **MAY** be extended through community governance processes. ### On-Chain Semantic Interface Role combinatorial safety can be queried on-chain through a standardized interface: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; interface IERCRoleSemantics { /// @notice Whether the role belongs to a known safe combination in the registry function isKnownRole(bytes32 roleHash) external view returns (bool); } ``` `isKnownRole()` returns `false` for unregistered hashes, but this does not indicate unsafety — it only indicates the need for manual review. Implementations **SHOULD** declare the `IERCRoleSemantics` interface through [EIP-165](./eip-165.md)'s `supportsInterface()`. ### Action Type Naming: Design Rationale for govern This standard selects `govern` as the authorization management action type for three reasons: (1) `govern` directly encodes role governance semantics — governors grant and revoke roles within their category; (2) `govern` has precise semantics — naturally covering both granting and revocation operations without ambiguity; (3) `govern` aligns with the `role.{category}.govern` naming pattern, enabling the action type to be directly derived from the action identifier. ## Backwards Compatibility ### Contract Role Naming Standard This standard is an extension of the Contract Role Naming Standard and does not modify any of its specification requirements. Contracts implementing only the role naming standard can operate independently without awareness of this standard. ### EIP-5982 The semantic rules defined by this standard are orthogonal to [EIP-5982](./eip-5982.md)'s RBAC interface mechanism. `isKnownRole()` provides role safety combination verification, complementing EIP-5982's `hasRole()` check. However, EIP-5982's current status is Stagnant; adopters should note that its status may affect interoperability guarantees. ### Existing RBAC System Compatibility Contracts implementing this standard that also use other RBAC systems **SHOULD** align role admin relationships with Governor semantics: ```solidity // Example of aligning role management relationships to Governor semantics _setRoleAdmin(ROLE_TOKEN_MINT, ROLE_TOKEN_GOVERN); // role.token.govern manages role.token.mint _setRoleAdmin(ROLE_TOKEN_BURN, ROLE_TOKEN_GOVERN); // role.token.govern manages role.token.burn _setRoleAdmin(ROLE_TOKEN_GOVERN, ROLE_ADMIN_ROOT); // role.admin.root manages role.token.govern ``` ## Security Considerations + **Annotation vs. runtime configuration inconsistency**: NatSpec `@custom:role-type` annotations do not reflect the actual configuration of runtime role admin relationships. If a contract modifies admin relationships causing the actual authorization path to deviate from the Governor pattern (e.g., `role.token.mint`'s admin is set to `role.admin.root` instead of `role.token.govern`), the annotation information remains unchanged. This inconsistency is a security anomaly that static analysis tools **SHOULD** detect. + **NatSpec annotation forgery**: NatSpec annotations are off-chain conventions that cannot be verified after compilation. Static analysis tools **SHOULD** cross-verify annotations against actual contract deployments and role configuration logic. + **Registry coverage incompleteness**: Custom category-action pairs not in the registry cause `isKnownRole()` to return `false`, but the names still conform to the Contract Role Naming Standard's naming syntax. This is by design: the registry is a subset of known safe combinations, not the full set of valid names. + **Govern-Atomic mutual exclusion**: Govern and atomic roles under the same category cannot be granted to the same address, avoiding overlapping governor and operator identities. The mutual exclusion constraint is defined and enforced by the security baseline framework; this standard only declares the semantic mutual exclusion relationship. + **Out-of-registry combination risk**: Role names conforming to the naming syntax do not necessarily belong to the registry. `isKnownRole()` returning `false` does not indicate unsafety — it only indicates the need for manual review. ## Reference Implementation The following implementation is for demonstration purposes only, under the MIT license. Production deployments should undergo a complete security audit. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; interface IERC165 { function supportsInterface(bytes4 interfaceId) external view returns (bool); } interface IERCRoleSemantics { /// @notice Whether the role belongs to a known safe combination in the registry function isKnownRole(bytes32 roleHash) external view returns (bool); } /** * @title RoleSemanticsStandard * @dev Contract Role Semantics Standard reference implementation * Demonstrates semantic verification and the IERCRoleSemantics interface */ contract RoleSemanticsStandard is IERC165, IERCRoleSemantics { // --- Role Constants (inherited from Naming Standard) --- /// @custom:role-type root bytes32 public constant ROLE_ADMIN_ROOT = keccak256("role.admin.root"); /// @custom:role-type governor /// @custom:role-grants role.token.mint,role.token.burn,role.token.freeze,role.token.unfreeze /// @custom:role-governor role.admin.root bytes32 public constant ROLE_TOKEN_GOVERN = keccak256("role.token.govern"); /// @custom:role-type atomic /// @custom:role-governor role.token.govern bytes32 public constant ROLE_TOKEN_MINT = keccak256("role.token.mint"); /// @custom:role-type atomic /// @custom:role-governor role.token.govern bytes32 public constant ROLE_TOKEN_BURN = keccak256("role.token.burn"); /// @custom:role-type atomic /// @custom:role-governor role.token.govern bytes32 public constant ROLE_TOKEN_FREEZE = keccak256("role.token.freeze"); /// @custom:role-type atomic /// @custom:role-governor role.token.govern bytes32 public constant ROLE_TOKEN_UNFREEZE = keccak256("role.token.unfreeze"); /// @custom:role-type governor /// @custom:role-grants role.system.pause,role.system.upgrade /// @custom:role-governor role.admin.root bytes32 public constant ROLE_SYSTEM_GOVERN = keccak256("role.system.govern"); /// @custom:role-type atomic /// @custom:role-governor role.system.govern bytes32 public constant ROLE_SYSTEM_PAUSE = keccak256("role.system.pause"); /// @custom:role-type atomic /// @custom:role-governor role.system.govern bytes32 public constant ROLE_SYSTEM_UPGRADE = keccak256("role.system.upgrade"); /// @custom:role-type governor /// @custom:role-grants role.treasury.withdraw /// @custom:role-governor role.admin.root bytes32 public constant ROLE_TREASURY_GOVERN = keccak256("role.treasury.govern"); /// @custom:role-type atomic /// @custom:role-governor role.treasury.govern bytes32 public constant ROLE_TREASURY_WITHDRAW = keccak256("role.treasury.withdraw"); /// @custom:role-type governor /// @custom:role-grants role.bridge.relay /// @custom:role-governor role.admin.root bytes32 public constant ROLE_BRIDGE_GOVERN = keccak256("role.bridge.govern"); /// @custom:role-type atomic /// @custom:role-governor role.bridge.govern bytes32 public constant ROLE_BRIDGE_RELAY = keccak256("role.bridge.relay"); // --- EIP-165 Interface Declaration --- function supportsInterface(bytes4 interfaceId) external pure returns (bool) { return interfaceId == type(IERC165).interfaceId || interfaceId == type(IERCRoleSemantics).interfaceId; } // --- Semantic Registry --- mapping(bytes32 => bool) private _knownRoleRegistry; constructor() { _registerKnownRole(ROLE_ADMIN_ROOT); _registerKnownRole(ROLE_TOKEN_GOVERN); _registerKnownRole(ROLE_TOKEN_MINT); _registerKnownRole(ROLE_TOKEN_BURN); _registerKnownRole(ROLE_TOKEN_FREEZE); _registerKnownRole(ROLE_TOKEN_UNFREEZE); _registerKnownRole(ROLE_SYSTEM_GOVERN); _registerKnownRole(ROLE_SYSTEM_PAUSE); _registerKnownRole(ROLE_SYSTEM_UPGRADE); _registerKnownRole(ROLE_TREASURY_GOVERN); _registerKnownRole(ROLE_TREASURY_WITHDRAW); _registerKnownRole(ROLE_BRIDGE_GOVERN); _registerKnownRole(ROLE_BRIDGE_RELAY); } function _registerKnownRole(bytes32 role) internal { _knownRoleRegistry[role] = true; } // --- IERCRoleSemantics Interface Implementation --- function isKnownRole(bytes32 roleHash) external view override returns (bool) { return _knownRoleRegistry[roleHash]; } } ``` ## Test Vectors | **Role Name** | **isKnownRole** | **Expected NatSpec Annotation** | **Description** | | --- | --- | --- | --- | | `role.admin.root` | true | type=root | Unique exception | | `role.token.govern` | true | type=governor | action="govern" → governor type | | `role.system.govern` | true | type=governor | action="govern" → governor type | | `role.token.mint` | true | type=atomic | Registered atomic role | | `role.system.pause` | true | type=atomic | Registered atomic role | | `role.treasury.withdraw` | true | type=atomic | Registered atomic role | | `role.bridge.relay` | true | type=atomic | Registered atomic role | | Unknown hash | false | — | Not in registry | ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).