ERC-5982 역할 기반 접근 제어 표준 (Role-based Access Control)
Review · 기타 · 2022-11-15 작성
스마트 컨트랙트의 권한을 역할(role) 단위로 조회·부여·생성·삭제하는 공통 인터페이스를 정의합니다. 역할은 bytes32 값으로 표현하고 역할의 힘은 특정 함수를 호출할 수 있는 능력으로 정의합니다.
왜 필요한가
스마트 컨트랙트에는 토큰 발행이나 설정 변경처럼 아무나 호출하면 안 되는 특권 기능이 있습니다. 이를 지키는 가장 단순한 방법은 소유자(owner) 계정 하나에 모든 권한을 몰아주는 소유권 기반 접근 제어입니다. 반면 역할 기반 접근 제어는 여러 사용자에게 하나 이상의 역할을 지정하고 역할에 따라 특권 기능 접근을 허용하는 방식입니다. 최소 권한 원칙(principle of least privilege)에 따라 여러 사람에게 꼭 필요한 권한만 나눠 줄 수 있어 소유권 기반보다 안전하고 유연합니다. 다만 이런 역할 관리를 컨트랙트마다 제각각 구현하면 지갑이나 도구가 권한 구조를 일관된 방식으로 읽기 어렵습니다. 이 표준은 역할을 읽고 부여하고 만들고 없애는 방법과 역할 메타데이터를 표현하는 방법을 하나의 공통 인터페이스로 정의합니다.
구현해야 하는 것
function hasRole(bytes32 role, address account) external view returns (bool);: 계정이 해당 역할을 가졌는지 조회합니다.function grantRole(bytes32 role, address account) external;: 계정에 역할을 부여합니다.function revokeRole(bytes32 role, address account) external;: 계정에서 역할을 회수합니다.event RoleGranted(address indexed grantor, bytes32 indexed role, address indexed grantee, bytes _data);: 누가 누구에게 어떤 역할을 부여했는지 기록합니다.event RoleRevoked(address indexed revoker, bytes32 indexed role, address indexed revokee, bytes _data);: 누가 누구에게서 어떤 역할을 회수했는지 기록합니다.event RoleCreated(address indexed roleGrantor, bytes32 role, bytes32 adminOfRole, string name, string desc, string uri, bytes32 calldata _data);: 새 역할이 만들어졌음을 관리 역할과 이름, 설명, URI와 함께 기록합니다.event RoleDestroyed(address indexed roleDestroyer, bytes32 role, bytes32 calldata _data);: 역할이 삭제되었음을 기록합니다.event RolePowerSet(address indexed rolePowerSetter, bytes32 role, bytes4 methods, bytes calldata _data);: 역할이 호출할 수 있는 메서드 권한이 설정되었음을 기록합니다.function grantRole(bytes32 role, address account, bytes calldata _data) external;: 임의 데이터 _data를 함께 넘기며 계정에 역할을 부여합니다.function revokeRole(bytes32 role, address account, bytes calldata _data) external;: 임의 데이터 _data를 함께 넘기며 계정에서 역할을 회수합니다.function createRole(bytes32 role, bytes32 adminOfRole, string name, string desc, string uri, bytes32 calldata _data) external;: 새 역할을 만들고 그 역할을 관리할 역할과 이름, 설명, URI를 등록합니다.function destroyRole(bytes32 role, bytes32 calldata _data) external;: 역할을 삭제합니다.function setRolePower(bytes32 role, bytes4 methods, bytes calldata _data) view external returns(bool);: 역할이 호출할 수 있는 메서드를 bytes4 셀렉터로 지정합니다.function hasRole(bytes32 role, address account, bytes calldata _data) external view returns (bool);: 임의 데이터 _data를 함께 넘기며 계정이 역할을 가졌는지 조회합니다.function canGrantRole(bytes32 grantor, bytes32 grantee, bytes calldata _data) view external returns(bool);: 어떤 역할(grantor)이 다른 역할(grantee)을 부여할 수 있는지 조회합니다.function canRevokeRole(bytes32 revoker, bytes32 revokee, address account, bytes calldata _data) view external returns(bool);: 어떤 역할이 특정 계정의 역할을 회수할 수 있는지 조회합니다.function canExecute(bytes32 executor, bytes4 methods, bytes32 calldata payload, bytes calldata _data) view external returns(bool);: 어떤 역할이 특정 메서드를 실행할 수 있는지 조회합니다.function roleName(bytes32) external view returns(string);: 역할의 이름을 돌려줍니다.function roleDescription(bytes32) external view returns(string);: 역할의 설명을 돌려줍니다.function roleURI(bytes32) external view returns(string);: 역할과 연결된 URI를 돌려줍니다.
심층 설명
역할의 표현과 생성 규칙
역할은 bytes32 값 하나로 표현합니다. 값은 역할 이름의 keccak256 해시로 계산하는 것이 권장되며(RECOMMENDED) 형식은 bytes32 role = keccak256("<role_name>")입니다. 예를 들어 발행자 역할은 bytes32 role = keccak256("MINTER")로 만듭니다. 사람이 읽는 역할 이름에서 역할 값이 그대로 계산되므로, 이름만 알면 누구나 같은 역할 값을 얻을 수 있습니다.
필수 인터페이스 IERC_ACL_CORE
표준을 따르는 컨트랙트는 IERC_ACL_CORE를 반드시 구현해야 합니다(MUST). 이 인터페이스는 세 함수로 구성됩니다. hasRole(bytes32 role, address account)는 계정이 역할을 가졌는지 조회하고 grantRole(bytes32 role, address account)는 역할을 부여하며 revokeRole(bytes32 role, address account)는 역할을 회수합니다. 함수 이름과 매개변수는 OpenZeppelin 구현과의 하위 호환이 가능하도록 선택되었습니다. 또 자기 역할을 스스로 포기하는 renounceRole은 별도 함수로 채택하지 않고 revokeRole에 통합해 인터페이스를 단순하게 만들었습니다.
권장 확장 IERC_ACL_GENERAL
IERC_ACL_GENERAL은 구현이 권장되는(RECOMMENDED) 선택 확장입니다. 임의 데이터 _data를 함께 넘기는 확장판 grantRole, revokeRole, hasRole이 있고 역할을 만들고 없애는 createRole과 destroyRole, 역할의 메서드 권한을 정하는 setRolePower가 있습니다. createRole은 그 역할을 관리하는 역할(adminOfRole)과 이름(name), 설명(desc), URI를 함께 받습니다. canGrantRole, canRevokeRole, canExecute 같은 조회 함수로 어떤 역할이 역할 부여, 회수, 메서드 실행을 할 수 있는지 미리 확인할 수 있습니다. 상태 변화는 RoleGranted, RoleRevoked, RoleCreated, RoleDestroyed, RolePowerSet 이벤트로 기록합니다. 이 함수들의 _data 매개변수는 ERC-5750을 따르는 것으로, 이후 확장을 가능하게 합니다.
역할의 힘은 메서드 호출 능력
이 표준은 역할이 가진 힘(role power)을 bytes4 메서드 셀렉터로 식별되는 특정 메서드를 호출할 수 있는 능력으로 정의합니다. 역할이 추상적인 직함에 그치지 않고 어떤 함수를 호출할 수 있는가로 구체화되는 것입니다. setRolePower(bytes32 role, bytes4 methods, bytes calldata _data)로 역할에 메서드 권한을 설정하면 RolePowerSet 이벤트가 기록되고 canExecute(bytes32 executor, bytes4 methods, bytes32 calldata payload, bytes calldata _data)로 특정 역할이 특정 메서드를 실행할 수 있는지 미리 조회할 수 있습니다.
역할 메타데이터 IERC_ACL_METADATA
IERC_ACL_METADATA는 구현해도 되는(MAY) 선택 확장입니다. roleName(bytes32)은 역할의 이름을, roleDescription(bytes32)은 역할의 설명을, roleURI(bytes32)는 역할과 연결된 URI를 돌려줍니다. 역할 자체는 bytes32 값이라 사람이 읽기 어렵기 때문에, 이 인터페이스를 구현하면 지갑이나 도구가 역할의 의미를 읽을 수 있는 형태로 보여 줄 수 있습니다. IERC_ACL_GENERAL의 createRole도 역할을 만들 때 name, desc, uri를 함께 받아 RoleCreated 이벤트로 남깁니다.
ERC-165 지원과 제안 상태
준수 컨트랙트는 ERC-165 식별자를 구현하는 것이 좋습니다(SHOULD). 이 제안은 ERC-165와 ERC-5750을 필요 조건(requires)으로 둡니다. 한편 원문의 하위 호환성(Backwards Compatibility) 절과 보안 고려 사항(Security Considerations) 절은 둘 다 논의 필요(Needs discussion)로만 적혀 있어 아직 내용이 채워지지 않았습니다. 이 제안은 현재 Review 상태로, 확정된 표준이 아니며 앞으로 내용이 바뀔 수 있습니다.