# [ERC7794] 複数チェーン上の助成金の管理・追跡を行う仕組みを理解しよう！

> ERC7794は、複数のブロックチェーンにまたがる助成金（グラント）を一元的に管理するGrant Registryコントラクトの仕組みを提案しています。

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

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、複数ブロックチェーンの助成金を一元的に管理・追跡する仕組みを提案している**ERC7794**についてまとめていきます！

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

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

他にも様々なEIPについてまとめています。

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

## 概要

**ERC7794**は、複数のブロックチェーンにまたがる助成金（グラント）を一元的に管理するGrant Registryコントラクトの仕組みを提案しています。
このコントラクトは、プロジェクトの資金調達を管理するためのフレームワークを提供し、助成金の登録、管理、追跡を効率化します。

助成金に関するデータは、変更できないデータと更新可能なデータに分けて整理されています。
また、資金の分配をモジュール化して管理できる仕組みを提供し、オフチェーン（ブロックチェーン外）に保存されたファイルなどのリンクを設定できるようになっています。

さらに、このコントラクトは助成金のライフサイクルに応じたイベントを発行するため、外部のプロトコルが助成金のデータを効率的に取得して透明性を確保できます。
この仕組みにより、異なるプロジェクトや助成金プログラム間での相互運用性が向上し、助成金の成果をより適切に評価できるようになります。

## 動機

現在のEthereumエコシステムには、複数のブロックチェーンにまたがる助成金を一限管理する仕組みが存在しません。
そのため、各助成金プログラムごとに異なるインターフェースや管理手法が採用されており、資金提供者や受給者にとっては以下のような問題が発生します。

- 透明性の欠如
    - 助成金の進捗や資金の使用状況が追跡しづらい。
- 資金分配の管理が煩雑
    - どのプロジェクトにどれだけの資金が割り当てられたかが一目で分からない。
- 助成金の有効性を評価しにくい
    - 異なるプログラム間でデータを統合しにくく、助成金がどのような影響を与えたかの分析が難しい。

また、異なるブロックチェーン上で活動するプロジェクトや開発者が、助成金を受ける時に統一的な管理ができないことも課題です。
現在の仕組みでは各チェーンごとに異なる手続きが必要になるため、資金調達のハードルが上がりプロジェクトの成長を阻害する要因となっています。

Grant Registryコントラクトは、これらの問題を解決するために全ての助成金を一元的に登録・管理できるフレームワークを提供します。
これにより、助成金のライフサイクル全体を効率的に追跡でき、異なるチェーンやプログラム間での相互運用性を向上させることが可能になります。

また、データの標準化により助成金の成果を測定しやすくなり、より深い分析が可能になります。
これにより、助成金がプロジェクトやエコシステム全体にどのような影響を与えたのかを正確に評価できるようになり、透明性の向上と意思決定の改善につながります。

## 仕様

#### `registerGrant`

```js
function registerGrant(
    uint256 id,
    uint256 chainid,
    string memory community,
    address grantManager
) external returns (bytes32);
```

**概要**  

`registerGrant` は、新しい助成金（グラント）を登録する関数。

**詳細**  

この関数を呼び出すことで、新たな助成金を登録できる。助成金ごとに一意の `grantId` を生成し、助成金がどのブロックチェーン上で提供されているか (`chainid`) や、コミュニティ名 (`community`)、管理者 (`grantManager`) を保存します。  

以下の条件を満たす必要があります。  
- `grantManager` のアドレスは `0x0` 以外でなければならない。  
- `community` のラベルは空であってはならない。  
- 助成金 ID (`grantId`) は既存のものと重複してはならない。  

この関数が成功すると、`GrantRegistered` イベントが発行されます。  

**引数**  
- `id`  
  - 助成金プログラムの一意な識別子。  
- `chainid`  
  - 助成金が登録されるブロックチェーンのチェーン ID。  
- `community`  
  - 助成金を提供するコミュニティの名称。  
- `grantManager`  
  - 助成金の管理者のアドレス。  

**戻り値**  
- `grantId`  
  - 助成金を一意に識別する `bytes32` 値。  

---

#### `transferOwnership`

```js
function transferOwnership(bytes32 grantId, address newGrantManager) external;
```

**概要**  

`transferOwnership` は、助成金の管理権限を別のアドレスに委譲する関数。  

**詳細**  

現在の `grantManager` のみがこの関数を呼び出せます。
管理権限を `newGrantManager` に変更することで、新しい管理者が助成金の運営や分配を担当することになります。
`newGrantManager` は `0x0` にできません。  

この関数が成功すると、`OwnershipTransferred` イベントが発行されます。  

**引数**  
- `grantId`  
  - 管理権限を移転する助成金の一意識別子。  
- `newGrantManager`  
  - 新しい助成金管理者のアドレス。  

---

#### `addGrantee`

```js
function addGrantee(bytes32 grantId, address grantee) external;
```

**概要**  

`addGrantee` は、新たな助成金の受給者（`grantee`）を登録する関数。  

**詳細**  

この関数を実行できるのは `grantManager` のみです。
登録する `grantee` は `0x0` 以外のアドレスである必要があり、すでに登録されている `grantee` は追加できません。  

この関数が成功すると、`GranteeAdded` イベントが発行されます。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  
- `grantee`  
  - 登録する受給者のアドレス。  

---

#### `removeGrantee`

```js
function removeGrantee(bytes32 grantId, address grantee) external;
```

**概要**  

`removeGrantee` は、既存の助成金受給者を削除する関数。  

**詳細**  

この関数を実行できるのは `grantManager` のみです。
削除する `grantee` は、すでに助成金に登録されている必要があります。  

この関数が成功すると、`GranteeRemoved` イベントが発行されます。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  
- `grantee`  
  - 削除する受給者のアドレス。  

---

#### `setStartDate`

```js
function setStartDate(bytes32 grantId, uint256 startDate) external;
```

**概要**  

`setStartDate` は、助成金の開始日を設定する関数。  

**詳細**  

`grantManager` のみが実行可能です。
助成金の開始日 (`startDate`) は `block.timestamp` 以上でなければいけません。  

この関数が成功すると、`StartDateSet` イベントが発行されます。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  
- `startDate`  
  - 助成金の開始日時（Unix タイムスタンプ）。  

---

#### `addMilestoneDate`

```js
function addMilestoneDate(bytes32 grantId, uint256 milestoneDate) external;
```

**概要**  

`addMilestoneDate` は、新たなマイルストーン日を追加する関数。  

**詳細**  

`grantManager` のみが実行可能です。
追加する `milestoneDate` はすでに登録されている日付と重複してはいけません。  

この関数が成功すると、`MilestoneDateAdded` イベントが発行されます。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  
- `milestoneDate`  
  - 追加するマイルストーンの日付（Unix タイムスタンプ）。  

---

#### `addDisbursement`

```js
function addDisbursement(
    bytes32 grantId,
    uint256 milestoneDate,
    address fundingToken,
    uint256 fundingAmount
) external;
```

**概要**  

`addDisbursement` は、特定のマイルストーンに対する資金の分配を設定する関数。  

**詳細**  

`grantManager` のみが実行可能です。
指定された `milestoneDate` が有効であり、助成金に登録されている必要があります。  

この関数が成功すると、`DisbursementAdded` イベントが発行されます。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  
- `milestoneDate`  
  - 資金分配を紐づけるマイルストーンの日付（Unix タイムスタンプ）。  
- `fundingToken`  
  - 分配に使用するトークンのアドレス。  
- `fundingAmount`  
  - 分配されるトークンの金額。  

---

#### `removeDisbursement`

```js
function removeDisbursement(bytes32 grantId, uint256 milestoneDate) external;
```

**概要**  

`removeDisbursement` は、指定されたマイルストーンに紐づく資金分配を削除する関数。  

**詳細**  

`grantManager` のみが実行可能です。
指定された `milestoneDate` が助成金のマイルストーンとして存在している必要があります。  

この関数が成功すると、`DisbursementRemoved` イベントが発行されます。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  
- `milestoneDate`  
  - 削除する資金分配が紐づくマイルストーンの日付（Unix タイムスタンプ）。  

---

#### `setDisbursementStatus`

```js
function setDisbursementStatus(
    bytes32 grantId,
    uint256 milestoneDate,
    bool isDisbursed
) external;
```

**概要**  

`setDisbursementStatus` は、指定されたマイルストーンの資金分配状況を更新する関数。  

**詳細**  

`grantManager` のみが実行可能です。
指定された `milestoneDate` が助成金のマイルストーンとして存在している必要があります。  

この関数が成功すると、`DisbursementMade` イベントが発行されます。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  
- `milestoneDate`  
  - 状況を更新する資金分配が紐づくマイルストーンの日付（Unix タイムスタンプ）。  
- `isDisbursed`  
  - `true` の場合、資金が分配されたことを示す。  

---

#### `addExternalLink`

```js
function addExternalLink(bytes32 grantId, string memory link) external;
```

**概要**  

`addExternalLink` は、助成金に関連する外部リンクを追加する関数。  

**詳細**  

`grantManager` のみが実行可能です。
追加する `link` は空文字列ではいけません。

この関数が成功すると、`ExternalLinkAdded` イベントが発行されます。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  
- `link`  
  - 追加する外部リンクのURL。  

---

#### `removeExternalLink`

```js
function removeExternalLink(bytes32 grantId, uint256 index) external;
```

**概要**  

`removeExternalLink` は、助成金に関連する外部リンクを削除する関数。  

**詳細**  

`grantManager` のみが実行可能です。
指定された `index` は、助成金に紐づく外部リンクの配列内で有効な範囲である必要があります。  

この関数が成功すると、`ExternalLinkRemoved` イベントが発行されます。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  
- `index`  
  - 削除する外部リンクの配列内のインデックス。  

---

#### `getGrant`

```js
function getGrant(bytes32 grantId) external view returns (Grant memory);
```

**概要**  

`getGrant` は、指定された助成金の詳細情報を取得する関数。  

**詳細**  

助成金の基本情報（`id`、`chainid`、`community`）を取得できる。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  

**戻り値**  
- `Grant`  
  - 助成金の詳細情報を含む構造体。  

---

#### `getGrantManager`

```js
function getGrantManager(bytes32 grantId) external view returns (address);
```

**概要**  

`getGrantManager` は、指定された助成金の現在の管理者アドレスを取得する関数。  

**詳細**  

助成金の管理者 (`grantManager`) のアドレスを返します。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  

**戻り値**  
- `grantManager`  
  - 助成金の管理者アドレス。  

---

#### `getGrantees`

```js
function getGrantees(bytes32 grantId) external view returns (address[] memory);
```

**概要**  

`getGrantees` は、助成金の受給者一覧を取得する関数。  

**詳細**  

指定された助成金の受給者（`grantees`）のアドレス一覧を取得できます。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  

**戻り値**  
- `address[]`  
  - 受給者のアドレス一覧。  

---

#### `getMilestonesDates`

```js
function getMilestonesDates(bytes32 grantId) external view returns (uint256, uint256[] memory);
```

**概要**  

`getMilestonesDates` は、助成金の開始日とマイルストーン日程を取得する関数。  

**詳細**  

助成金の開始日 (`startDate`) と、設定されたマイルストーン日程のリストを返します。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  

**戻り値**  
- `startDate`  
  - 助成金の開始日（Unix タイムスタンプ）。  
- `milestoneDates`  
  - 設定されたマイルストーン日程のリスト。  

---

#### `getDisbursement`

```js
function getDisbursement(bytes32 grantId, uint256 milestoneDate) external view returns (Disbursements memory);
```

**概要**  

`getDisbursement` は、特定のマイルストーンにおける資金分配の詳細を取得する関数。  

**詳細**  

指定した `milestoneDate` に紐づく資金分配情報（`fundingToken`、`fundingAmount`、`isDisbursed`）を取得できます。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  
- `milestoneDate`  
  - 資金分配を取得するマイルストーンの日付（Unix タイムスタンプ）。  

**戻り値**  
- `Disbursements`  
  - 資金分配の詳細情報を含む構造体。  

---

#### `getExternalLinks`

```js
function getExternalLinks(bytes32 grantId) external view returns (string[] memory);
```

**概要**  

`getExternalLinks` は、助成金に紐づく外部リンクの一覧を取得する関数。  

**詳細**  

指定された助成金に追加された外部リンクのリストを返します。  

**引数**  
- `grantId`  
  - 助成金の一意識別子。  

**戻り値**  
- `string[]`  
  - 助成金に紐づく外部リンクのリスト。  

---

### コントラクト

```js
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;

interface IGrantRegistry {
  /**
   * @dev Thrown when the community name length is invalid (e.g., too short or too long).
   */
  error InvalidCommunityNameLength();

  /**
   * @dev Thrown when the caller is not the current grant manager.
   */
  error InvalidGrantManager();

  /**
   * @dev Thrown when a grant is already registered with the provided ID.
   */
  error GrantAlreadyRegistered();

  /**
   * @dev Thrown when attempting to add a grantee that is already present in the set.
   */
  error GranteeAlreadyAdded();

  /**
   * @dev Thrown when attempting to remove a grantee that is not found in the set.
   */
  error GranteeNotFound();

  /**
   * @dev Thrown when attempting to add or reference an invalid external link.
   */
  error InvalidExternalLink();

  /**
   * @dev Thrown when an invalid index is provided (e.g., out of bounds for an array).
   */
  error InvalidIndex();

  /**
   * @dev Thrown when a milestone date is invalid (e.g., earlier than the grant's start date).
   */
  error InvalidStartDate();

  /**
   * @dev Thrown when attempting to add a milestone date that is already present.
   */
  error MilestoneDateAlreadyAdded();

  /**
   * @dev Thrown when attempting to remove or reference a milestone date that is not found.
   */
  error MilestoneDateNotFound();

  /**
   * @dev Emitted when a new grant is registered.
   * @param grantId The unique identifier for the grant.
   * @param id The grant's unique numeric ID.
   * @param chainid The chain ID where the grant is registered.
   * @param community The name of the community that issued the grant.
   * @param grantManager The address of the grant manager.
   */
  event GrantRegistered(
    bytes32 indexed grantId,
    uint256 indexed id,
    uint256 chainid,
    string indexed community,
    address grantManager
  );

  /**
   * @dev Emitted when the ownership of a grant is transferred.
   * @param grantId The unique identifier of the grant.
   * @param newGrantManager The address of the new grant manager.
   */
  event OwnershipTransferred(
    bytes32 indexed grantId,
    address indexed newGrantManager
  );

  /**
   * @dev Emitted when a new grantee is added to the grant.
   * @param grantId The unique identifier of the grant.
   * @param grantee The address of the new grantee.
   */
  event GranteeAdded(bytes32 indexed grantId, address indexed grantee);

  /**
   * @dev Emitted when a grantee is removed from the grant.
   * @param grantId The unique identifier of the grant.
   * @param grantee The address of the removed grantee.
   */
  event GranteeRemoved(bytes32 indexed grantId, address indexed grantee);

  /**
   * @dev Emitted when the start date of a grant is set.
   * @param grantId The unique identifier of the grant.
   * @param startDate The timestamp representing the start date.
   */
  event StartDateSet(bytes32 indexed grantId, uint256 startDate);

  /**
   * @dev Emitted when a new milestone date is added to the grant.
   * @param grantId The unique identifier of the grant.
   * @param milestoneDate The timestamp of the added milestone.
   */
  event MilestoneDateAdded(bytes32 indexed grantId, uint256 milestoneDate);

  /**
   * @dev Emitted when a milestone date is removed from the grant.
   * @param grantId The unique identifier of the grant.
   * @param milestoneDate The timestamp of the removed milestone.
   */
  event MilestoneDateRemoved(bytes32 indexed grantId, uint256 milestoneDate);

  /**
   * @dev Emitted when a disbursement is added to a milestone.
   * @param grantId The unique identifier of the grant.
   * @param milestoneDate The timestamp of the milestone.
   * @param fundingToken The token used for the disbursement.
   * @param fundingAmount The amount of the disbursement.
   */
  event DisbursementAdded(
    bytes32 indexed grantId,
    uint256 milestoneDate,
    address indexed fundingToken,
    uint256 fundingAmount
  );

  /**
   * @dev Emitted when a disbursement is removed from a milestone.
   * @param grantId The unique identifier of the grant.
   * @param milestoneDate The timestamp of the milestone.
   */
  event DisbursementRemoved(bytes32 indexed grantId, uint256 milestoneDate);

  /**
   * @dev Emitted when a disbursement status is updated.
   * @param grantId The unique identifier of the grant.
   * @param milestoneDate The timestamp of the milestone.
   * @param isDisbursed Boolean indicating if the disbursement has been made.
   */
  event DisbursementMade(
    bytes32 indexed grantId,
    uint256 milestoneDate,
    bool isDisbursed
  );

  /**
   * @dev Emitted when an external link is added to the grant.
   * @param grantId The unique identifier of the grant.
   * @param link The external URL added.
   */
  event ExternalLinkAdded(bytes32 indexed grantId, string link);

  /**
   * @dev Emitted when an external link is removed from the grant.
   * @param grantId The unique identifier of the grant.
   * @param link The external URL removed.
   */
  event ExternalLinkRemoved(bytes32 indexed grantId, string link);

  /**
   * @dev Registers a new grant with the provided details. `grantId` is generated by hashing the grant
   * details and the current timestamp.
   *
   * Requirements:
   *
   * - The `grantManager` address must not be the zero address.
   * - The `community` name must not be empty.
   * - The grant must not already be registered.
   *
   * Emits a {GrantRegistered} event.
   *
   * @param id The unique identifier for the grant program.
   * @param chainid The chain ID where the grant is being registered.
   * @param community The name of the community or protocol issuing the grant.
   * @param grantManager The address of the grant manager.
   * @return The generated `grantId` as a bytes32 value.
   */
  function registerGrant(
    uint256 id,
    uint256 chainid,
    string memory community,
    address grantManager
  ) external returns (bytes32);

  /**
   * @dev Transfers ownership of the grant to a new grant manager.
   *
   * Requirements:
   *
   * - The caller must be the current grant manager.
   * - The `newGrantManager` address must not be the zero address.
   *
   * Emits an {OwnershipTransferred} event.
   *
   * @param grantId The unique identifier of the grant.
   * @param newGrantManager The address of the new grant manager.
   */
  function transferOwnership(bytes32 grantId, address newGrantManager) external;

  /**
   * @dev Adds a new grantee to the grant.
   *
   * Requirements:
   *
   * - The caller must be the current grant manager.
   * - The `grantee` address must not be the zero address.
   *
   * Emits a {GranteeAdded} event.
   *
   * @param grantId The unique identifier of the grant.
   * @param grantee The address of the grantee to be added.
   */
  function addGrantee(bytes32 grantId, address grantee) external;

  /**
   * @dev Removes an existing grantee from the grant.
   *
   * Requirements:
   *
   * - The caller must be the current grant manager.
   * - The `grantee` address must be present in the grant.
   *
   * Emits a {GranteeRemoved} event.
   *
   * @param grantId The unique identifier of the grant.
   * @param grantee The address of the grantee to be removed.
   */
  function removeGrantee(bytes32 grantId, address grantee) external;

  /**
   * @dev Sets the start date for the grant.
   *
   * Requirements:
   *
   * - The caller must be the current grant manager.
   *
   * Emits a {StartDateSet} event.
   *
   * @param grantId The unique identifier of the grant.
   * @param startDate The timestamp representing the start date.
   */
  function setStartDate(bytes32 grantId, uint256 startDate) external;

  /**
   * @dev Adds a new milestone date for the grant.
   *
   * Requirements:
   *
   * - The caller must be the current grant manager.
   * - The milestone date must not already exist in the grant.
   *
   * Emits a {MilestoneDateAdded} event.
   *
   * @param grantId The unique identifier of the grant.
   * @param milestoneDate The timestamp representing the milestone date.
   */
  function addMilestoneDate(bytes32 grantId, uint256 milestoneDate) external;

  /**
   * @dev Removes a milestone date from the grant.
   *
   * Requirements:
   *
   * - The caller must be the current grant manager.
   * - The milestone date must exist in the grant.
   *
   * Emits a {MilestoneDateRemoved} event.
   *
   * @param grantId The unique identifier of the grant.
   * @param milestoneDate The timestamp representing the milestone date to be removed.
   */
  function removeMilestoneDate(bytes32 grantId, uint256 milestoneDate) external;

  /**
   * @dev Ovewrites a disbursement for a specific milestone.
   *
   * Requirements:
   *
   * - The caller must be the current grant manager.
   * - The milestone date must exist in the grant.
   *
   * Emits a {DisbursementAdded} event.
   *
   * @param grantId The unique identifier of the grant.
   * @param milestoneDate The milestone date associated with the disbursement.
   * @param fundingToken The address of the token used for funding.
   * @param fundingAmount The amount of tokens to be disbursed.
   */
  function addDisbursement(
    bytes32 grantId,
    uint256 milestoneDate,
    address fundingToken,
    uint256 fundingAmount
  ) external;

  /**
   * @dev Removes a disbursement for a specific milestone.
   *
   * Requirements:
   *
   * - The caller must be the current grant manager.
   * - The milestone date must exist in the grant.
   *
   * Emits a {DisbursementRemoved} event.
   *
   * @param grantId The unique identifier of the grant.
   * @param milestoneDate The milestone date associated with the disbursement.
   */
  function removeDisbursement(bytes32 grantId, uint256 milestoneDate) external;

  /**
   * @dev Updates the disbursement status for a specific milestone.
   *
   * Requirements:
   *
   * - The caller must be the current grant manager.
   * - The milestone date must exist in the grant.
   *
   * Emits a {DisbursementMade} event.
   *
   * @param grantId The unique identifier of the grant.
   * @param milestoneDate The milestone date associated with the disbursement.
   * @param isDisbursed A boolean value indicating if the disbursement has been made.
   */
  function setDisbursementStatus(
    bytes32 grantId,
    uint256 milestoneDate,
    bool isDisbursed
  ) external;

  /**
   * @dev Adds an external link related to the grant.
   *
   * Requirements:
   *
   * - The caller must be the current grant manager.
   * - The link must not be empty.
   *
   * Emits an {ExternalLinkAdded} event.
   *
   * @param grantId The unique identifier of the grant.
   * @param link The external URL to be added.
   */
  function addExternalLink(bytes32 grantId, string memory link) external;

  /**
   * @dev Removes an external link associated with the grant.
   *
   * Requirements:
   *
   * - The caller must be the current grant manager.
   * - The index must be within the bounds of the external links array.
   *
   * Emits an {ExternalLinkRemoved} event.
   *
   * @param grantId The unique identifier of the grant.
   * @param index The index of the external link to be removed.
   */
  function removeExternalLink(bytes32 grantId, uint256 index) external;

  /**
   * @dev Retrieves details of a specific grant by its ID
   * @param grantId The unique identifier of the grant
   * @return The `Grant` struct containing id, chainid, and community label
   */
  function getGrant(bytes32 grantId) external view returns (Grant memory);

  /**
   * @dev Retrieves the current grant manager for a specific grant
   * @param grantId The unique identifier of the grant
   * @return The address of the grant manager
   */
  function getGrantManager(bytes32 grantId) external view returns (address);

  /**
   * @dev Retrieves the list of grantees associated with a specific grant
   * @param grantId The unique identifier of the grant
   * @return An array of addresses representing the grantees
   */
  function getGrantees(
    bytes32 grantId
  ) external view returns (address[] memory);

  /**
   * @dev Retrieves the start date and list of milestone dates for a specific grant
   * @param grantId The unique identifier of the grant
   * @return The start date and an array of milestone dates
   */
  function getMilestonesDates(
    bytes32 grantId
  ) external view returns (uint256, uint256[] memory);

  /**
   * @dev Retrieves the disbursement details for a specific milestone in a grant
   * @param grantId The unique identifier of the grant
   * @param milestoneDate The date of the milestone for which disbursement details are requested
   * @return The `Disbursements` struct containing the token address, funding amount, and disbursement status
   */
  function getDisbursement(
    bytes32 grantId,
    uint256 milestoneDate
  ) external view returns (Disbursements memory);

  /**
   * @dev Retrieves the list of external links associated with a specific grant
   * @param grantId The unique identifier of the grant
   * @return An array of strings representing the external links
   */
  function getExternalLinks(
    bytes32 grantId
  ) external view returns (string[] memory);
}
```

## 補足

### フィールドの分離による効率的なデータ管理  

このコントラクトでは、データを識別情報（`id` や `chainid`）、助成金の詳細情報（`community` など）、資金分配に関する情報（`milestones` や `disbursements`）といった異なるカテゴリに分けています。
この設計により、ブロックチェーン上のストレージを効率的に利用できます。  

特に、`id` や `chainid`、`community` などの基本的な識別情報は定数として管理され、登録後に変更されることはありません。
これにより、助成金の基本情報の一貫性が保たれて異なるシステムやエコシステム間で参照しやすくなります。
一方で、マイルストーンの追加や受給者の変更など、助成金の進行に応じて変化する情報は変数として管理されて更新が可能になります。
この分離により、助成金の透明性を維持しながらも、必要な変更を柔軟に行うことができます。  

### モジュール化された資金分配管理  

**ERC7794**では、助成金の資金分配を必ずオンチェーンで処理する必要はないです。
資金の分配は、コントラクト内部で直接トークンを送付する方法だけでなく、外部リンクを通じてオフチェーンで管理することもできます。  

この設計により、すべての助成金プログラムが同じ方式で資金を配布する必要がなくなり、個々のプロジェクトが適切な方法を選択できるようになります。
例えば、銀行送金や中央集権的な組織が管理する資金分配システムと統合することも可能になります。
一方で、完全にオンチェーンでの分配をしているプロジェクトは、コントラクト内部で資金の移動を行う実装にもできます。  

この柔軟性により、異なる規模や性質の助成金プログラムにも対応でき、Ethereum だけでなく他のブロックチェーンとの互換性も高めることができます。
また、オフチェーン管理を選択した場合でも、助成金の進捗や分配状況のステータスをコントラクト上に記録することができるため、エコシステム全体の透明性は維持されます。  

### チームベースの助成金管理  

このコントラクトでは、助成金の受給者を複数のアドレスで管理するチーム単位の構造を採用しています。
受給者として登録されるアドレスは、`EnumerableSet` を利用して管理されます。
この仕組みにより、助成金の受給者リストの検索や追加・削除がガス効率よく行えるようになります。  

また、助成金の進行に応じて、チーム内の参加者を変更することが可能です。
例えば、新たなメンバーを追加したり、役割を終えたメンバーを削除したりすることで、助成金チームの運営を動的に管理できます。
この設計によりチームの構成を柔軟に変更できるため、プロジェクトの状況に応じた適切な助成金管理が可能になります。  

さらに、この仕組みは貢献度の記録や評判システムと連携することにも適しています。
例えば、助成金を受け取ったメンバーの貢献状況を記録し、その情報をもとに今後の助成金審査に活用することも可能です。
このような透明性の向上により、助成金の効果を評価しやすくなり、助成金の適切な運営が促進されます。  

### スケーラブルで効率的な設計  

このコントラクトは、単なる助成金の管理システムではなく、スケーラブルで長期的に利用可能な仕組みを目指しています。
透明性、モジュール性、ガスコストの最適化を考慮し、さまざまな助成金プログラムの要件に対応できるように設計されています。  

この設計により、将来的に新たな助成金の形態が登場しても、システム全体を改修することなく柔軟に適応できるようになっています。
また、データの標準化により、助成金の評価や分析が容易になり、エコシステム全体の資金の流れを可視化することができます。  

## 参考実装

```js
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;

import { IGrantRegistry } from "./IGrantRegistry.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

contract GrantRegistry is IGrantRegistry {
  using EnumerableSet for EnumerableSet.AddressSet;
  using EnumerableSet for EnumerableSet.UintSet;

  /**
   * @dev Mapping to store the details of each grant, keyed by its unique grantId.
   */
  mapping(bytes32 => Grant) private _grants;

  /**
   * @dev Stores information about the participants in each grant (manager and grantees), keyed by the grantId.
   * This mapping allows tracking of the grant manager and the associated grantees for each grant.
   */
  mapping(bytes32 => Participants) private _participants;

  /**
   * @dev Stores milestone-related data for each grant, keyed by the grantId.
   * This includes the start date, milestone dates, and disbursements related to each milestone.
   */
  mapping(bytes32 => Milestones) private _milestones;

  /**
   * @dev Stores external links related to each grant, such as proposal URLs or related documentation, keyed by grantId.
   * External links provide references to off-chain information about the grant.
   */
  mapping(bytes32 => string[]) private _externalLinks;

  /**
   * @dev See {IGrantRegistry-registerGrant}.
   */
  function registerGrant(
    uint256 id,
    uint256 chainid,
    string memory community,
    address grantManager
  ) external returns (bytes32) {
    bytes32 grantId = keccak256(
      abi.encodePacked(id, chainid, community, block.timestamp)
    );

    if (grantManager == address(0)) revert InvalidGrantManager();
    if (bytes(community).length == 0) revert InvalidCommunityNameLength();
    if (bytes(_grants[grantId].community).length > 0)
      revert GrantAlreadyRegistered();

    _grants[grantId] = Grant(id, chainid, community);
    _participants[grantId].grantManager = grantManager;

    emit GrantRegistered(grantId, id, chainid, community, grantManager);
    return grantId;
  }

  /**
   * @dev See {IGrantRegistry-transferOwnership}.
   */
  function transferOwnership(
    bytes32 grantId,
    address newGrantManager
  ) external {
    _requireManager(grantId);
    if (newGrantManager == address(0)) revert InvalidGrantManager();
    _participants[grantId].grantManager = newGrantManager;
    emit OwnershipTransferred(grantId, newGrantManager);
  }

  /**
   * @dev See {IGrantRegistry-addGrantee}.
   */
  function addGrantee(bytes32 grantId, address grantee) external {
    _requireManager(grantId);
    if (grantee == address(0)) revert InvalidGrantManager();
    bool success = _participants[grantId].grantees.add(grantee);
    if (!success) revert GranteeAlreadyAdded();
    emit GranteeAdded(grantId, grantee);
  }

  /**
   * @dev See {IGrantRegistry-removeGrantee}.
   */
  function removeGrantee(bytes32 grantId, address grantee) external {
    _requireManager(grantId);
    bool success = _participants[grantId].grantees.remove(grantee);
    if (!success) revert GranteeNotFound();
    emit GranteeRemoved(grantId, grantee);
  }

  /**
   * @dev See {IGrantRegistry-setStartDate}.
   */
  function setStartDate(bytes32 grantId, uint256 startDate) external {
    _requireManager(grantId);
    _milestones[grantId].startDate = startDate;
    emit StartDateSet(grantId, startDate);
  }

  /**
   * @dev See {IGrantRegistry-addMilestoneDate}.
   */
  function addMilestoneDate(bytes32 grantId, uint256 milestoneDate) external {
    _requireManager(grantId);
    bool success = _milestones[grantId].milestonesDates.add(milestoneDate);
    if (!success) revert MilestoneDateAlreadyAdded();
    emit MilestoneDateAdded(grantId, milestoneDate);
  }

  /**
   * @dev See {IGrantRegistry-removeMilestoneDate}.
   */
  function removeMilestoneDate(
    bytes32 grantId,
    uint256 milestoneDate
  ) external {
    _requireManager(grantId);
    bool success = _milestones[grantId].milestonesDates.remove(milestoneDate);
    if (!success) revert MilestoneDateNotFound();
    emit MilestoneDateRemoved(grantId, milestoneDate);
  }

  /**
   * @dev See {IGrantRegistry-addDisbursement}.
   */
  function addDisbursement(
    bytes32 grantId,
    uint256 milestoneDate,
    address fundingToken,
    uint256 fundingAmount
  ) external {
    _requireManager(grantId);
    _requireMilestoneDate(grantId, milestoneDate);
    _milestones[grantId].disbursements[milestoneDate] = Disbursements(
      fundingToken,
      fundingAmount,
      false
    );
    emit DisbursementAdded(grantId, milestoneDate, fundingToken, fundingAmount);
  }

  /**
   * @dev See {IGrantRegistry-removeDisbursement}.
   */
  function removeDisbursement(bytes32 grantId, uint256 milestoneDate) external {
    _requireManager(grantId);
    _requireMilestoneDate(grantId, milestoneDate);
    delete _milestones[grantId].disbursements[milestoneDate];
    emit DisbursementRemoved(grantId, milestoneDate);
  }

  /**
   * @dev See {IGrantRegistry-setDisbursementStatus}.
   */
  function setDisbursementStatus(
    bytes32 grantId,
    uint256 milestoneDate,
    bool isDisbursed
  ) external {
    _requireManager(grantId);
    _requireMilestoneDate(grantId, milestoneDate);
    _milestones[grantId].disbursements[milestoneDate].isDisbursed = isDisbursed;
    emit DisbursementMade(grantId, milestoneDate, isDisbursed);
  }

  /**
   * @dev See {IGrantRegistry-addExternalLink}.
   */
  function addExternalLink(bytes32 grantId, string memory link) external {
    _requireManager(grantId);
    if (bytes(link).length == 0) revert InvalidExternalLink();
    _externalLinks[grantId].push(link);
    emit ExternalLinkAdded(grantId, link);
  }

  /**
   * @dev See {IGrantRegistry-removeExternalLink}.
   */
  function removeExternalLink(bytes32 grantId, uint256 index) external {
    _requireManager(grantId);
    if (index >= _externalLinks[grantId].length) revert InvalidIndex();
    string memory link = _externalLinks[grantId][index];
    _externalLinks[grantId][index] = _externalLinks[grantId][
      _externalLinks[grantId].length - 1
    ];
    _externalLinks[grantId].pop();
    emit ExternalLinkRemoved(grantId, link);
  }

  /**
   * @dev Ensures that the caller is the grant manager for the given grantId.
   * Reverts with `InvalidGrantManager` if the caller is not the grant manager.
   * @param grantId The unique identifier of the grant being checked.
   */
  function _requireManager(bytes32 grantId) internal view {
    if (msg.sender != _participants[grantId].grantManager)
      revert InvalidGrantManager();
  }

  /**
   * @dev Ensures that the milestone date is present in the grant.
   * Reverts with `MilestoneDateNotFound` if the milestone date is not present.
   * @param grantId The unique identifier of the grant being checked.
   * @param milestoneDate The milestone date being checked.
   */
  function _requireMilestoneDate(
    bytes32 grantId,
    uint256 milestoneDate
  ) internal view {
    if (!_milestones[grantId].milestonesDates.contains(milestoneDate))
      revert MilestoneDateNotFound();
  }

  /**
   * @dev See {IGrantRegistry-getGrant}.
   */
  function getGrant(bytes32 grantId) external view returns (Grant memory) {
    return _grants[grantId];
  }

  /**
   * @dev See {IGrantRegistry-getGrantManager}.
   */
  function getGrantManager(bytes32 grantId) external view returns (address) {
    return _participants[grantId].grantManager;
  }

  /**
   * @dev See {IGrantRegistry-getGrantees}.
   */
  function getGrantees(
    bytes32 grantId
  ) external view returns (address[] memory) {
    return _participants[grantId].grantees.values();
  }

  /**
   * @dev See {IGrantRegistry-getMilestonesDates}.
   */
  function getMilestonesDates(
    bytes32 grantId
  ) external view returns (uint256, uint256[] memory) {
    return (
      _milestones[grantId].startDate,
      _milestones[grantId].milestonesDates.values()
    );
  }

  /**
   * @dev See {IGrantRegistry-getDisbursement}.
   */
  function getDisbursement(
    bytes32 grantId,
    uint256 milestoneDate
  ) external view returns (Disbursements memory) {
    return _milestones[grantId].disbursements[milestoneDate];
  }

  /**
   * @dev See {IGrantRegistry-getExternalLinks}.
   */
  function getExternalLinks(
    bytes32 grantId
  ) external view returns (string[] memory) {
    return _externalLinks[grantId];
  }
}
```

## 引用

Guilherme Neves (@0xneves), "ERC-7794: Grant Registry [DRAFT]," Ethereum Improvement Proposals, no. 7794, October 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7794.

## 最後に

今回は「複数ブロックチェーンの助成金を一元的に管理・追跡する仕組みを提案している**ERC7794**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下の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
