# [ACP224] Subnet-EVMの動的手数料アップグレードの仕組みを理解しよう！

> ACP224は、Subnet-EVMに対してACP176を実装し、さらに新たなプリコンパイルコントラクトであるACP224FeeManagerPrecompileを導入することを目的としています。

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

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、Subnet-EVMに**ACP176**の動的手数料を取り込み、オンチェーンで手数料パラメータを変更できる新プリコンパイルを追加して、SAE対応とC-Chain同等の料金制御を可能にする仕組みを提案している**ACP224**についてまとめていきます！

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

https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/224-dynamic-gas-limit-in-subnet-evm/README.md

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

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

## 概要

**ACP224**は、**Subnet-EVM**に対して**ACP176**を実装し、さらに新たなプリコンパイルコントラクトである`ACP224FeeManagerPrecompile`を導入することを目的としています。
このプリコンパイルは、**ACP176**の有効化後にオンチェーン上で手数料パラメータを動的に設定・変更できる機能を提供します。
これは、現在`FeeManagerPrecompile`が**ACP176**導入前の仕組みとして提供している操作性を、**ACP176**以降の新しい手数料モデルに対応した形で維持するものです。

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

[\[ACP176\] AvalancheEVMの動的ガス調整の仕組みを理解しよう！](/articles/8e9ccb3b0316ca1ad7c8)

つまり、**ACP224**によってSubnet-EVMでも動的なガス制御と料金管理が可能となり、C-Chainと同様の挙動・メカニズムを持つ統一的な料金システムを利用できるようになります。

## 動機

#### 背景

**ACP176**は、EVMにおける動的手数料メカニズムを改良し、ターゲットとなるガス消費量をより正確に達成するための調整機構を導入しました。
さらに、ターゲットガス消費率そのものを動的に更新できる機能も追加されています。

現時点では、この**ACP176**はC-Chainのみに適用されています。
その理由は、多くのL1チェーン（レイヤー1ブロックチェーン）が、ジェネシス設定（genesisファイル）内の`FeeManagerPrecompile`と`FeeConfig`を通じて、手数料やガスターゲットを静的に制御する構成を採用しているためです。
しかし、従来の`FeeManagerPrecompile`は、**ACP176**で導入された新しい料金調整メカニズムとは互換性がないという制約がありました。

#### SAEとの関係

**ACP194**で提案されている**Streaming Asynchronous Execution（SAE）**を利用するためには、**ACP176**と整合したガスターゲットとガス容量の管理メカニズムが必要です。

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

[\[ACP194\] トランザクション順序と処理を分けるSAEの仕組みを理解しよう！](/articles/a5f0eaf1703a393a92f0)

SAEの前提条件として、以下の2つが明示されています。

- **秒単位で追加されるガス容量の量（known gas capacity added per second）**
- **最大ガス容量（maximum gas capacity）**

これらが明確に定義されていなければ、トランザクションの有効性判定や料金見積りの上限計算が困難になります。
しかし、現在のSubnet-EVMで採用されている**Windower Fee Mechanism**には、こうした固定的なガス容量レートの概念が存在しません。
そのため、ガス価格の上限を事前に算出することが難しく、静的解析モデルが成立しないという問題があります。

この課題を解決し、SAEをSubnet-EVM上で利用できるようにするには、**ACP176**で導入された動的ガスキャパシティ管理機構の統合が必須となります。

#### Subnet-EVMへの統合の意義

**ACP176**をSubnet-EVMに組み込むことには、以下の2つの大きな利点があります。

- **将来的なSAE利用に向けた技術的要件を満たす**
    SAEは、効率的で非同期的な実行モデルを実現するために設計されています。
    そのためには、ガス供給量やターゲット消費量を正確に追跡できるメカニズムが不可欠です。

- **C-Chainとの一貫性確保とメンテナンス負荷の削減**
    Subnet-EVMとC-Chainのガス・料金管理メカニズムを統一することで、Avalanche全体としてのコードベースの整合性と保守コストの削減を実現できます。
    これにより、複数の料金計算ロジックを個別に維持する必要がなくなり、将来的なアップデートやバグ修正が容易になります。

#### 新プリコンパイル`ACP224FeeManagerPrecompile`の役割

この提案のもう一つの柱は、**`ACP224FeeManagerPrecompile`という新しいプリコンパイルの導入**です。
このプリコンパイルは、ACP-176で導入された**動的料金モデルに対応したFee Manager**として機能し、**オンチェーンで手数料設定を柔軟に更新可能**にします。

これまで、料金パラメータの変更は主に**ジェネシス設定**を通じて静的に定義されていましたが、この新プリコンパイルによって次のようなことが可能になります。

* **管理者（Admin）権限を持つアカウント**が、ネットワーク稼働中に**料金パラメータを変更**できる
* **ガスターゲット値**や**調整率**などを、オンチェーン上で**動的に更新**できる
* これにより、**需要変動に応じた柔軟な料金制御**が可能になる

このように、**ACP176**と`ACP224FeeManagerPrecompile`の組み合わせは、Subnet-EVMの料金設計をより高度かつ柔軟にし、SAEなどの将来提案に対応可能なEVMプラットフォームを構築する上で欠かせないアップグレードとなります。

## 仕様

### ACP176のパラメータ

**ACP224**では、C-Chainで導入済みの**ACP176**と同じパラメータ体系を使用し、Subnetごと（チェーンごと）に値を設定できるようにします。
各記号の意味は以下です。

| Parameter | 説明                | C-Chainの設定    |
| :-------- | :---------------- | :------------ |
| $T$       | 1秒あたりの目標ガス消費量     | dynamic       |
| $R$       | 1秒あたりに追加されるガス容量   | 2*T           |
| $C$       | 最大ガス容量            | 10*T          |
| $P$       | 1秒あたりの最小目標ガス消費量   | 1,000,000     |
| $D$       | 目標ガス消費率の更新定数      | 2^25          |
| $Q$       | 目標更新の1ブロックあたり変化制限 | 2^15          |
| $M$       | 最低ガス価格            | 1×10^-18 AVAX |
| $K$       | 初期ガス価格更新定数        | 87*T          |

「`dynamic`」はブロック提案やプロトコルにより変化することを意味します。
$R=2T$ は、ガス価格が上昇する速度と下降する速度を同じに保つための設定です。
つまり、ネットワークの利用量が増えた場合に価格が上がるスピードと、利用量が減った場合に価格が下がるスピードが対称的（バランス良く）になるようにしています。

一方、$D$と$Q$は、それぞれ目標ガス消費量 $T$ をどれだけ細かく、どれだけの幅で変更できるかを決める値です。
これにより、各ブロックごとのガスターゲットの変動が急すぎず、かつ十分に調整できるようになります。
C-Chainではこの2つの値によって、ガスターゲットの変化が安定しつつも実際のネットワーク負荷にしっかり追随できるよう調整されています。

#### 旧Subnet-EVMの手数料設定パラメータ

**ACP176**導入以前、Subnet-EVMは以下の設定で手数料（**EIP1559**系のBaseFeeと「**ブロックガスコスト**」）を制御していました。用語の要点も併せて説明します。

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

[\[EIP1559\] Ethereumの新たなガス価格設定メカニズムを理解しよう！](/articles/ce77124940010280717b)

- **GasLimit**
    各ブロックで消費できる最大ガス量（ブロックガス上限）を直接指定します。
- **TargetBlockRate**
    手数料調整の基準となる目標ブロック生成間隔（秒）です。
    実際の生成がこれより速ければブロックガスコストを上げ、遅ければ下げます。
- **MinBaseFee**
    ブロックの**EIP1559**のBaseFeeの下限です。
    BaseFeeはトランザクションの最低ガス価格でもあるため、ネットワーク全体の最低ガス価格を決めます。
- **TargetGas**
    10秒間の合計ガス消費量の目標値を設定します（この中にはブロック生成時のガスコストも含まれます）。
    ネットワークの実際のガス消費量がこの目標値を上回った場合はBaseFeeを上げ、逆に下回った場合はBaseFeeを下げます。
- **BaseFeeChangeDenominator**
    「**実績−目標**」の差をどれくらいの割合でBaseFeeに反映するかを決める分母です。
    値が大きいほどBaseFeeの変化はゆっくりになり、値が小さいほど素早く変わります。
- **MinBlockGasCost** / **MaxBlockGasCost**
    ブロック生成に課すガスコストの下限／上限です（ブロック毎に必ず消費される固定ガスの幅）。
- **BlockGasCostStep**
    ブロック生成間隔のずれに応じて、ブロックガスコストをどの程度増減させるかを決める設定値です。
    もしブロックが目標どおりの間隔で生成された場合は、親ブロックと同じガスコストが適用されます。
    一方、目標より早く生成された場合は「**早かった秒数 × Step**」の分だけガスコストを増やし、遅れた場合は「**遅れた秒数 × Step**」の分だけガスコストを減らします。
    なお、この値を非常に大きく設定すると、ブロックを目標間隔より速く生成することが難しくなるため、
    実質的に「**TargetBlockRateより速い生成**」を抑制する働きを持ちます。
    （例：目標より2秒速くブロックが生成された場合、ガスコストは「**2 × Step**」だけ増加します）

#### Subnet-EVMにおけるACP176のパラメータ

**ACP176**の導入により、Subnet-EVMの以下の設定は不要になります。
`GasLimit`と`BaseFeeChangeDenominator`は**ACP176**の枠組みに置き換わります。
さらに`TargetBlockRate`、`MinBlockGasCost`、`MaxBlockGasCost`、`BlockGasCostStep`は**ACP226**（動的最小ブロック時間）により削除されます。

一方で、既存設定と**ACP176**の対応は以下のとおりです。

* `MinGasPrice`は**ACP176**の$M$に相当し、最低ガス価格を設定します。
    従来の`MinBaseFee`に近い効果です。
    現在のデフォルトは`25 * 10^-18`（25nAVAX）ですが、C-Chainに合わせて**1Wei**に変更されます。
* `TargetGas`は**ACP176**の$T$（1秒あたりの目標ガス消費量）です。
* `MaxCapacityFactor`は$C$の係数に相当し、最大ガス容量（=ブロックガス上限）を決めます。
    $C = MaxCapacityFactor \times T$で計算され、デフォルトは**10**（C-Chainと同じ）です。
* `TimeToDouble`は価格応答定数$K$の調整に使います。
    $K = (RMult \times TimeToDouble) / \ln 2$で求め、$RMult=2$とします。
    デフォルト`TimeToDouble=60秒`なら$K\approx 87T$（C-Chainと同じ）です。

最終的な既定値と可変性は以下の表のとおりです。

| Parameter | 説明 | 既定値 | 変更可否 |
| :-------- | :-- | :---- | :------ |
| $T$  | 1秒あたりの目標ガス消費量   | 1,000,000 | ✅ |
| $R$       | 1秒あたりに追加されるガス容量 | 2*T       | ❌ |
| $C$ | 最大ガス容量  | 10*T | ✅（`MaxCapacityFactor`、既定10） |
| $P$ | 最小目標ガス消費量/秒     | 1,000,000 | ❌ |
| $D$       | 目標更新の定数         | 2^25      | ❌ |
| $Q$       | 目標更新の変化制限       | 2^15      | ❌   |
| $M$       | 最低ガス価格          | 1Wei      | ✅  |
| $K$ | 価格応答定数 | 約87*T | ✅（`TimeToDouble`、既定60s）     |

$R=2T$の固定は、ガス価格が「**上がる速度**」と「**下がる速度**」を同じに保つためです。
$D$と$Q$は各ブロックで$T$をどれだけ動かせるか（およびその刻み）を決めます。
C-Chainと同一の既定値により、1ブロックあたり約$1/1024$の相対変更が可能です。
$P=1{,}000{,}000$gas/秒はどのEVM L1にとっても十分低い下限で、$T$は上方向に上限なく動的に増やせます。

#### ジェネシス設定（Genesis Configuration）

新しいチェーンでは、プリコンパイルを有効化しなくてもジェネシスのチェーン設定でパラメータを与えられます。
未指定の場合はC-Chainの既定が使われます。
設定例は以下のとおりです。

```json
{
  "...": "...",
  "acp224Timestamp": 0,
  "acp224FeeConfig": {
    "minGasPrice": 0,
    "maxCapacityFactor": 10,
    "timeToDouble": 60
  }
}
```

`acp224Timestamp`は**ACP224**を有効化する時刻（エポック秒を表す64bit整数）です。
`acp224FeeConfig`で$M$、$C$（係数）、$K$（を決める時間）を定義できます。

#### バリデータ設定による動的ガスターゲット

C-Chainの**ACP176**と同様に、各バリデータはノード設定でgas-targetを設定できます。
ブロックビルダーは、許容される更新幅に従いこの設定を用いて提案ブロックの `targetExcess` を計算して反映します。
これにより、オンチェーンのガスターゲットはバリデータの設定値に合わせて段階的に更新されます。

#### `ACP224FeeManagerPrecompile`による動的ガスターゲットと手数料設定

管理者アカウントでガスターゲットや手数料関連パラメータをオンチェーンで動的に変更したいL1向けに、任意で有効化できる新プリコンパイル`ACP224FeeManagerPrecompile`を追加します。
操作体系は、Subnet-EVMに存在する既存の`FeeManagerPrecompile`に類似します（アクセス制御は`IAllowList`準拠）。

```solidity
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "./IAllowList.sol";

/// @title ACP-224 Fee Manager Interface
/// @notice Interface for managing dynamic gas limit and fee parameters
/// @dev Inherits from IAllowList for access control
interface IACP224FeeManager is IAllowList {
    /// @notice Configuration parameters for the dynamic fee mechanism
    struct FeeConfig {
        uint256 targetGas;           // Target gas consumption per second
        uint256 minGasPrice;         // Minimum gas price in wei
        uint256 maxCapacityFactor;   // Maximum capacity factor (C = factor * T)
        uint256 timeToDouble;        // Time in seconds for gas price to double at max capacity
    }

    /// @notice Emitted when fee configuration is updated
    /// @param sender Address that triggered the update
    /// @param oldFeeConfig Previous configuration
    /// @param newFeeConfig New configuration
    event FeeConfigUpdated(address indexed sender, FeeConfig oldFeeConfig, FeeConfig newFeeConfig);

    /// @notice Set the fee configuration
    /// @param config New fee configuration parameters
    function setFeeConfig(FeeConfig calldata config) external;

    /// @notice Get the current fee configuration
    /// @return config Current fee configuration
    function getFeeConfig() external view returns (FeeConfig memory config);

    /// @notice Get the block number when fee config was last changed
    /// @return blockNumber Block number of last configuration change
    function getFeeConfigLastChangedAt() external view returns (uint256 blockNumber);
}
```

重要な挙動として、`setFeeConfig`での更新はトランザクションのAccept/Execute時ではなく、Settlement時に発効します（**ACP194**の実行ライフサイクルに準拠）。
これにより、トランザクションを受け入れる時の検証条件が、すべてのノードで常に同じ基準で維持されます。
手数料パラメータが途中で変更されても、ノード間で検証条件が不一致になることはありません。

またプリコンパイルは、最新のFeeConfigに加えて、導出済みの **$q$** （targetExcess計算で使う係数）と **$K$** をステートに保持します。
$q$はCorethのdesired target excess計算と同じ手順で`targetGas`から決定します。
$K$は本来$K = \frac{targetGas \cdot timeToDouble}{\ln 2}$で計算できますが、浮動小数点の誤差を避けるため2分探索で最も近い整数値を求めます。

`targetExcess`の決定フローは以下の図の通りです。

```mermaid
flowchart TD
    A[ACP-224 activated] --> B{Is ACP224FeeManager precompile active?}

    B -- Yes --> C[Use targetExcess from precompile storage at latest settled root]

    B -- No --> D{Is gas-target set in node chain config file?}
    D -- Yes --> E[Calculate targetExcess from configured preference and allowed update bounds]

    D -- No --> F{Does parent block have ACP176 fields?}
    F -- Yes --> G[Use parent block ACP176 gas target]
    F -- No --> H[Use MinTargetPerSecond]
```

#### 価格発見に関するACP176計算の調整

**ACP176**では、ブロックのガス価格は以下のように定義されています。

$$
gas_price ;=; M \cdot e^{,x/K}
$$

ここで、Mは最低ガス価格（下限）、Kは価格がどれくらいの速さで変化できるかを決める定数、xは現在の**excess**（目標ガス量に対して実際の利用がどれだけ多い／少ないかを表す内部指標）です。
需要が高いほどxは大きくなり、低いほど小さくなります。
価格はこのxを使って指数関数で計算されます。

`ACP224FeeManagerPrecompile`から $M$（minGasPrice）や $K$（timeToDouble から導出）を変更する場合、$x$ も同時に更新して変更前後で現在のガス価格が急に変わらないように調整します。

#### $M$を変更した場合

$M$を $M_0$ から $M_1$ に変更する場合、理論上は以下の式で $x$ を補正すれば変更前と同じガス価格を維持できます。

$$
x_1 = \ln\left(\frac{M_0}{M_1}\right) \cdot K + x_0
$$

ただし、この計算には浮動小数点演算が必要となり、EVM上では誤差が生じる可能性があります。
そのため実装上は、2分探索を用いて「**$M_1$ を使って計算したガス価格が、変更前の価格以上となる最小の非負整数 $x_1$**」を求めます。

この手法により、以下のような挙動が保証されます。

* 最低ガス価格を下げる、または現行価格以下に設定する場合
    ガス価格は即座に変化しません。
* 最低ガス価格を現行価格より高く設定する場合
    ガス価格はその新しい下限まで即座に引き上げられます。

つまり、$M$ の変更は「**価格の下限を調整する操作**」であり、現行のガス価格がその下限より高い場合は維持され、下限より低い場合は新しい最低価格に合わせて引き上げられます。

#### $K$を変更した場合

$K$を $K_0$ から $K_1$ に変更する場合は、以下の式で $x$ を再計算します。

$$
x_1 = x_0 \cdot \frac{K_1}{K_0}
$$

この補正により、変更後もガス価格そのものは変わりません。
$K$ は「**ガス価格が変化する速度**」を決める定数のため、値を変えてもその瞬間の価格には影響せず、以降の価格変動のスピードだけが変わるという挙動になります。

#### パラメータ計算と整合性

$q$（`targetExcess` の導出に用いる係数）および $K$ は、`targetGas` と `timeToDouble` の値から算出されます。
この計算手順は C-Chainと同一であり、EVMの整数演算の制約を踏まえて、実際には 2分探索（二分法）により最も近い整数値を求めます。

すべての `setFeeConfig` による設定変更は、トランザクションのSettlement（確定）時に有効化されます。
これにより、トランザクションを受け入れる段階での検証基準が、すべてのノードで統一され、手数料パラメータの変更によってノード間で整合性が崩れることはありません。
これは **SAE（ACP194）** におけるトランザクションキューの一貫性を確保する上で重要な要素です。

これらの設計により、Subnet-EVMはC-Chainと同様の**ACP176**ベースの動的手数料モデルを採用し、`ACP224FeeManagerPrecompile` を利用してネットワーク稼働中でも安全かつ精密に手数料設定を調整できるようになります。

## 互換性

**ACP224**を有効化するには、まず新しい手数料メカニズム（**ACP224**のフィーロジック）をネットワークアップグレードで有効化する必要があります。
新しい手数料マネージャプリコンパイル（**ACP224FeeManagerPrecompile**）を使う場合は、これとは別に追加のアクティベーションが必要です。
プリコンパイルのアクティベーションは、必ず**ACP224**（フィーメカニズム）のアクティベーション後に行ってください。
プリコンパイルは**ACP224**の手数料更新ロジックに依存して動作するためです。

**ACP224**のメカニズムを有効化すると、従来の手数料メカニズムと従来のFeeManagerプリコンパイルは無効化されます。
旧ロジックと新ロジックが同時に有効になって挙動が曖昧になることを防ぐためです。
すでに稼働中のネットワーク向けには、アクティベーション時刻と**ACP176**の構成パラメータを上書き指定できるネットワークアップグレード用オーバーライドが導入されます。

これらのアップグレードは現時点では任意です。
ただし**ACP194**（SAE）の導入に伴い、SAEを利用するには**ACP224**の有効化が必須になります。
**ACP224**を有効化しないネットワークは**ACP194**を利用できません。

```mermaid
flowchart TD
    A[Step1:ACP224の手数料メカニズムを有効化] --> B[旧フィー機構と旧FeeManagerを無効化]
    B --> C{新FeeManagerを使うか}
    C -- はい --> D[Step2:ACP224FeeManagerPrecompileを有効化]
    C -- いいえ --> E[プリコンパイルなしで運用継続]
    %% 注意: DはAの後でのみ有効化可
```

## セキュリティ

基本的なセキュリティ前提は**ACP176**と同じです。
一方で、**ACP224FeeManagerPrecompile**で動的に変更可能なパラメータが公開されるため、設定ミスのリスクが追加されます。
設定を誤ると、ネットワークがDoS攻撃に弱くなる、あるいは不必要に高い手数料が発生するといった問題につながり得ます。
したがって、どの値をいつ反映するかの運用管理や、反映順序（Settlementで有効化されること）を含む手順管理が重要です。

## 未解決の論点

下記の点は未確定です。

| 質問 |
| :-- |
| `ACP224FeeManager`プリコンパイルを有効化したときに旧プリコンパイルを自動で無効化すべきか、それとも別のアップグレードで手動無効化を求めるべきか。 |
| ジェネシス/チェーン設定に`targetGas`を任意項目として持たせ、チェーン設定がバリデータ設定より優先されることを示すシグナルとして使うべきか。       |
| 上記に関連し、`ACP224FeeManager`プリコンパイル側に`targetGas`の制御をバリデータに委ねるトグルを用意すべきか。             |

## 最後に

今回は「Subnet-EVMに**ACP176**の動的手数料を取り込み、オンチェーンで手数料パラメータを変更できる新プリコンパイルを追加して、SAE対応とC-Chain同等の料金制御を可能にする仕組みを提案している**ACP224**」についてまとめてきました！
いかがだったでしょうか？

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