# [ERC5805] 投票の委任と履歴管理の仕組みを理解しよう！

> ERC5805は、DAO（分散型自律組織）の投票トークンに必須となる「委任（delegation：自分の投票権を別のアドレスに預けること）」と「チェックポイント（checkpoint：過去の投票権の状態を記録し後から参照できる仕組み）」のイ

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

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、DAOなどのオンチェーンガバナンスで使われるトークンに「投票権の委任」や「過去の投票権の照会」を標準化する仕組みを提案している**ERC5805**についてまとめていきます！

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

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

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

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

## 概要

**ERC5805**は、DAO（分散型自律組織）の投票トークンに必須となる「委任（delegation：自分の投票権を別のアドレスに預けること）」と「チェックポイント（checkpoint：過去の投票権の状態を記録し後から参照できる仕組み）」のインターフェースについて提案しています。
**ERC20**や**ERC721**を含む各種トークンに適用でき、ブロック番号・ブロックタイムスタンプ・その他の単調増加関数など、任意のタイムトラッキング関数で投票履歴を管理できる点も特徴です。
これにより、チェーンごとにブロック間隔が不規則なマルチチェーン環境でも一貫したガバナンスが実現できます。

## 動機

現在のトークン標準が備える `balanceOf(address)` だけでは、以下の問題が生じます。

* ユーザーが投票後にトークンを送付して同じトークンで二重に投票できてしまう。
* トークンの保有者が、所有権を移さずに投票権だけを他人に安全に委任することができない。

これらを解決するため、実運用では以下の機能を持つ投票トークンが登場しています。

* 誰に投票権を委任するかを指定して保有量と投票権を切り分ける。
* 各アカウントの投票権をチェックポイントに記録して過去時点の値を参照できる。

しかし実装方式が標準化されておらず、多数のチェーンで採用される「**ブロック番号のみで管理する方式**」は、ブロック生成間隔が等間隔でない環境に適しません。
**ERC5805**は、これらの問題を解消してトークンとガバナンスコントラクトが互換的に連携できる基盤を提供します。

## 仕様

**ERC5805**は以下の要素で構成されます。

* 各アドレスは自身または他人へ投票権を委任できる（`delegate`）。
* 委任されたトークン残高が、その対象アカウントの投票権として加算される。
* 委任関係は過去の履歴を含めて記録されて任意の時点の投票権を参照できる（`getPastVotes`）。
* 投票履歴の時刻は `block.number`、`block.timestamp`、その他の単調増加関数を利用できる。
* **ERC6372**に準拠することで時間計測の方式を外部に明示できる。
* トークンを `address(0)` に委任した場合は投票としてカウントされない。

:::message
**ERC6372**については以下の記事を参考にしてください。
:::

[\[ERC6372\] コントラクトが使用している時間表現を取得する仕組みを理解しよう！](/articles/eacc6a32cbac790df52d)

### 関数

#### `getVotes`

```javascript
function getVotes(address account) external view returns (uint256);
```

現在の投票権を取得する関数。
指定されたアカウントに委任されている全投票権の合計を返します。`address(0)` に対して呼び出した場合は常に `0` を返す必要があります。

**引数**

* `account`
    * 投票権を取得したい対象アドレス。

**戻り値**

* `votingWeight`
    * 現時点で委任されている投票権の総量。

#### `getPastVotes`

```javascript
function getPastVotes(address account, uint256 timepoint) external view returns (uint256);
```

過去の投票権を取得する関数。
指定された時刻（`block.number` など）において、どれだけの投票権がそのアカウントに委任されていたかを返します。
将来の時刻を指定した場合は `revert` します。

**引数**

* `account`
    * 調査対象のアカウント。
* `timepoint`
    * 参照したい時刻（ブロック番号やタイムスタンプ）。

**戻り値**

* `votingWeight`
    * 指定時刻に委任されていた投票権の量。

#### `delegates`

```javascript
function delegates(address account) external view returns (address);
```

現在の委任先アドレスを取得する関数。
指定されたアカウントが投票権を誰に委任しているかを返します。
`address(0)` を返す場合、その投票権はカウントされません。

**引数**

* `account`
    * 委任元のアカウント。

**戻り値**

* `delegatee`
    * 委任先のアドレス。

#### `delegate`

```javascript
function delegate(address delegatee) external;
```

自分の投票権を他アカウントへ委任する関数。
呼び出し元のアカウントの投票権を、指定したアカウントに委任します。
変更があった場合、`DelegateChanged` と `DelegateVotesChanged` イベントが発行されます。

**引数**

* `delegatee`
    * 新たな委任先となるアドレス。

#### `delegateBySig`

```javascript
function delegateBySig(
  address delegatee,
  uint256 nonce,
  uint256 expiry,
  uint8 v,
  bytes32 r,
  bytes32 s
) external;
```

署名によって委任を行う関数。
オフチェーンで署名されたデリゲート情報を用いて、オンチェーンで委任を設定します。
`nonce` や `expiry` を使ってリプレイ攻撃を防止します。

**引数**

* `delegatee`
    * 委任先。
* `nonce`
    * 一意な実行カウント。
* `expiry`
    * 有効期限（タイムスタンプ）。
* `v`, `r`, `s`
    * **EIP712**署名パーツ。

:::message
**EIP712**については以下の記事を参考にしてください。
:::

[\[EIP712\] 型付き構造化データのハッシュ化と署名の仕組みを理解しよう！](/articles/f54bc85cb66de5a54257)

#### `nonces`

```javascript
function nonces(address owner) public view virtual returns (uint256);
```

アカウントの現在の `nonce` を取得する関数。
署名による委任処理（`delegateBySig`）のリプレイ防止用カウンタです。
処理ごとに1ずつ増加させます。

**引数**

* `owner`
    * 対象のアカウントアドレス。

**戻り値**

* `nonce`
    * 現在のカウンタ値。

### イベント

#### `DelegateChanged`

```javascript
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
```

委任先を変更したときに発行されるイベント。
`delegate` または `delegateBySig` により、投票権の委任先が変更されたときに発行されます。

**パラメータ**

* `delegator`
    * 委任を行ったアカウント。
* `fromDelegate`
    * 元の委任先。
* `toDelegate`
    * 新たな委任先。

#### `DelegateVotesChanged`

```javascript
event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);
```

委任先の投票権が変化したときに発行されるイベント。
以下のケースで発行されます。

* 委任元のアカウントが委任先を変更したとき。
* トークンが送受信され、そのアカウントが誰かに委任していた場合。

**パラメータ**

* `delegate`
    * 委任先。
* `previousBalance`
    * 変更前の投票権。
* `newBalance`
    * 変更後の投票権。

## 補足

委任機構を導入すると、トークン保有者はトークン自体を移動させずに投票権だけを信頼できる代表者へ預けられます。
これにより、実際に投票ガスを支払う必要があるアカウント数が抑えられ、小口保有者の意思を代表者経由で反映しやすくなります。
保有者はいつでも自分で投票権を取り戻し、別の相手や自分自身へ再委任できます。

チェックポイントは二重投票の防止策です。
ガバナンス提案の投票では、あらかじめ定義された「スナップショット時点」の委任状態だけが有効になります。
スナップショット後のトークン移転は投票権に影響しません。
そのため、投票に参加したいユーザーはスナップショットより前にトークンを取得して委任しておく必要があります。
多くのガバナーは、提案提出からスナップショットまで猶予期間を置き、委任変更や追加購入を行えるようにしています。

タイムスタンプは**ERC6372**の `clock()` で `uint48` 型として扱われますが、`getPastVotes` の引数は後方互換のため `uint256` です。
`48 ビットを超える値（未来時刻）を渡した場合、関数はリバートすべきです。

`delegateBySig` は、オフチェーン署名を用いたガスレス委任フローを実現します。`nonces` マッピングはリプレイ攻撃を防ぐために使用します。署名形式には、広く対応している**EIP712**型メッセージを採用しています。

## 互換性

CompoundとOpenZeppelinには既に委任機能付きトークン実装があります。
**ERC5805**の委任関連メソッドは両実装と共通で、過去票取得はOpenZeppelinと同じ `uint256` 返値型を採用して制限を回避しています。

どちらの実装もチェックポイントに `block.number` を使用し、**ERC6372**を未実装ですが、その場合でも**ERC5805**の「**ブロック番号モード**」と互換です。
したがって、OpenZeppelin ベースの既存ガバナーは変更なしで**ERC5805**対応トークンを扱えます。

## セキュリティ

**ERC5805**を安全に利用するためには、投票権の取得や委任に関していくつかのセキュリティリスクとその対策を理解しておく必要があります。

### 時刻の取り扱いによるリスク

投票履歴を取得する際、`clock()` の戻り値が使用している時刻の形式（ブロック番号 or タイムスタンプ）に応じて、照会パラメータが適切かを必ず確認する必要があります。

* タイムスタンプを使っているコントラクトに対して、ブロック番号で照会すると常に `0` を返す可能性があります。
* ブロック番号を使っているコントラクトに対して、タイムスタンプで照会すると `revert` （失敗）します。

### `delegateBySig` のフロントラン攻撃

署名ベースの委任（`delegateBySig`）は、他者が先に同じ署名を使ってトランザクションを送ることで「フロントラン」される可能性があります。
ただし、これは意図された委任内容を早めに実行されるだけであり、署名者本人にとっての結果は変わりません。

### `address(0)` への誤委任の防止

`ecrecover`（署名検証プリコンパイル）は、無効な署名を渡すと `address(0)` を返しますが、これは明示的なエラーとはならないため注意が必要です。
委任先が `address(0)` のまま登録されると、誰にもアクセスできない投票権が永遠にロックされる危険があります。
そのため、`delegateBySig` 実行時には `signer != address(0)` のチェックが必須です。

### 委任署名の検閲リスク

署名付き委任（Delegation）は中継者によってオンチェーンに送信されない（= 検閲される）可能性があります。
このリスクへの対策として、以下の処理を実行します。

* `expiry` を指定して署名の有効期限を制限する。
* 署名者自身がETHを保有している場合、自分で `delegateBySig` を送信する。

### `DOMAIN_SEPARATOR` のチェーン分岐リスク

`DOMAIN_SEPARATOR` に `chainId` を含め、署名検証時に再生成せず固定値として使用する場合、将来的なチェーン分岐（フォーク）により他チェーンでのリプレイ攻撃が成立する可能性があります。

* このリスクを防ぐには、署名ごとに `DOMAIN_SEPARATOR` を動的に再計算することが望ましいです。

## 引用

Hadrien Croubois (@Amxx), Francisco Giordano (@frangio), "ERC-5805: Voting with delegation [DRAFT]," Ethereum Improvement Proposals, no. 5805, July 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5805.

## 最後に

今回は「DAOなどのオンチェーンガバナンスで使われるトークンに「投票権の委任」や「過去の投票権の照会」を標準化する仕組みを提案している**ERC5805**」についてまとめてきました！
いかがだったでしょうか？

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