Access Control 이란?
Solidity에서 Access Control이란
권한을 부여받은 account만 function을 실행시킬 수 있도록 Contract에 제어 및 관리 기능을 적용하는 방법론입니다.
ERC20 Mint 기능은 다른 사람들이 함부로 실행시켜선 안 되는 함수임으로, 주로 Owner만 실행시킬 수 있습니다.
하지만 여기에 Access Control를 적용하여, Owenr 외 지정된 account도 실행 시킬 수 있도록 권한을 부여할 수 있습니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract BornToDev is ERC20, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor() ERC20("BornToDev", "BTD") {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
}
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
}
- @openzeppelin/contracts/access/AccessControl.sol
> openzepplin Access Control
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/AccessControl.sol)
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/IAccessControl.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
return _roles[role].members[account];
}
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(uint160(account), 20),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
return _roles[role].adminRole;
}
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
}
※ 지금은 Openzeppelin의 Access Control를 이해하실 필요는 없습니다. 밑에 Clone Coding 실습을 통해 Access Control 방법론에 대해 이해하신 후에 다시 보면 이해가 훨씬 빠를 겁니다.
// ERC20 Minting 권한 명
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
// 권한 부여 기능
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
Openzeppelin에서 제공하는 Access Control을 적용한 ERC20 Solidity 코드입니다.
contract BornToDev에 MINTER_ROLE을 _grantRole 파라미터에 넣어 실행시키면, 해당 account도 minting을 할 수 있는 권한을 얻습니다.
이처럼 블록체인 생태계에선 다양한 Smart Contract에 Access Control 방법론을 적용합니다.
Clone Coding 📝
지금부터 Access Control을 구현하는 방법과 적용하는 방법에 대해 배워보겠습니다.
(1) 전체 코드
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract AccessControl {
event GrantRole(bytes32 indexed role,address indexed account);
event RevokeRole(bytes32 indexed role,address indexed account);
error OnlyRole(bytes32 _role);
// 권한 부여 여부 check
mapping(bytes32 => mapping(address => bool)) public roles;
// role
bytes32 public constant ADMIN = keccak256(abi.encodePacked("ADMIN"));
bytes32 public constant USER = keccak256(abi.encodePacked("USER"));
modifier onlyRole(bytes32 _role) {
if(!roles[_role][msg.sender]) {
revert OnlyRole(_role);
}
_;
}
// contract owner에 ADMIN 권한 부여
constructor() {
_grantRole(ADMIN, msg.sender);
}
function grantRole(bytes32 _role, address _account) external onlyRole(ADMIN) {
_grantRole(_role,_account);
}
function _grantRole(bytes32 _role, address _account) internal {
roles[_role][_account] = true;
emit GrantRole(_role, _account);
}
function revokeRole(bytes32 _role, address _account) external onlyRole(ADMIN) {
_revokeRole(_role,_account);
}
function _revokeRole(bytes32 _role, address _account) internal {
roles[_role][_account] = false;
emit RevokeRole(_role, _account);
}
}
Access Control을 구현하는 가장 기본 기능입니다.
1. 역할 명을 정한다.(ex ADMIN, USER...)
2. account에 정해진 권한이 부여됐는지 확인할 수 있어야 한다.
3. 권한을 부여하는 건 Owner(contract creater)가 최초로 가져야 한다.
4. 권한 부여(grant role) & 권한 취소(revoke role)를 구현한다.
5. 함수에 적용할 modifier 구현한다.
1~5는 AccessControl Contract에 대한 기능 설명입니다.
지금부터 하나씩 코드를 설명해보겠습니다.
(2) 기능 설명
1. 역할 명을 정한다.(ex ADMIN, USER...)
bytes32 public constant ADMIN = keccak256(abi.encodePacked("ADMIN"));
> 0xdf8b4c520ffe197c5343c6f5aec59570151ef9a492f2c624fd45ddde6135ec42
bytes32 public constant USER = keccak256(abi.encodePacked("USER"));
> 0x2db9fd3d099848027c2383d0a083396f6c41510d7acfd92adc99b6cffcf31e96
Contract를 운영할 수 있는 역할은 다양할 수 있습니다. 각 역할을 bytes32 변수로 저장합니다.
String으로 저장하면 데이터가 무겁기에 bytes로 저장합니다.
저장된 역할 변수는 조회만 하고 변경할 일이 없기에, constant로 지정하여 가스비를 절약해줍니다.
Solidity 내에서 ADMIN과 USER는 위 사진처럼 bytes32로 저장됩니다.
2. account에 정해진 권한이 부여됐는지 확인할 수 있어야 한다.
// 권한 부여 여부 check
mapping(bytes32 => mapping(address => bool)) public roles;
// roles["account address"]["Role Byte32"]
roles[0x....][ADMIN]
> false or true
account에 권한이 부여되어 있다면 true,
부여되지 않았다면 false가 return 됩니다.
3. 권한을 부여하는 건 Owner(contract creater)가 최초로 가져야 한다.
// contract owner에 ADMIN 권한 부여
constructor() {
_grantRole(ADMIN, msg.sender);
}
constructor를 이용하여, Contract가 최초로 생성될 때, Owner에게 ADMIN 권한을 부여합니다.
4. 권한 부여(grant role) & 권한 취소(revoke role)를 구현한다.
// 권한 부여
function _grantRole(bytes32 _role, address _account) internal {
roles[_role][_account] = true;
emit GrantRole(_role, _account);
}
// 권한 취소
function _revokeRole(bytes32 _role, address _account) internal {
roles[_role][_account] = false;
emit RevokeRole(_role, _account);
}
_grantRole와 _revokeRole의 파라미터는 bytes32 _role, address _account 두 가지입니다.
1) _role : 부여할 권한 명(bytes32)
2) _account : 권한을 부여받을 account address
grantRole을 실행시키면 해당 account는 ADMIN 권한에 true가 저장됩니다.
revokeRole를 실행시키면 해당 account는 ADMIN 권한에 false가 저장됩니다.
5. 함수에 적용할 modifier 구현한다.
modifier onlyRole(bytes32 _role) {
if(!roles[_role][msg.sender]) {
revert OnlyRole(_role);
}
_;
}
modifier를 만들어서 특정 함수들에 권한 제어를 합니다.
function grantRole(bytes32 _role, address _account) external onlyRole(ADMIN) {
_grantRole(_role,_account);
}
function revokeRole(bytes32 _role, address _account) external onlyRole(ADMIN) {
_revokeRole(_role,_account);
}
권한을 부여 & 취소하는 기능은 ADMIN만 사용할 수 있어야 합니다.
함수 마지막에 modifier onlyRole(ADMIN)을 넣어 ADMIN 권한을 부여받는 사용자만 함수를 실행할 수 있게 할 수 있습니다.
(3) 적용 방법
이제 Access Control를 내가 만드는 Contract에 적용하는 방법을 배워보겠습니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract myContract is AccessControl {
function onlyAdmin() public view onlyRole(ADMIN) returns(bool) {
return true;
}
function onlyUser() public view onlyRole(USER) returns(bool) {
return true;
}
}
onlyAdmin() 함수를 만들었습니다. 이 함수는 msg.sender가 ADMIN 권한을 부여받은 사람만 실행시킬 수 있습니다.
onlyUser() 함수도 마찬가지입니다. msg.sender가 USER 권한을 부여 받은 경우에만 실행 시킬 수 있습니다.
이 처럼 AccessControl Contract를 적용하고 싶은 Contract에 상속하여 modify를 통해 적용하면 됩니다.
'Block Chain > Solidity' 카테고리의 다른 글
[Solidity] Array Memory에서 사용하는 방법 || Solidity 0.8 || (0) | 2022.12.17 |
---|---|
[Solidity] Unchecked || Optimization of gas cost | Solidity 0.8 || (0) | 2022.11.27 |
[Solidity] Event || emit | indexed | ethers | Solidity 0.8 || (0) | 2022.11.21 |
[Solidity] 가스비(gas fee) 줄이는 코딩 방법 || Solidity 0.8 || KR (0) | 2022.10.06 |
[Solidity] ABI 인코딩(encoding) || Solidity 0.8 || KR (0) | 2022.09.26 |
댓글