# [ERC2917] ステーキング報酬をオンチェーンで計算する仕組みを理解しよう！

> ERC2917は、ステーキング報酬をチェーン上で計算する提案です。ERC2917では、ユーザーの報酬を「有効な担保量（＝生産性）×時間」の積をもとにリアルタイムで算出する仕組みを導入しています。報酬の計算式は次の通りです。

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

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、ステーキング報酬をオンチェーン上で計算する仕組みを提案している**ERC2917**についてまとめていきます！

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

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

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

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

## 概要

**ERC2917**は、ステーキング報酬をチェーン上で計算する提案です。
**ERC2917**
では、ユーザーの報酬を「有効な担保量（＝生産性）×時間」の積をもとにリアルタイムで算出する仕組みを導入しています。
報酬の計算式は次の通りです。

```
Reward_U = Σ (Δp_i / ΔP_i) × ΔG_i
```

![erc-reward-formula.png](/images/qiita-7f2a0c96-60ae-44e6-91c7-b4aa700fb683.webp)

* $Δp\_i$ は、特定の期間 `ti-1` から `ti` におけるユーザー U の個別生産性の変化量
* $ΔP\_i$ は、同期間における全体（グローバル）の生産性の変化量
* $ΔG\_i$ は、同期間における総生産量（報酬プールなど）の変化量

この式は、誰かが途中で抜けたり、遅れて参加したりしても不公平が生まれないように設計されています。
つまり、期間中の貢献度に基づいて報酬が決まります。
また、この計算式はSolidityで簡略化され、あらゆるDeFiプロジェクトで再利用可能な汎用的な設計がなされています。

スマートコントラクトは以下のイベント時に計算をトリガーします。

* ユーザーの生産性（担保量など）が増減したとき
* ユーザーが資金を引き出したとき

#### 動機

多くのDeFiプロジェクトにおける課題は、報酬分配の仕組みが非効率または非透明であることです。
現在採用されている主な方法は以下の2つです。

- **全ユーザーがコントラクトから退出してから報酬を分配する方式**

完全オンチェーンであるが、報酬の受け取りが遅く、担保を外すことで不利益を被る場合がある。

- **オンチェーンデータを収集し、オフチェーンで計算してから結果を反映する方式**

セミ・ディセントラライズド（半分中央集権）で、計算の公平性や透明性が担保されない。

これによりユーザーは以下の情報を把握しにくい状態にあります。

* 自分が受け取れる利息の金額
* その利息がどうやって計算されたのか
* 自分の貢献度が全体と比べてどの程度か

**ERC2917**を標準化することで、各DeFiプロジェクトの利息生成メカニズムを抽象化でき、ウォレットなどのアプリケーションが各プロジェクトの指標を取得しやすくなり、ユーザーにとっても理解しやすくなります。

## 仕様

### インターフェース

```js:IERC2917.sol
interface IERC2917 is IERC20 {

    /// @dev This emit when interests amount per block is changed by the owner of the contract.
    /// It emits with the old interests amount and the new interests amount.
    event InterestRatePerBlockChanged (uint oldValue, uint newValue);

    /// @dev This emit when a users' productivity has changed
    /// It emits with the user's address and the value after the change.
    event ProductivityIncreased (address indexed user, uint value);

    /// @dev This emit when a users' productivity has changed
    /// It emits with the user's address and the value after the change.
    event ProductivityDecreased (address indexed user, uint value);

    
    /// @dev Return the current contract's interests rate per block.
    /// @return The amount of interests currently producing per each block.
    function interestsPerBlock() external view returns (uint);

    /// @notice Change the current contract's interests rate.
    /// @dev Note the best practice will be restrict the gross product provider's contract address to call this.
    /// @return The true/false to notice that the value has successfully changed or not, when it succeed, it will emite the InterestRatePerBlockChanged event.
    function changeInterestRatePerBlock(uint value) external returns (bool);

    /// @notice It will get the productivity of given user.
    /// @dev it will return 0 if user has no productivity proved in the contract.
    /// @return user's productivity and overall productivity.
    function getProductivity(address user) external view returns (uint, uint);

    /// @notice increase a user's productivity.
    /// @dev Note the best practice will be restrict the callee to prove of productivity's contract address.
    /// @return true to confirm that the productivity added success.
    function increaseProductivity(address user, uint value) external returns (bool);

    /// @notice decrease a user's productivity.
    /// @dev Note the best practice will be restrict the callee to prove of productivity's contract address.
    /// @return true to confirm that the productivity removed success.
    function decreaseProductivity(address user, uint value) external returns (bool);

    /// @notice take() will return the interests that callee will get at current block height.
    /// @dev it will always calculated by block.number, so it will change when block height changes.
    /// @return amount of the interests that user are able to mint() at current block height.
    function take() external view returns (uint);

    /// @notice similar to take(), but with the block height joined to calculate return.
    /// @dev for instance, it returns (_amount, _block), which means at block height _block, the callee has accumulated _amount of interests.
    /// @return amount of interests and the block height.
    function takeWithBlock() external view returns (uint, uint);

    /// @notice mint the available interests to callee.
    /// @dev once it mint, the amount of interests will transfer to callee's address.
    /// @return the amount of interests minted.
    function mint() external returns (uint);
}
```

#### `InterestRatePerBlockChanged`

```javascript
event InterestRatePerBlockChanged (uint oldValue, uint newValue);
```

ブロックごとの利息量が変更された時に発行されるイベント。
コントラクトのオーナーが利息の発生率（利率）を変更した際に、変更前と変更後の値とともに発行されるイベントです。

**パラメータ**

* `oldValue`
    * 変更前の利息量。
* `newValue`
    * 変更後の利息量。

#### `ProductivityIncreased`

```javascript
event ProductivityIncreased (address indexed user, uint value);
```

ユーザーの生産性が増加した時に発行されるイベント。
ユーザーがステーキング量を増加させるなどして、生産性が高まった際にその変化を通知するためのイベントです。

**パラメータ**

* `user`
    * 対象ユーザーのアドレス。
* `value`
    * 増加後の生産性の値。

#### `ProductivityDecreased`

```javascript
event ProductivityDecreased (address indexed user, uint value);
```

ユーザーの生産性が減少した時に発行されるイベント。
ユーザーがステーキング量を減らすなどして、生産性が下がった際にその変化を通知するためのイベントです。

**パラメータ**

* `user`
    * 対象ユーザーのアドレス。
* `value`
    * 減少後の生産性の値。

#### `interestsPerBlock`

```javascript
function interestsPerBlock() external view returns (uint);
```

ブロックごとの利息量を取得する関数。
現在のブロックあたりの利息生成量を返す関数です。
ユーザー報酬の基本となる利率の確認に使用されます。

**戻り値**

* `uint`
    * 現在のブロックあたりの利息量。

#### `changeInterestRatePerBlock`

```javascript
function changeInterestRatePerBlock(uint value) external returns (bool);
```

ブロックごとの利息量を変更する関数。
報酬生成率を設定または変更する関数です。
推奨される実装では、報酬供給元コントラクトからのみ実行できるよう制限します。
成功時には`InterestRatePerBlockChanged`イベントが発行されます。

**引数**

* `value`
    * 新しい利息量。

**戻り値**

* `bool`
    * 変更が成功したかどうか。

#### `getProductivity`

```javascript
function getProductivity(address user) external view returns (uint, uint);
```

ユーザーの生産性と全体の生産性を取得する関数。
指定したユーザーの現在の生産性（ステーク量など）と、全体の生産性を取得できます。
ユーザーが何もしていない場合は0が返されます。

**引数**

* `user`
    * 対象ユーザーのアドレス。

**戻り値**

* `uint`
    * ユーザーの生産性。
* `uint`
    * 全体の生産性。

#### `increaseProductivity`

```javascript
function increaseProductivity(address user, uint value) external returns (bool);
```

ユーザーの生産性を増加させる関数。
ステーキングなどにより、ユーザーの報酬計算に影響する生産性を増加させます。
呼び出し元は、信頼されたコントラクトに制限するのが望ましいです。

**引数**

* `user`
    * 対象ユーザーのアドレス。
* `value`
    * 増加させる値。

**戻り値**

* `bool`
    * 成功したかどうか。

#### `decreaseProductivity`

```javascript
function decreaseProductivity(address user, uint value) external returns (bool);
```

ユーザーの生産性を減少させる関数。
ユーザーのステーキング量を減らす場合などに使用され、生産性を減少させます。
これも信頼されたコントラクトのみが実行できるようにすべきです。

**引数**

* `user`
    * 対象ユーザーのアドレス。
* `value`
    * 減少させる値。

**戻り値**

* `bool`
    * 成功したかどうか。

#### `take`

```javascript
function take() external view returns (uint);
```

現在のブロック時点で受け取れる報酬を取得する関数。
ブロック番号に基づいて、呼び出し元ユーザーが現在までに受け取れる利息（報酬）を取得できます。
あくまで確認用で、実際のミントは`mint`で行います。

**戻り値**

* `uint`
    * 現在までに受け取れる利息。

#### `takeWithBlock`

```javascript
function takeWithBlock() external view returns (uint, uint);
```

報酬と対応するブロック番号を同時に取得する関数。
`take`関数と同様に利息を取得しますが、どのブロック番号までの報酬かを併せて返します。

**戻り値**

* `uint`
    * 報酬量。
* `uint`
    * 報酬計算の対象となったブロック番号。

#### `mint`

```javascript
function mint() external returns (uint);
```

蓄積された利息（報酬）を実際に受け取る関数。
`take`で確認できる利息を実際にユーザーのアドレスにミント（配布）する処理です。
Mint後は対象分の利息が消費されます。

**戻り値**

* `uint`
    * ミントされた利息量。

## 引用

Tony Carson <tony.carsonn@gmail.com>, Mehmet Sabir Kiraz <m.kiraz@gmail.com>, Süleyman Kardaş <skardas@gmail.com>, "ERC-2917: Staking Reward Calculation [DRAFT]," Ethereum Improvement Proposals, no. 2917, August 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2917.

## 最後に

今回は「ステーキング報酬をオンチェーン上で計算する仕組みを提案している**ERC2917**」についてまとめてきました！
いかがだったでしょうか？

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