ERC-7820 접근 제어 레지스트리 (Access Control Registry)
Final · 기타 · 2024-11-19 작성
여러 스마트 컨트랙트의 역할(role) 기반 접근 권한을 하나의 레지스트리 컨트랙트에서 등록하고 부여하고 회수하고 조회할 수 있게 하는 표준입니다.
왜 필요한가
탈중앙 앱이 커지면 컨트랙트가 여러 개로 늘어나고 누가 어떤 컨트랙트에서 무엇을 할 수 있는지 관리하는 접근 제어 코드도 컨트랙트마다 제각각 만들어집니다. 이런 개별 구현에서는 같은 기능을 반복해서 작성하게 되고 접근 제어 로직에 실수가 섞이면 보안 결함으로 이어질 수 있습니다. 또 컨트랙트마다 권한을 확인하는 방법이 달라서 다른 시스템과 연동하거나 감사(audit)하기도 어렵습니다. 이 표준은 컨트랙트 등록, 역할 부여와 회수, 역할 확인을 하나의 공통 인터페이스로 통일해 중복을 줄이고 권한 변경이 모두 이벤트로 기록되게 해서 투명성과 보안을 높이는 것을 목표로 합니다.
구현해야 하는 것
event ContractRegistered(address indexed _contract, address indexed _admin);: 컨트랙트가 레지스트리에 등록될 때 컨트랙트 주소와 관리자 주소를 기록합니다.event ContractUnregistered(address indexed _contract, address indexed _admin);: 컨트랙트 등록이 해제될 때 해제된 컨트랙트와 해제를 수행한 주소를 기록합니다.function registerContract(address _admin) external;: 호출한 컨트랙트를 레지스트리에 등록하고 역할을 관리할 관리자 주소를 지정합니다.function unRegisterContract(address _contract) external;: 지정한 컨트랙트의 등록을 해제합니다.
심층 설명
등록은 컨트랙트 자신만, 해제는 관리자만 합니다
registerContract는 등록하려는 컨트랙트가 직접 호출해야 하며 msg.sender가 등록 대상이 됩니다. 중앙 소유자가 따로 등록해 주는 구조가 아니라 각 컨트랙트가 스스로 등록하는 탈중앙 설계입니다. 레지스트리는 0 주소 등록을 반드시(MUST) 거부해야 하고 참조 구현은 이미 활성 상태인 컨트랙트가 다시 등록하면 ContractAlreadyRegistered 오류로 되돌립니다. unRegisterContract는 해당 컨트랙트의 관리자만 호출할 수 있고 활성 상태인 컨트랙트에만 적용되며 해제 시 isActive를 false로 바꾸고 admin을 0 주소로 지웁니다.
역할 부여와 회수는 배열로 일괄 처리합니다
grantRole과 revokeRole은 targetContracts, roles, accounts 세 배열을 받아 같은 인덱스끼리 묶어 처리합니다. 세 배열의 길이가 다르면 Array lengths do not match로 실패합니다. 내부의 _grantRole과 _revokeRole은 호출자가 그 컨트랙트의 관리자이거나 등록된 컨트랙트 자신일 때만 통과하는 onlyAdminOrContract, 대상이 활성 컨트랙트인지 확인하는 onlyActiveContract, 계정이 0 주소가 아닌지 확인하는 validAddress 검사를 거칩니다. _revokeRole은 이미 역할이 없는 계정에 대해 Role already revoked로 실패합니다. 일괄 처리는 큰 시스템에서 가스 비용을 줄이려는 설계입니다.
조회 함수와 이벤트로 투명성을 확보합니다
getContractInfo는 컨트랙트의 활성 여부(isActive)와 관리자 주소를 돌려주며 인터페이스 주석에 따르면 등록된 컨트랙트가 없을 때 반드시(MUST) 되돌려야 합니다. 역할 보유 여부는 getRoleInfo로 조회합니다. 등록(ContractRegistered), 해제(ContractUnregistered), 역할 부여(RoleGranted), 회수(RoleRevoked)가 모두 이벤트로 남아 접근 제어 변경을 공개적으로 추적하고 감사할 수 있습니다.
getRoleInfo 선언이 원문 안에서 서로 다릅니다
인터페이스 코드에는 getRoleInfo(address _contract)가 getContractInfo와 똑같이 (bool isActive, address admin)을 돌려주는 형태로 선언되어 있습니다. 반면 Rationale 절과 참조 구현에서는 getRoleInfo(address targetContract, address account, bytes32 role)가 해당 계정이 그 역할을 가졌는지 bool 하나를 돌려주는 함수로 쓰입니다. 구현할 때는 이 두 서명이 원문 안에서 일치하지 않는다는 점을 확인할 필요가 있습니다.
저장 구조는 중첩 매핑입니다
참조 구현은 등록 정보를 mapping(address => ContractInfo)에, 역할 배정을 mapping(address => mapping(address => mapping(bytes32 => bool)))에 저장합니다. ContractInfo 구조체에는 활성 여부 isActive와 관리자 주소 admin 두 필드가 있습니다. 역할은 bytes32 값 하나로 식별되며 컨트랙트 주소와 계정 주소, 역할 식별자 세 단계로 찾아가는 구조라서 컨트랙트와 역할이 많아져도 조회가 효율적입니다.
쓸 때 주의할 점
- 관리자 전용 제한: 상태를 바꾸는 함수는 컨트랙트 관리자에게만 허용되어 권한 없는 사용자가 중요한 변경을 하지 못하게 막습니다.
- 활성 컨트랙트 확인: 작업이 활성 상태인 컨트랙트로 제한되어 폐기되었거나 등록되지 않은 컨트랙트와 상호작용할 위험을 줄입니다.
- 이벤트 기록: 모든 주요 동작이 이벤트로 기록되어 투명성과 감사 가능성을 확보하고 권한 없는 행위를 감시하고 탐지할 수 있게 합니다.