【OpenZeppelin】Ownable.sol及びAccessControl.solを分析してみた【Solidity】

Web3
まるお
まるお
スマートコントラクトの実行者を制限するやつやな。
 
かとてん
かとてん
せやで。
めっちゃ優秀なやつや。
この記事では特によく利用する「Ownable」「Access Contorol」を解説するで。
 
 

Access Controlとは?

OpenZeppelin社が提供するライブラリの一つで、その名の通り「コントラクトへのアクセスを制御」してくれるライブラリです。
このライブラリを利用することで、「誰がコントラクトを実行できるか」を簡単に制御することができます。
 
以下に具体例を挙げます。
・コントラクトをデプロイした人だけがmint関数を使うことができる
・会社のCEO、COO、CFOだけがburn関数を使うことができる
・NFTを持っている人に権限を付与し、権限を持つ人だけがvote関数を使うことができる
 
めちゃくちゃ便利なライブラリなのでぜひ使ってみてください。
ちなみに、こちらの神ウィザードを使用することで簡単にスマートコントラクトを書くことができるので試してみてください。
 
OpenZeppelin社はDApps開発者に向けたセキュリティ製品を提供してくれる会社です。ライブラリの他にも様々なサービスがあります。
 
 

Ownable【2022/04時点】

解説コメント入りのコードを以下に載せますので、参考にしていただけると幸いです。
※詳細はこちらをご覧ください。
 
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../utils/Context.sol";

abstract contract Ownable is Context {
 // コントラクトの所有者アドレスを格納する変数
 address private _owner;

 // 新旧のOwnerアドレスをフロント側へ返すためのイベント
 event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
 // コントラクトをデプロイしたアドレスをコントラクトの所有者アドレス(Ownerアドレス)として設定する
 constructor() {
  _transferOwnership(_msgSender());
 }

 // 現在のコントラクトの所有者アドレス(Ownerアドレス)を返す
 function owner() public view virtual returns (address) {
  return _owner;
 }

 // 関数を呼び出したアドレスがコントラクトの所有者アドレス(Ownerアドレス)であるかをチェックする関数修飾子
 modifier onlyOwner() {
  require(owner() == _msgSender(), "Ownable: caller is not the owner");
  _;
 }

 // Ownerアドレスにゼロアドレスを指定する(コントラクトの所有者が不在となるため注意)
 // 現在のOwnerのみが呼び出すことができる
 function renounceOwnership() public virtual onlyOwner {
  _transferOwnership(address(0));
 }

 // 引数で指定したnewOwnerを新しいOwnerアドレスに指定し、新旧のOwnerアドレスをイベントとしてフロントへ返す_transferOwnershipを呼び出す
 // 現在のOnwerのみ呼び出すことができ、newOwnerアドレスがゼロアドレスでなければ実行される
 function transferOwnership(address newOwner) public virtual onlyOwner {
  require(newOwner != address(0), "Ownable: new owner is the zero address");
  _transferOwnership(newOwner);
 }

 // 引数で指定したnewOwnerを新しいOwnerアドレスに指定し、新旧のOwnerアドレスを返す
 function _transferOwnership(address newOwner) internal virtual {
  address oldOwner = _owner;
  _owner = newOwner;
  emit OwnershipTransferred(oldOwner, newOwner);
 }

 

Access Contorol【2022/04時点】

解説コメント入りのコードを以下に載せますので、参考にしていただけると幸いです。
※詳細はこちらをご覧ください。
 
 
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";

abstract contract AccessControl is Context, IAccessControl, ERC165 {
 struct RoleData {
  mapping(address => bool) members;
  bytes32 adminRole;
 }

 // role名=>RoleDataマッピングを作成
 mapping(bytes32 => RoleData) private _roles;

 // DEFAULT_ADMIN_ROLEの初期値にゼロアドレスを設定
 bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

 // アカウントに特定のロールがあるかどうかをチェックする関数修飾子
 modifier onlyRole(bytes32 role) {
  _checkRole(role);
  _;
 }

 // interfaceIdが同じならtrueを返す
 function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
  return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
 }

 // 引数に指定したroleとaccountが_rolesマッピング内に紐づいていればtrueを返す
 function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
  return _roles[role].members[account];
 }

 // msg.senderに指定したroleが付与されていない場合はcontractを停止して巻き戻し処理を行う
 function _checkRole(bytes32 role) internal view virtual {
  _checkRole(role, _msgSender());
 }

 // 引数のaccountに指定したroleが付与されてていない場合、正規表現(AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$)で出力する
 // revert関数が呼び出された場合、contractの実行を停止してcontractの状態を実行前に戻し、残りのガスをcallerに返却する
 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)
     )
    )
   );
  }
 }

 // roleが付与されているadminRoleを返す
 function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
  return _roles[role].adminRole;
 }

 // 指定したaccountにroleを付与する
 function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
  _grantRole(role, account);
 }

 // 指定したaccountのroleを削除する
 function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
  _revokeRole(role, account);
 }

【続き】

 // 呼び合だし元のアカウントからroleを取り消す ※現在はrevokeRole関数を推奨し、この関数の利用は非推奨となっている
 function renounceRole(bytes32 role, address account) public virtual override {
  require(account == _msg.sender(), "AccessControl: can only renounce roles for self");
  _revokeRole(role, account);
 }

 // 指定したaccountにroleをセットする(_grantRoleを呼び出す) ※現在はgrantRole関数を推奨し、この関数の利用は非推奨となっている
 function _setupRole(bytes32 role, address account) internal virtual {
  _grantRole(role, account);
 }

 // roleを管理するrole「AdminRole」を設定する
 function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
  bytes32 previousAdminRole = getRoleAdmin(role);
  _roles[role].adminRole = adminRole;
  emit RoleAdminChanged(role, previousAdminRole, adminRole);
 }

 // 指定したaccountにroleを付与する関数(roleが付与されていないときに動き、誰がどのアカウントにロールを付与したかEmitする)
 // この関数は設定時にのみコンストラクタから呼び出す必要があり、コントラクトの初期ロールを設定する
 function _grantRole(bytes32 role, address account) internal virtual {
  if (!hasRole(role, account)) {
   _roles[role].members[account] = true;
   emit RoleGranted(role, account, _msgSender());
  }
 }

 // 指定したaccountからroleを削除する関数(roleが付与されているときに動き、誰がどのアカウントのロールを削除したかEmitする)
 function _revokeRole(bytes32 role, address account) internal virtual {
  if (hasRole(role, account)) {
   _roles[role].members[account] = false;
   emit RoleRevoked(role, account, _msgSender());
  }
 }


ERC721とAccessControlを併用する際はsupportsInterface関数が二重定義されてしまうため、呼び出し元で以下のようにオーバーライドする必要があります。
 
 
 

さいごに

スマートコントラクト開発ではセキュリティが最も大切です。
高いセキュリティを実現するためにも、様々な攻撃に耐えた実績のある、検証されたコード(OpenZeppelinのライブラリなど)を利用しましょう。

タイトルとURLをコピーしました