Alert Source Discuss
Standards Track: ERC

ERC-7820: 访问控制注册表

合约的注册、注销、角色分配和角色撤销,确保安全和透明的角色管理。

Authors Shubham Khandelwal (@shubh-ta), Anushka Yadav (@anushka642000)
Created 2024-11-19

摘要

访问控制注册表(ACR)标准定义了一个通用的接口,用于管理多个智能合约中基于角色的访问控制。此标准引入了一个集中式的注册表系统,允许对多个智能合约进行访问控制管理。单个访问控制注册表智能合约管理多个合约中的用户角色,并且可以查询特定于合约的角色信息。此外,ACR标准还提供为特定账户授予和撤销角色的功能,可以单独授予或批量授予,从而确保只有授权用户才能在特定合约中执行特定操作。

该标准的核心包括:

  • 注册和注销: 合约可以向ACR注册,并指定一个管理员来管理合约中的角色。当合约不再有效时,也可以注销合约。

  • 角色管理: 管理员可以为账户授予或撤销角色,可以单独授予或批量授予,从而确保对谁可以在合约中执行哪些操作进行细粒度的控制。

  • 角色验证: 任何账户都可以验证另一个账户是否在已注册的合约中拥有特定角色,从而提供透明度并促进与其他系统的更轻松集成。

通过集中访问控制管理,ACR标准旨在减少冗余,最大限度地减少访问控制逻辑中的错误,并为智能合约中的角色管理提供清晰和标准化的方法。这提高了安全性和可维护性,使开发人员可以更轻松地在其应用程序中实现强大的访问控制机制。

动机

随着去中心化应用程序(dApp)复杂性的增长,跨多个智能合约管理访问控制变得越来越困难。 目前的实践包括定制的实现,导致冗余和潜在的安全缺陷。管理角色和权限的标准化方法将确保更好的互操作性、安全性和透明度。通过提供用于注册合约和管理角色的统一接口,此标准简化了开发,确保了一致性并增强了安全性。它促进了更轻松的集成和审计,从而构建了更强大和可互操作的生态系统。

使用所提供的系统的优点可能是:

通过专门的合约进行结构化的智能合约管理。 协议的临时访问控制供应。 能够指定自定义的访问控制规则来维护协议。

规范

本文档中的关键词“必须”,“禁止”,“需要”,“应该”,“不应该”,“推荐”,“不推荐”,“可以”和“可选”应按照RFC 2119和RFC 8174中的描述进行解释。

AccessControlRegistry合约为管理以太坊智能合约中的访问控制提供了一个标准化的接口。它包括注册和注销合约,授予和撤销特定合约的角色,以及检查账户是否在已注册的合约中具有特定角色的功能。对于合约注册、注销、角色授予和角色撤销,都会发出事件,从而确保访问控制更改的透明性和可追溯性。

此外,AccessControlRegistry 必须拒绝注册零地址。

pragma solidity 0.8.23;
interface IAccessControlRegistry {
    // Emitted when a contract is registered.
    // @param _contract The address of the registered contract.
    // @param _admin The address of the admin for the registered contract.
    event ContractRegistered(address indexed _contract, address indexed _admin);
    // Emitted when a contract is unregistered.
    // @param _contract The address of the unregistered contract.
    // @param _admin The address who unregistered the contract
    event ContractUnregistered(address indexed _contract, address indexed _admin);
    // Emitted when a role is granted to an account for a contract.
    // @param targetContract The address of the contract.
    // @param role The role being granted.
    // @param account The address of the account.
    event RoleGranted(
        address indexed targetContract,
        bytes32 indexed role,
        address indexed account
    );
    // Emitted when a role is revoked from an account for a contract.
    // @param targetContract The address of the contract.
    // @param role The role being revoked.
    // @param account The address of the account.
    event RoleRevoked(
        address indexed targetContract,
        bytes32 indexed role,
        address indexed account
    );
    // Registers a contract with the given admin.
    // @param _admin The address of the admin for the registered contract.
    function registerContract(address _admin) external;
    // Unregisters a contract.
    // @param _contract The address of the contract to unregister.
    function unRegisterContract(address _contract) external;
    // Grants roles to multiple accounts for multiple contracts.
    // @param targetContracts An array of contract addresses to which roles will be granted.
    // @param roles An array of roles to be granted.
    // @param accounts An array of accounts to be granted the roles.
    function grantRole(
        address[] memory targetContracts,
        bytes32[] memory roles,
        address[] memory accounts
    ) external;
    // Revokes roles from multiple accounts for multiple contracts.
    // @param targetContracts An array of contract addresses from which roles will be revoked.
    // @param roles An array of roles to be revoked.
    // @param accounts An array of accounts from which the roles will be revoked.
    function revokeRole(
        address[] memory targetContracts,
        bytes32[] memory roles,
        address[] memory accounts
    ) external;
    
    //Gets the information of a registered contract.
    //@param _contract The address of the contract to get the information.
    //@return isActive Whether the contract is active.
    //@return admin The address of the admin for the contract.
    //MUST revert if the registered contract doesn't exist
    function getContractInfo(
        address _contract
    ) external view returns (bool isActive, address admin);
    // Gets the information of a registered contract.
    // @param _contract The address of the contract to get the information.
    // @return isActive Whether the contract is active.
    // @return admin The address of the admin for the contract.
    // MUST revert if the registered contract doesn't exist`
    function getRoleInfo(
        address _contract
    ) external view returns (bool isActive, address admin);
}

理由

IAccessControlRegistry接口旨在提供一种标准化的方法来管理生态系统中多个合约的访问控制。通过定义清晰的结构和一组事件,此接口有助于简化合约的注册、注销和管理角色的过程。每个函数和事件的基本原理如下:

合约注册和取消注册

registerContract(address _admin): 此函数允许注册一个新合约及其管理员地址。这对于初始化合约的访问控制设置并确保有一个负责任的管理员可以管理角色和权限至关重要。

unRegisterContract(address _contract): 此函数允许从注册表中删除合约。当合约不再使用或需要退役以防止未经授权的访问时,注销合约非常重要。

角色管理

grantRole(address[] memory targetContracts, bytes32[] memory roles, address[] memory accounts): 此函数允许在单个事务中为多个合约的多个账户分配角色。此批量操作旨在降低 gas 成本,并简化具有大量合约和用户的 Role 分配过程。

revokeRole(address[] memory targetContracts, bytes32[] memory roles, address[] memory accounts): 与grantRole类似,此函数有助于在单个事务中撤销多个合约中多个账户的角色。这确保了权限的有效管理,尤其是在许多用户需要同时更新其角色的情况下。

角色检查

getRoleInfo(address targetContract, address account, bytes32 role): 此 view 函数允许验证特定帐户是否具有给定合约的特定角色。这对于确保需要特定权限的操作仅由授权用户执行至关重要。

合约信息检索

getContractInfo(address _contract): 此函数提供检索已注册合约的状态和管理员信息的能力。它增强了透明度,并允许管理员和用户轻松查询注册表中任何合约的状态和管理。

事件

ContractRegistered(address indexed _contract, address indexed _admin): 当注册新合约时发出,此事件确保存在合约注册的公共记录,从而促进可审计性和透明度。

ContractUnregistered(address indexed _contract, address indexed _admin): 当合约注销时发出,此事件用于通知系统及其用户从注册表中删除合约,这对于维护最新和准确的注册表至关重要。

RoleGranted(address indexed targetContract, bytes32 indexed role, address indexed account): 当向账户授予角色时发出,此事件提供了一个公共日志,可用于跟踪角色分配和更改,从而确保角色授予是透明且可验证的。

RoleRevoked(address indexed targetContract, bytes32 indexed role, address indexed account): 当从账户撤销角色时发出,此事件类似地确保角色撤销被公开记录和可追溯,从而支持强大的访问控制管理。

参考实现

register函数必须从注册智能合约调用。 grantRolerevokeRole函数必须从已注册的合约或已注册合约的管理员调用。

pragma solidity 0.8.23;

import "./IAccessControlRegistry.sol";

contract AccessControlRegistry is IAccessControlRegistry {

    // Contains information about a registered contract.
    // @param isActive Indicates whether the contract is active.
    // @param admin The address of the admin for the registered contract.
    struct ContractInfo {
        bool isActive;
        address admin;
    }

    // Mapping to store information of registered contracts
    mapping(address => ContractInfo) public contracts;

    // Mapping to track roles assigned to accounts for specific contracts
    mapping(address => mapping(address => mapping(bytes32 => bool))) public _contractRoles;

    // Custom error to handle duplicate registration attempts
    error ContractAlreadyRegistered();

    // Modifier to check if the caller is an admin or the contract itself
    modifier onlyAdminOrContract(address _contract) {
        require(
            _isAdmin(_contract, msg.sender) || 
            (contracts[msg.sender].isActive && msg.sender == _contract),
            "Caller is not admin nor contract"
        );
        _;
    }

    // Modifier to check if the caller is an admin of the contract
    modifier onlyAdmin(address _contract) {
        require(
            _isAdmin(_contract, msg.sender),
            "Caller is not an admin"
        );
        _;
    }

    // Modifier to ensure the contract is active
    modifier onlyActiveContract(address _contract) {
        require(contracts[_contract].isActive, "Contract not registered");
        _;
    }

    // Modifier to validate if the provided address is non-zero
    modifier validAddress(address addr) {
        require(addr != address(0), "Invalid address");
        _;
    }

    // Registers a contract with the given admin
    // _admin: Address of the admin to register
    function registerContract(address _admin) external validAddress(_admin) {
        address _contract = msg.sender;

        // Check if the contract is already registered
        // 检查合约是否已注册
        ContractInfo storage contractInfo = contracts[_contract];
        if (contractInfo.isActive) {
            revert ContractAlreadyRegistered();
        }

        // Register the contract with the provided admin
        // 使用提供的管理员注册合约
        contractInfo.isActive = true;
        contractInfo.admin = _admin;

        emit ContractRegistered(_contract, _admin);
    }

    // Unregisters a contract
    // _contract: Address of the contract to unregister
    function unRegisterContract(address _contract) 
        public 
        onlyAdmin(_contract) 
        onlyActiveContract(_contract) 
    {
        ContractInfo storage contractInfo = contracts[_contract];
        contractInfo.isActive = false;
        contractInfo.admin = address(0);

        emit ContractUnregistered(_contract, msg.sender);
    }

    // Grants roles to multiple accounts for multiple contracts
    // targetContracts: Array of contract addresses
    // roles: Array of roles to grant
    // accounts: Array of accounts to assign the roles
    function grantRole(
        address[] memory targetContracts,
        bytes32[] memory roles,
        address[] memory accounts
    ) public {
        require(
            targetContracts.length == roles.length &&
            roles.length == accounts.length,
            "Array lengths do not match"
        );

        uint256 cachedArrayLength = roles.length;

        // Grant roles in a batch
        // 批量授予角色
        for (uint256 i; i < cachedArrayLength; ++i) {
            _grantRole(targetContracts[i], roles[i], accounts[i]);
        }
    }

    // Revokes roles from multiple accounts for multiple contracts
    // targetContracts: Array of contract addresses
    // roles: Array of roles to revoke
    // accounts: Array of accounts from which roles are revoked
    function revokeRole(
        address[] memory targetContracts,
        bytes32[] memory roles,
        address[] memory accounts
    ) public {
        require(
            targetContracts.length == roles.length &&
            roles.length == accounts.length,
            "Array lengths do not match"
        );

        uint256 cachedArrayLength = roles.length;

        // Revoke roles in a batch
        // 批量撤销角色
        for (uint256 i; i < cachedArrayLength; ++i) {
            _revokeRole(targetContracts[i], roles[i], accounts[i]);
        }
    }

    // Retrieves information of a registered contract
    // _contract: Address of the contract
    // Returns: isActive status and admin address
    function getContractInfo(address _contract) 
        public 
        view 
        returns (bool isActive, address admin) 
    {
        ContractInfo storage info = contracts[_contract];
        return (info.isActive, info.admin);
    }

    // Gets role information for an account and contract
    // targetContract: Address of the target contract
    // account: Address of the account
    // role: Role identifier
    // Returns: Boolean indicating if the account has the role
    function getRoleInfo(
        address targetContract,
        address account,
        bytes32 role
    ) public view returns (bool) {
        return _contractRoles[targetContract][account][role];
    }

    // Internal function to grant a role to an account for a contract
    // 用于为合约的账户授予角色的内部函数
    function _grantRole(
        address targetContract,
        bytes32 role,
        address account
    )
        internal
        onlyAdminOrContract(targetContract)
        onlyActiveContract(targetContract)
        validAddress(account)
    {
        _contractRoles[targetContract][account][role] = true;
        emit RoleGranted(targetContract, role, account);
    }

    // Internal function to revoke a role from an account for a contract
    // 用于从合约的账户撤销角色的内部函数
    function _revokeRole(
        address targetContract,
        bytes32 role,
        address account
    )
        internal
        onlyAdminOrContract(targetContract)
        onlyActiveContract(targetContract)
        validAddress(account)
    {
        require(
            _contractRoles[targetContract][account][role],
            "Role already revoked"
        );
        _contractRoles[targetContract][account][role] = false;
        emit RoleRevoked(targetContract, role, account);
    }

    // Checks if the caller is an admin for the contract
    // _contract: Address of the contract
    // _admin: Address of the admin
    // Returns: Boolean indicating admin status
    function _isAdmin(address _contract, address _admin) internal view returns (bool) {
        return _admin == contracts[_contract].admin;
    }
}

设计决策

有一些设计决策必须明确指定,以确保IAccessControlRegistry的功能、安全性和效率:

去中心化的合约注册

无中心所有者: 没有可以注册合约的中心所有者。这种设计选择促进了去中心化,并确保各个合约对其自身的注册和管理负责。

高效的存储和查找

映射利用: 使用映射来存储合约信息(mapping(address => ContractInfo) private contracts)和角色分配(mapping(address => mapping(address => mapping(bytes32 => bool))) private _contractRoles)可以确保高效的存储和查找。这对于在具有大量合约和角色的大规模系统中保持性能至关重要。

灵活的角色管理

批量操作: 诸如grantRolerevokeRole之类的函数允许在单个事务中为多个合约的多个账户分配和撤销角色。此批量操作降低了 gas 成本,并简化了大型系统中的角色管理过程。

强大的安全措施

仅限管理员的操作: 诸如 unRegisterContract、_grantRole_revokeRole之类的修改状态的函数仅限于合约管理员。这确保了只有经过授权的人员才能管理合约和角色,从而降低了未经授权的更改的风险。

有效地址检查: validAddress修饰符确保地址为非零,从而防止空地址的潜在问题,这可能导致意外行为或安全漏洞。

活动合约检查: onlyActiveContract修饰符确保仅对活动或已注册的合约执行操作,从而防止对非活动或未注册的合约执行操作。

透明的审计

事件日志记录: 为每个重要操作(注册、注销、角色授予和撤销)发出事件会提供一个透明的日志,可以对其进行监视和审计。这有助于及时检测和响应未经授权或可疑的活动。

安全注意事项

AccessControlRegistry实施了多项安全措施,以确保访问控制系统的完整性和可靠性:

仅限管理员的限制: 通过将状态修改函数限制为合约管理员,系统可以防止未经授权的用户进行关键更改。

活动合约检查: 操作仅限于活动合约,从而降低了与已弃用或未注册合约交互的风险。

事件日志记录: 完善的事件日志记录支持透明性和可审计性,从而可以有效地监视和检测未经授权的操作。

版权

CC0下放弃版权和相关权利。

Citation

Please cite this document as:

Shubham Khandelwal (@shubh-ta), Anushka Yadav (@anushka642000), "ERC-7820: 访问控制注册表," Ethereum Improvement Proposals, no. 7820, November 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7820.