# [ERC7820] 複数コントラクトの権限を一括管理するACRの仕組みを理解しよう！

> Access Control Registry（ACR）標準は、複数のスマートコントラクトにおける**ロールベースのアクセス制御（Role-Based Access Control, RBAC）**を一元的に管理するための共通インターフェー

- URL: https://cardene.dev/articles/b3214581da37cdfcdbd7
- 公開日: 2025-10-09
- 更新日: 2025-10-09
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

今回は、複数のスマートコントラクトにおけるロール管理を1つのコントラクトで行う仕組みを提案している**ERC7820**についてまとめていきます！

:::message
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-7820

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

## 概要

#### ACR標準とは

**Access Control Registry（ACR）標準**は、複数のスマートコントラクトにおける**ロールベースのアクセス制御（Role-Based Access Control, RBAC）**を一元的に管理するための共通インターフェースを定義した仕組みです。
これまでのスマートコントラクト開発では、各コントラクトが独自にアクセス制御を実装していましたが、ACR標準では1つの中央レジストリコントラクトで複数コントラクトの権限管理をまとめて扱うことができます。
これにより、複雑化しがちな権限設定を整理し、安全性と保守性を大幅に向上させます。

#### 中央レジストリによる統合管理

ACRの中核は、すべてのアクセス制御情報をまとめる**レジストリコントラクト**にあります。
このコントラクトは、どのアカウントがどのスマートコントラクトでどんなロールを持つかを管理し、他のシステムやユーザーが簡単に問い合わせ（クエリ）できるようになっています。
これにより、個別のコントラクトごとに冗長な管理ロジックを持つ必要がなくなり、開発者はより安全で再利用性の高いスマートコントラクトを設計できます。

#### 標準の主要機能

ACR標準の中心となる機能は以下の3つです。

| 機能名 | 説明 |
| ----- | ---- |
| **登録・登録解除（Registration and Unregistration）** | コントラクトをACRに登録し、そのコントラクト内のロール管理者（Admin）を指定できます。不要になった場合は登録解除も可能です。        |
| **ロール管理（Role Management）** | 管理者は、特定のアカウントに対してロールを付与または剥奪できます。個別にも一括（バッチ）でも操作でき、柔軟かつ詳細な制御が可能です。   |
| **ロール検証（Role Verification）** | 誰でも、あるアカウントが特定のコントラクト内で指定されたロールを持っているかどうかを確認できます。これにより、アクセス制御の透明性が向上します。 |

#### 標準化によるメリット

ACR標準によって、以下のような利点が得られます。

* 各コントラクトにアクセス制御機能を個別に実装する必要がなくなり冗長性を削減。
* ロール付与や削除の時の人的ミスを防ぎセキュリティリスクを低減。
* 統一されたインターフェースにより他のシステムやdAppとの連携が容易に。
* コードのメンテナンスや監査がしやすくなり開発効率と透明性を向上。

これらにより、ACR標準はスマートコントラクトのアクセス制御をより明確かつ安全に実現するための基盤となります。

## 動機

#### 背景

近年、分散型アプリケーション（dApp）は急速に発展し、複数のスマートコントラクトを連携させて動作する構造が一般的になっています。
しかし、その一方で「**誰がどのコントラクトで何をできるのか**」というアクセス制御の管理が非常に複雑になってきました。
現在多くのプロジェクトでは、個別にカスタマイズされたアクセス制御ロジックを持ち、それぞれが異なる仕様や設計方針で実装されています。
その結果、以下のような問題が頻発しています。

* 同様の機能を何度も作り直す「**冗長な実装**」
* 開発チームごとに異なるルールによる「**一貫性の欠如**」
* 設計や実装ミスによる「潜**在的なセキュリティホール**」

#### 標準化の意義

こうした課題を解決するために、アクセス制御を統一的に扱う標準規格としてACRが提案されました。
標準化によって得られる主な効果は以下の通りです。

- **相互運用性（Interoperability）の向上**
    複数のスマートコントラクトやプロトコル間で統一的にアクセス管理を行えるため、システム全体の整合性が高まります。
- **セキュリティの強化**
    標準化されたロジックにより、独自実装によるバグや脆弱性の発生を減らし、監査もしやすくなります。
- **開発効率と透明性の向上**
    開発者はACRの共通インターフェースを利用することで、重複コードの削減と開発スピードの向上を実現できます。
    また、外部からも誰がどんな権限を持っているかを容易に確認できるため、透明性も確保されます。

## 仕様

### AccessControlRegistry コントラクトの概要

`AccessControlRegistry` コントラクトは、スマートコントラクト間で共通的に利用できるアクセス制御（ロールベースアクセス制御: RBAC）を提供します。
主な機能は以下です。

| 機能  | 説明 |
| ---- | ---- |
| コントラクト登録・登録解除 | 各コントラクトをレジストリに登録・解除し、管理者（Admin）を設定する。     |
| ロール付与・剥奪 | 特定のコントラクト内でアカウントにロールを付与・剥奪できる。  |
| ロール確認 | あるアカウントが特定のコントラクトにおいて特定のロールを持っているかを確認できる。 |

また、このコントラクトは透明性を確保するためにイベントを発行し、すべての登録・変更操作をトラッキングできるようにしています。
さらに、ゼロアドレス（`0x000...0`）の登録は拒否する必要があるという明確な制約があります。

### コード全体

```javascript
pragma solidity 0.8.23;
interface IAccessControlRegistry {
    event ContractRegistered(address indexed _contract, address indexed _admin);
    event ContractUnregistered(address indexed _contract, address indexed _admin);
    event RoleGranted(address indexed targetContract, bytes32 indexed role, address indexed account);
    event RoleRevoked(address indexed targetContract, bytes32 indexed role, address indexed account);

    function registerContract(address _admin) external;
    function unRegisterContract(address _contract) external;

    function grantRole(
        address[] memory targetContracts,
        bytes32[] memory roles,
        address[] memory accounts
    ) external;

    function revokeRole(
        address[] memory targetContracts,
        bytes32[] memory roles,
        address[] memory accounts
    ) external;

    function getContractInfo(
        address _contract
    ) external view returns (bool isActive, address admin);

    function getRoleInfo(
        address _contract
    ) external view returns (bool isActive, address admin);
}
```

#### `ContractRegistered`

```javascript
event ContractRegistered(address indexed _contract, address indexed _admin);
```

コントラクトが登録された時に発行されるイベント。

新しいコントラクトがACRに登録された時に発行されます。
これにより、どのコントラクトがいつ登録されたか、またその管理者（Admin）が誰かを追跡できます。

**パラメータ**

* `_contract`
  * 登録されたコントラクトのアドレス。
* `_admin`
  * 登録されたコントラクトの管理者のアドレス。

#### `ContractUnregistered`

```javascript
event ContractUnregistered(address indexed _contract, address indexed _admin);
```

コントラクトが登録解除された時に発行されるイベント。

登録されていたコントラクトがレジストリから削除された時に発行されます。
管理者が解除操作を行ったことを明示的に示します。

**パラメータ**

* `_contract`
  * 登録解除されたコントラクトのアドレス。
* `_admin`
  * 登録解除を実行した管理者のアドレス。

#### `RoleGranted`

```javascript
event RoleGranted(
    address indexed targetContract,
    bytes32 indexed role,
    address indexed account
);
```

特定のコントラクトにおいてアカウントへロールが付与された時に発行されるイベント。

管理者が特定のアカウントに新しいロールを与えた時に発行されます。
このイベントにより、いつ・誰に・どのロールが与えられたかを外部から追跡できます。

**パラメータ**

* `targetContract`
  * 対象のコントラクトのアドレス。
* `role`
  * 付与されたロールの識別子（`bytes32`型）。
* `account`
  * ロールを与えられたアカウントのアドレス。

#### `RoleRevoked`

```javascript
event RoleRevoked(
    address indexed targetContract,
    bytes32 indexed role,
    address indexed account
);
```

特定のコントラクトでアカウントからロールが剥奪された時に発行されるイベント。

管理者がアカウントから既存のロールを削除した時に発行されます。
このイベントによって、アクセス制御の変更履歴が完全に記録されます。

**パラメータ**

* `targetContract`
  * 対象のコントラクトのアドレス。
* `role`
  * 剥奪されたロールの識別子。
* `account`
  * ロールを失ったアカウントのアドレス。

#### `registerContract`

```javascript
function registerContract(address _admin) external;
```

コントラクトをACRに登録する関数。

新しいコントラクトをレジストリに登録し、指定したアドレスをそのコントラクトの管理者（Admin）として設定します。
ゼロアドレスを登録しようとした場合は拒否（MUST reject）されます。

**引数**

* `_admin`
  * 登録するコントラクトの管理者のアドレス。

#### `unRegisterContract`

```javascript
function unRegisterContract(address _contract) external;
```

コントラクトを登録解除する関数。

既に登録されているコントラクトをACRから削除します。
削除操作はそのコントラクトの管理者によってのみ実行可能であるべきです。

**引数**

* `_contract`
  * 登録解除するコントラクトのアドレス。

#### `grantRole`

```javascript
function grantRole(
    address[] memory targetContracts,
    bytes32[] memory roles,
    address[] memory accounts
) external;
```

複数のコントラクトおよびアカウントに対してロールを付与する関数。

同時に複数のコントラクトと複数のアカウントにロールを割り当てることができます。
バッチ処理に対応しており、効率的なアクセス制御管理が可能です。

**引数**

* `targetContracts`
  * ロールを付与するコントラクトのアドレス配列。
* `roles`
  * 付与するロール識別子の配列。
* `accounts`
  * ロールを付与するアカウントのアドレス配列。

#### `revokeRole`

```javascript
function revokeRole(
    address[] memory targetContracts,
    bytes32[] memory roles,
    address[] memory accounts
) external;
```

複数のコントラクトおよびアカウントからロールを剥奪する関数。

指定したロールを複数のコントラクトとアカウントから一括で削除します。
`grantRole`と同様にバッチ処理が可能で、アクセス管理を効率的に更新できます。

**引数**

* `targetContracts`
  * ロールを剥奪するコントラクトのアドレス配列。
* `roles`
  * 剥奪するロール識別子の配列。
* `accounts`
  * ロールを失うアカウントのアドレス配列。

#### `getContractInfo`

```javascript
function getContractInfo(
    address _contract
) external view returns (bool isActive, address admin);
```

登録されているコントラクトの情報を取得する関数。

指定されたコントラクトが現在有効かどうか、またその管理者が誰かを返します。
登録されていないコントラクトを指定した場合はrevertします。

**引数**

* `_contract`
  * 情報を取得したいコントラクトのアドレス。

**戻り値**

* `isActive`
  * コントラクトが有効であるかどうか（`true` / `false`）。
* `admin`
  * そのコントラクトの管理者アドレス。

#### `getRoleInfo`

```javascript
function getRoleInfo(
    address _contract
) external view returns (bool isActive, address admin);
```

特定のコントラクトに関連するロール情報を取得する関数。

指定されたコントラクトに関して、現在有効な状態かどうか、および管理者アドレスを返します。
こちらも登録されていない場合は**revert**します。
`getContractInfo`と類似していますが、ロール関連情報の参照に用いられます。

**引数**

* `_contract`
  * 対象のコントラクトのアドレス。

**戻り値**

* `isActive`
  * コントラクトが有効であるかどうか。
* `admin`
  * そのコントラクトにおける管理者アドレス。

## 参考実装

```js
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;
    }
}
```

## AccessControlRegistry 参考実装の詳細解説

この章では、**AccessControlRegistry** の参考実装コードについて詳しく解説します。
本コントラクトは、`IAccessControlRegistry` インターフェースの実装であり、複数のスマートコントラクトにわたるロールベースアクセス制御を、安全かつ効率的に行う仕組みを提供します。
コードの構造、主要な関数、修飾子、設計上の意図をそれぞれ分かりやすく説明します。

---

### コード全体

```javascript
pragma solidity 0.8.23;

import "./IAccessControlRegistry.sol";

contract AccessControlRegistry is IAccessControlRegistry {
    struct ContractInfo {
        bool isActive;
        address admin;
    }

    mapping(address => ContractInfo) public contracts;
    mapping(address => mapping(address => mapping(bytes32 => bool))) public _contractRoles;

    error ContractAlreadyRegistered();

    modifier onlyAdminOrContract(address _contract) { ... }
    modifier onlyAdmin(address _contract) { ... }
    modifier onlyActiveContract(address _contract) { ... }
    modifier validAddress(address addr) { ... }

    function registerContract(address _admin) external validAddress(_admin) { ... }
    function unRegisterContract(address _contract) public onlyAdmin(_contract) onlyActiveContract(_contract) { ... }

    function grantRole(address[] memory targetContracts, bytes32[] memory roles, address[] memory accounts) public { ... }
    function revokeRole(address[] memory targetContracts, bytes32[] memory roles, address[] memory accounts) public { ... }

    function getContractInfo(address _contract) public view returns (bool isActive, address admin) { ... }
    function getRoleInfo(address targetContract, address account, bytes32 role) public view returns (bool) { ... }

    function _grantRole(address targetContract, bytes32 role, address account) internal onlyAdminOrContract(targetContract) onlyActiveContract(targetContract) validAddress(account) { ... }
    function _revokeRole(address targetContract, bytes32 role, address account) internal onlyAdminOrContract(targetContract) onlyActiveContract(targetContract) validAddress(account) { ... }

    function _isAdmin(address _contract, address _admin) internal view returns (bool) { ... }
}
```

#### `ContractInfo`

```javascript
struct ContractInfo {
    bool isActive;
    address admin;
}
```

登録済みコントラクトの状態を保持するための構造体。

各コントラクトの有効・無効状態と、そのコントラクトを管理する管理者アドレスを保存します。
この構造体は `contracts` マッピングを通じて参照されます。

**パラメータ**

* `isActive`
  * コントラクトが有効であるかを示す真偽値。
* `admin`
  * コントラクトの管理者アドレス。

### ストレージ変数

| 変数名 | 型 | 説明   |
| ----- | -- | ----- |
| `contracts` | `mapping(address => ContractInfo)`  | コントラクトアドレスとその情報（`ContractInfo`）を対応付けるマッピング。 |
| `_contractRoles` | `mapping(address => mapping(address => mapping(bytes32 => bool)))` | コントラクトごと、アカウントごと、ロールごとの権限付与状態を保持するマッピング。    |

#### `ContractAlreadyRegistered`

```javascript
error ContractAlreadyRegistered();
```

すでに登録済みのコントラクトを再度登録しようとした時に発行されるエラー。

コントラクトがすでにアクティブ状態 (`isActive == true`) の場合、このエラーをスローして重複登録を防ぎます。

#### `onlyAdminOrContract`

```javascript
modifier onlyAdminOrContract(address _contract) {
    require(
        _isAdmin(_contract, msg.sender) || 
        (contracts[msg.sender].isActive && msg.sender == _contract),
        "Caller is not admin nor contract"
    );
    _;
}
```

呼び出し元が管理者または対象コントラクト自身であることを確認する修飾子。

この修飾子は、コントラクト自身またはその管理者のみが実行できる関数に適用されます。
これにより、外部からの不正操作を防ぎます。

**パラメータ**

* `_contract`
  * チェック対象のコントラクトアドレス。

#### `onlyAdmin`

```javascript
modifier onlyAdmin(address _contract) {
    require(_isAdmin(_contract, msg.sender), "Caller is not an admin");
    _;
}
```

呼び出し元が管理者であることを確認する修飾子。
対象のコントラクトに対してロール付与や削除を行う時、管理者以外が実行することを防ぎます。

**パラメータ**

* `_contract`
  * 対象のコントラクトアドレス。

#### `onlyActiveContract`

```javascript
modifier onlyActiveContract(address _contract) {
    require(contracts[_contract].isActive, "Contract not registered");
    _;
}
```

コントラクトが登録済み（有効）であることを確認する修飾子。
未登録または無効なコントラクトに対して操作を行おうとした場合に実行をブロックします。

**パラメータ**

* `_contract`
  * 対象のコントラクトアドレス。

#### `validAddress`

```javascript
modifier validAddress(address addr) {
    require(addr != address(0), "Invalid address");
    _;
}
```

アドレスがゼロアドレスでないことを確認する修飾子。

ゼロアドレスの登録やロール付与を防ぎます。
ゼロアドレスを扱うことはセキュリティリスクや予期せぬ動作の原因となるため、このチェックは非常に重要です。

**パラメータ**

* `addr`
  * チェック対象のアドレス。

#### `registerContract`

```javascript
function registerContract(address _admin) external validAddress(_admin)
```

新しいコントラクトを登録する関数。

呼び出し元のコントラクト（`msg.sender`）をレジストリに登録し、指定した `_admin` を管理者として設定します。
すでに登録されている場合は `ContractAlreadyRegistered` エラーを発生させます。

**引数**

* `_admin`
  * 管理者のアドレス。

#### `unRegisterContract`

```javascript
function unRegisterContract(address _contract) 
    public 
    onlyAdmin(_contract) 
    onlyActiveContract(_contract)
```

既存のコントラクトを登録解除する関数。

対象コントラクトの `isActive` フラグを `false` に変更し、`admin` アドレスをリセットします。
登録解除時には `ContractUnregistered` イベントが発行されます。

**引数**

* `_contract`
  * 登録解除するコントラクトのアドレス。

#### `grantRole`

```javascript
function grantRole(
    address[] memory targetContracts,
    bytes32[] memory roles,
    address[] memory accounts
) public
```

複数のコントラクトおよびアカウントに対してロールを付与する関数。

3つの配列（`targetContracts`、`roles`、`accounts`）の長さが一致することを確認し、それぞれの要素に対して `_grantRole` を呼び出します。
一括処理によってガスコストの削減と効率的なロール管理を実現します。

**引数**

* `targetContracts`
  * コントラクトアドレスの配列。
* `roles`
  * 付与するロール識別子の配列。
* `accounts`
  * ロールを付与するアカウントアドレスの配列。

#### `revokeRole`

```javascript
function revokeRole(
    address[] memory targetContracts,
    bytes32[] memory roles,
    address[] memory accounts
) public
```

複数のコントラクトおよびアカウントからロールを剥奪する関数。

`grantRole` と同様に、3つの配列を同じ長さで受け取り、バッチ形式で `_revokeRole` を呼び出します。
効率的な一括権限剥奪を実現します。

**引数**

* `targetContracts`
  * コントラクトアドレスの配列。
* `roles`
  * 剥奪するロール識別子の配列。
* `accounts`
  * ロールを剥奪されるアカウントアドレスの配列。

#### `getContractInfo`

```javascript
function getContractInfo(address _contract)
    public
    view
    returns (bool isActive, address admin)
```

登録されているコントラクトの情報を取得する関数。

指定した `_contract` の `isActive` 状態と `admin` アドレスを返します。
登録されていない場合は `false` と空アドレスが返されます。

**引数**

* `_contract`
  * 対象のコントラクトアドレス。

**戻り値**

* `isActive`
  * コントラクトが有効かどうか。
* `admin`
  * 管理者アドレス。

#### `getRoleInfo`

```javascript
function getRoleInfo(address targetContract, address account, bytes32 role)
    public
    view
    returns (bool)
```

特定のコントラクトにおけるアカウントのロール情報を取得する関数。
指定された `targetContract` において、特定の `account` が `role` を持っているかどうかを真偽値で返します。

**引数**

* `targetContract`
  * 対象のコントラクトアドレス。
* `account`
  * 対象のアカウントアドレス。
* `role`
  * 確認するロールの識別子。

**戻り値**

* `bool`
  * アカウントがロールを持つ場合は `true`。

#### `_grantRole`

```javascript
function _grantRole(address targetContract, bytes32 role, address account)
    internal
    onlyAdminOrContract(targetContract)
    onlyActiveContract(targetContract)
    validAddress(account)
```

内部的にロールを付与する関数。

ロールを `_contractRoles` マッピングに登録し、`RoleGranted` イベントを発行します。
呼び出しは管理者またはコントラクト自身に限定されます。

**引数**

* `targetContract`
  * 対象コントラクトのアドレス。
* `role`
  * 付与するロール識別子。
* `account`
  * 対象アカウントのアドレス。

#### `_revokeRole`

```javascript
function _revokeRole(address targetContract, bytes32 role, address account)
    internal
    onlyAdminOrContract(targetContract)
    onlyActiveContract(targetContract)
    validAddress(account)
```

内部的にロールを剥奪する関数。

指定されたロールを `_contractRoles` から削除し、`RoleRevoked` イベントを発行します。
すでにロールが存在しない場合はエラーをスローします。

**引数**

* `targetContract`
  * 対象コントラクトのアドレス。
* `role`
  * 剥奪するロール識別子。
* `account`
  * 対象アカウントのアドレス。

#### `_isAdmin`

```javascript
function _isAdmin(address _contract, address _admin) internal view returns (bool)
```

特定のアドレスが管理者であるか確認する内部関数。
指定された `_admin` が `_contract` の管理者である場合に `true` を返します。

**引数**

* `_contract`
  * 対象コントラクトのアドレス。
* `_admin`
  * 確認する管理者アドレス。

**戻り値**

* `bool`
  * 管理者である場合は `true`。

### 設計

#### 分散化されたコントラクト登録

中央管理者を持たない設計により、各コントラクトが自ら登録・管理を行います。
これにより、完全な分散性を維持し、特定の権限集中を防ぎます。

#### 効率的なストレージと検索

コントラクト情報とロール情報をマッピングで管理することで、**O(1)** の高速アクセスを実現します。
大規模システムでもスケーラブルに動作するよう設計されています。

#### 柔軟なロール管理

`grantRole` と `revokeRole` による一括操作（バッチ処理）に対応しており、ガスコスト削減と効率的な権限管理を両立します。

#### 強固なセキュリティ設計

管理者専用の操作制限（`onlyAdmin`）、ゼロアドレス防止（`validAddress`）、アクティブチェック（`onlyActiveContract`）によって、不正な操作やミスを防止します。

#### 透明性の高い監査性

すべての重要操作（登録・削除・ロール付与・剥奪）でイベントを発行するため、ブロックチェーン上で完全な操作履歴を追跡可能です。
監査や異常検知が容易になり、セキュリティ面で非常に優れています。

## セキュリティ

#### 管理者限定の操作制限

AccessControlRegistryでは、状態を変更する関数（例えばロール付与・削除、コントラクト登録解除など）を管理者（Admin）のみが実行可能とする制限を設けています。

この仕組みは、スマートコントラクトにおける最も基本的かつ重要なセキュリティ対策の一つです。
もし誰でもロールを付与・削除できると、悪意あるユーザーが他人に権限を与えたり、正当な管理者の権限を奪うといった操作が可能になってしまいます。
そのため、`onlyAdmin` や `onlyAdminOrContract` 修飾子によって、管理者または対象コントラクト自身に限定してこれらの操作が行えるようにしています。

この制限は、アクセス制御全体の信頼性を支える「**最初の防御線**」として機能します。
管理者認証がスマートコントラクト内部で厳格に実施されることで、外部からの不正操作を未然に防止します。

#### 有効コントラクトの確認

ACRでは、すべての操作対象となるコントラクトが「**登録済みで有効（active）**」であることを確認する仕組みを導入しています。

このチェックは、`onlyActiveContract` 修飾子で実装されており、以下のような目的を持ちます。

- **登録されていないコントラクトへの操作防止**
    未登録のコントラクトに対してロールを付与・削除することを禁止します。
    これにより、存在しないまたは意図しないコントラクトへのアクセスが発生するリスクを防ぎます。

- **非アクティブなコントラクトへの操作防止**
    登録解除済みの（`isActive = false`）コントラクトに対しても操作を行えません。
    これにより、古いバージョンのコントラクトや廃止されたプロトコルに対する誤操作を防止します。

このように「**アクティブ状態の確認**」を行うことで、登録済み・有効なコントラクトのみが操作対象となる安全な環境を保証しています。
システムの整合性を維持し、想定外のデータ改変やガバナンスリスクを減らすうえで非常に重要な仕組みです。

#### イベントロギングによる透明性の確保

AccessControlRegistryでは、重要なアクションが発生するたびにイベントを発行（emit）する設計になっています。
これには以下のような目的があります。

| 対応する操作   | 発行されるイベント              |
| ---------- | ---------------------- |
| コントラクト登録   | `ContractRegistered`   |
| コントラクト登録解除 | `ContractUnregistered` |
| ロール付与      | `RoleGranted`          |
| ロール剥奪      | `RoleRevoked`          |

これらのイベントログはブロックチェーン上に永続的に記録されるため、第三者による検証（監査）やトラブル発生時の調査が容易になります。
また、分散的なアプリケーション環境においても、イベントを監視することでリアルタイムにアクセス制御の変更を検出でき、不正行為の早期発見につながります。

具体的には、以下のようなセキュリティ効果があります。

* **透明性の向上**
    すべての権限変更が記録されて履歴を誰でも確認できる。
* **監査容易性**
    外部監査人やガバナンスチームが容易にアクセス制御の変更を追跡可能。
* **不正検出**
    想定外の操作（例：不正なロール付与）が行われた場合、即座に検知可能。

これにより、AccessControlRegistryは「**誰が**」、「**いつ**」、「**どのコントラクトで**」、「**どのロールを変更したか**」という完全な証跡を提供します。

## 引用

Shubham Khandelwal (@shubh-ta), Anushka Yadav (@anushka642000), "ERC-7820: Access Control Registry," Ethereum Improvement Proposals, no. 7820, November 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7820.

## 最後に

今回は「複数のスマートコントラクトにおけるロール管理を1つのコントラクトで行う仕組みを提案している**ERC7820**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4
