ERC-7820 Access Control Registry
Final · Other · created 2024-11-19
A standard that lets a single registry contract register, grant, revoke, and query role-based access permissions for multiple smart contracts.
Why it is needed
As a decentralized app grows, it accumulates multiple contracts, and the access control code that manages who can do what in which contract ends up built differently in each one. Such individual implementations repeat the same functionality, and a mistake in access control logic can lead to a security flaw. Because each contract checks permissions in a different way, it is also hard to integrate with other systems or to audit. This standard unifies contract registration, role granting and revocation, and role checks into one common interface, aiming to reduce duplication and to raise transparency and security by recording every permission change as an event.
What to implement
event ContractRegistered(address indexed _contract, address indexed _admin);: Records the contract address and the admin address when a contract is registered in the registry.event ContractUnregistered(address indexed _contract, address indexed _admin);: Records the unregistered contract and the address that performed the unregistration when a contract is unregistered.function registerContract(address _admin) external;: Registers the calling contract in the registry and specifies the admin address that will manage its roles.function unRegisterContract(address _contract) external;: Unregisters the specified contract.
In depth
Only the contract itself registers, and only the admin unregisters
registerContract must be called directly by the contract being registered, and msg.sender becomes the registration target. This is a decentralized design in which each contract registers itself, not a structure in which a central owner registers contracts separately. The registry MUST reject registration of the zero address, and the reference implementation reverts with the ContractAlreadyRegistered error if an already active contract registers again. unRegisterContract can be called only by the admin of that contract and applies only to active contracts; on unregistration it sets isActive to false and clears admin to the zero address.
Role granting and revoking are processed in bulk with arrays
grantRole and revokeRole take three arrays, targetContracts, roles, and accounts, and process them by pairing entries at the same index. If the three arrays differ in length, the call fails with Array lengths do not match. The internal _grantRole and _revokeRole pass through three checks: onlyAdminOrContract, which passes only if the caller is the admin of that contract or the registered contract itself; onlyActiveContract, which checks that the target is an active contract; and validAddress, which checks that the account is not the zero address. _revokeRole fails with Role already revoked for an account that no longer has the role. Bulk processing is a design meant to reduce gas costs in large systems.
Transparency through query functions and events
getContractInfo returns whether a contract is active (isActive) and its admin address, and according to the interface comments it MUST revert when no contract is registered. Whether a role is held is queried with getRoleInfo. Registration (ContractRegistered), unregistration (ContractUnregistered), role granting (RoleGranted), and revocation (RoleRevoked) are all recorded as events, so access control changes can be tracked and audited publicly.
The getRoleInfo declaration differs within the source
In the interface code, getRoleInfo(address _contract) is declared to return (bool isActive, address admin), the same as getContractInfo. In the Rationale section and the reference implementation, however, getRoleInfo(address targetContract, address account, bytes32 role) is used as a function that returns a single bool indicating whether the account holds that role. When implementing, you need to confirm that these two signatures do not match within the source.
The storage structure is a nested mapping
The reference implementation stores registration information in mapping(address => ContractInfo) and role assignments in mapping(address => mapping(address => mapping(bytes32 => bool))). The ContractInfo struct has two fields, isActive and admin. A role is identified by a single bytes32 value, and because lookup goes through three levels, contract address, account address, and role identifier, queries remain efficient even as contracts and roles multiply.
Things to watch out for
- Admin-only restriction: Functions that change state are allowed only to the contract admin, which prevents unauthorized users from making important changes.
- Active contract check: Operations are restricted to active contracts, which reduces the risk of interacting with contracts that have been deprecated or were never registered.
- Event logging: All major actions are recorded as events, which provides transparency and auditability and makes it possible to monitor and detect unauthorized activity.