0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[ERC8056] ERC20トークンの実残高を変えずに表示数量だけを調整する仕組みを理解しよう!

0
Posted at

はじめに

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

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!

今回は「ERC20トークンの実残高を変えずに表示数量だけを調整する表示数量スケーリング拡張を定義するERC8056」についてまとめていきます。

ERC8056は、ERC20トークンの balanceOftransfer で扱う数量をそのまま保ちながら、ウォレットや取引所でユーザーに見せる数量だけを倍率で変えられるようにする提案です。
株式分割のように、保有者全員の見た目の数量を同じ比率で変えたい場面で、全保有者への追加mintや個別送金を避けられます。

以下にまとめられているものを解説しながらまとめていきます。

概要

ERC8056は、ERC20トークンに表示用の倍率を追加する拡張です。
提案名では、この仕組みを Scaled UI Amount Extension と呼んでいます。
トークンの実残高、総供給量、送金数量、DeFiコントラクト内の会計は raw amount として扱い、ウォレットや取引所の画面では raw amountuiMultiplier を掛けた数量を表示します。

ERC20は、トークンの残高取得、送金、承認を共通化するための標準です。
ERC8056はこの標準を置き換えず、表示用の関数とイベントを追加します。

ERC20については以下の記事を参考にしてください。

ERC8056の中心は、実残高と表示数量を分けることです。

ERC8056 の全体像

表示倍率が 2.0 の時、実残高が 100 のアカウントはウォレット上で 200 と表示できます。
一方で、送金や残高会計は 100 のまま扱います。
そのため、既存のERC20連携先は従来どおりの数量で処理できます。

動機

現実資産をトークン化する時、株式分割のようなイベントをオンチェーンで表現したくなる場面があります。
例えば、ある株式が2対1で分割されると、株主が持つ株数は見た目として2倍になります。
しかし、経済的な持分が突然2倍になるわけではありません。

ERC20だけで同じ見せ方を作るには、全保有者へ追加mintしたり、保有者ごとに残高を調整したりする必要があります。
保有者数が多いほどガスコストと運用負荷が大きくなり、取引所、レンディング、AMMを含む内部会計とも衝突しやすくなります。

ERC8056は、この問題を「実残高を変える」のではなく「表示倍率を変える」ことで解決します。
発行体は uiMultiplier を更新し、インテグレーターは表示時だけその倍率を使います。
これにより、トークンの経済的な会計を保ちながら、ユーザーに見える数量だけを分割後の数量へ合わせられます。

既存方式との違い

株式分割の表現には、リベース型トークン、ラッパートークン、交換レート型トークン、オフチェーン表示だけで処理する方法があります。
ただし、それぞれ課題があります。

方式 課題
リベース型トークン 残高そのものが変わるため、DeFiコントラクト側の前提と衝突しやすい
ラッパートークン 分割のたびに別トークンや変換処理が増え、運用が複雑になる
交換レート型トークン 表示側で追加計算が必要になり、ユーザーが直感的に理解しにくい
オフチェーン表示 標準化されていないため、ウォレットや取引所ごとに扱いが分かれる

ERC8056は、オンチェーンで倍率を公開し、表示側が同じ規則で数量を変換できるようにします。
倍率は表示用の層に閉じるため、ERC20の実残高を前提にした既存システムを壊しません。

仕様

ERC8056に準拠するコントラクトは、中心となる IScaledUIAmount と、予約中の表示倍率を公開する IScaledUIAmountNewUIMultiplier を実装します。
変換補助と表示用残高取得は任意拡張として定義されます。

IScaledUIAmount

IScaledUIAmount は、現在有効な表示倍率を返す中心インターフェースです。

interface IScaledUIAmount {
    event UIMultiplierUpdated(
        uint256 oldMultiplier,
        uint256 newMultiplier,
        uint256 effectiveAtTimestamp
    );

    event TransferWithUIAmount(
        address indexed from,
        address indexed to,
        uint256 amount,
        uint256 uiAmount
    );

    function uiMultiplier() external view returns (uint256);
}

uiMultiplier は18桁の小数精度で表現されます。
1e181.0 を表し、2e182.0 を表します。

uiMultiplier

function uiMultiplier() external view returns (uint256);

現在有効な表示倍率を返します。
実装が将来の倍率を予約している場合でも、反映時刻に到達するまでは古い倍率を返し、反映時刻以降は新しい倍率を返します。

  • 戻り値
説明
uint256 18桁精度の表示倍率。1e181.0

表示数量は、実残高と倍率から計算します。

uiAmount = (rawAmount * uiMultiplier) / 1e18

この式は、実残高を表示用数量に変換するだけです。
ERC20の balanceOftransfertransferFrom で使う数量は変わりません。

UIMultiplierUpdated

event UIMultiplierUpdated(
    uint256 oldMultiplier,
    uint256 newMultiplier,
    uint256 effectiveAtTimestamp
);

表示倍率が変更された時に発行されるイベントです。
ウォレット、取引所、インデクサーはこのイベントを見て、表示用キャッシュやユーザー通知を更新できます。

フィールド 説明
oldMultiplier 変更前の表示倍率
newMultiplier 変更後に使う表示倍率
effectiveAtTimestamp 新しい倍率が有効になるUnixタイムスタンプ

TransferWithUIAmount

event TransferWithUIAmount(
    address indexed from,
    address indexed to,
    uint256 amount,
    uint256 uiAmount
);

ERC20送金と同時に、表示用数量も通知したい実装で使うイベントです。
このイベントは任意で発行され、インターフェースIDには影響しません。

フィールド 説明
from 送信元アドレス
to 送信先アドレス
amount ERC20送金で使われた実数量
uiAmount 送金時点の表示倍率を使った表示用数量

このイベントがあっても、ERC20の Transfer イベントの意味は変わりません。
送金数量の正本は amount であり、uiAmount は画面表示や履歴表示の補助です。

IScaledUIAmountNewUIMultiplier

IScaledUIAmountNewUIMultiplier は、予約中の倍率と反映時刻を公開する必須拡張です。

interface IScaledUIAmountNewUIMultiplier {
    function newUIMultiplier() external view returns (uint256);
    function effectiveAt() external view returns (uint256);
}

表示倍率が突然変わると、ウォレット、取引所、インデクサーが更新を追いにくくなります。
そこで、準拠コントラクトは予約中の倍率と反映時刻を外部から読めるようにします。

表示倍率の予約と反映は以下です。

表示倍率の予約と反映

effectiveAt に到達するまでは、uiMultiplier は現在の倍率を返します。
到達後は、予約されていた newUIMultiplier が有効な表示倍率として使われます。
この間も、ERC20の実残高は変更されません。

newUIMultiplier

function newUIMultiplier() external view returns (uint256);

反映待ちの新しい表示倍率を返します。
すでに反映済みの状態では、現在有効な倍率と同じ値を返す実装になります。

  • 戻り値
説明
uint256 18桁精度の予約済み表示倍率

effectiveAt

function effectiveAt() external view returns (uint256);

newUIMultiplier が有効になるUnixタイムスタンプを返します。
ウォレットや取引所はこの値を使い、ユーザーに「いつ表示数量が変わるか」を事前に示せます。

  • 戻り値
説明
uint256 新しい倍率が有効になるUnixタイムスタンプ

IScaledUIAmountConversion

IScaledUIAmountConversion は、実数量と表示用数量をオンチェーンで相互変換する任意拡張です。

interface IScaledUIAmountConversion {
    function toUIAmount(uint256 rawAmount) external view returns (uint256);
    function fromUIAmount(uint256 uiAmount) external view returns (uint256);
}

この拡張を実装すると、ウォレットや他のコントラクトは変換式を自前で持たず、トークンコントラクトに変換結果を問い合わせられます。

toUIAmount

function toUIAmount(uint256 rawAmount) external view returns (uint256);

実数量を表示用数量へ変換します。

  • 引数
名前 説明
rawAmount uint256 ERC20の実数量
  • 戻り値
説明
uint256 表示倍率を反映した表示用数量

fromUIAmount

function fromUIAmount(uint256 uiAmount) external view returns (uint256);

表示用数量を実数量へ戻します。
ユーザーが画面上で 200 と入力した時、実際に transfer へ渡す数量を計算する用途で使えます。

  • 引数
名前 説明
uiAmount uint256 ユーザーに見せる表示用数量
  • 戻り値
説明
uint256 ERC20の transfer へ渡す実数量

IScaledUIAmountBalances

IScaledUIAmountBalances は、表示倍率を反映した残高と総供給量を返す任意拡張です。

interface IScaledUIAmountBalances {
    function balanceOfUI(address account) external view returns (uint256);
    function totalSupplyUI() external view returns (uint256);
}

balanceOfUI

function balanceOfUI(address account) external view returns (uint256);

指定したアカウントの表示用残高を返します。
内部的には balanceOf(account) に表示倍率を掛けた値になります。

  • 引数
名前 説明
account address 表示用残高を確認するアドレス
  • 戻り値
説明
uint256 表示倍率を反映した残高

totalSupplyUI

function totalSupplyUI() external view returns (uint256);

表示倍率を反映した総供給量を返します。
実際のERC20総供給量は totalSupply() の戻り値であり、totalSupplyUI() は表示用の値です。

  • 戻り値
説明
uint256 表示倍率を反映した総供給量

インターフェース検出

ERC8056に準拠するコントラクトは、ERC165によるインターフェース検出を実装します。
IScaledUIAmount に対応している場合、supportsInterface で対象のインターフェースIDに true を返します。

ERC165については以下の記事を参考にしてください。

提案で示されているインターフェースIDは以下です。

インターフェース ID
IScaledUIAmount 0xa60bf13d
IScaledUIAmountNewUIMultiplier 0xTBD
IScaledUIAmountConversion 0xTBD
IScaledUIAmountBalances 0xd890fd71

0xTBD は未確定の値として示されています。
実装や連携側は、提案の更新でIDが確定したかを確認する必要があります。

実装要件

準拠コントラクトが満たす要件は以下です。

要件 内容
ERC165対応 supportsInterface で対応インターフェースを検出できる
倍率の精度 uiMultiplier は18桁精度で表す
ERC20互換 balanceOftransfertransferFrom は実数量で動き続ける
イベント発行 表示倍率が変わる時に UIMultiplierUpdated イベントを発行する

倍率の更新方法は、提案が固定しません。
倍率を変更する主体は、発行体の管理者、ロールベースの権限、ガバナンス、オラクル連携から実装側で決めます。
ただし、誰でも倍率を変えられる設計にすると、ユーザーの表示残高を自由に操作できてしまいます。

補足

設計判断

ERC8056は、ERC20の中心関数を変更せず、表示専用の関数を分けて追加します。
balanceOftransfer の意味を変えると、既存のウォレット、取引所、DeFiコントラクトが同じトークンを違う数量として扱う恐れがあります。
そのため、ERC20の実数量を正本に残し、表示用の倍率だけを別インターフェースとして公開します。

倍率は18桁精度で表現します。
これはEthereumでよく使われる小数表現に合わせ、1e181.0 として扱うためです。
また、変換補助や表示用残高取得を任意拡張に分けることで、最小限の実装は uiMultiplier と予約中倍率の公開に集中できます。

テストケース

提案で想定されている確認観点は、初期倍率と株式分割の2つです。
初期状態では uiMultiplier1e18 であり、balanceOfbalanceOfUI が同じ数量を返すことを確認します。

株式分割の確認では、2対1の分割を表すために倍率を 2e18 に設定します。
この時、表示用残高は実残高の2倍になり、toUIAmountfromUIAmount が対応する数量を相互に変換できる必要があります。
一方で、ERC20の実残高、送金数量、総供給量は倍率変更だけでは変わりません。

表示と会計の境界

インテグレーターが最も注意すべき点は、表示用数量と実数量を混ぜないことです。
ウォレット、ポートフォリオ、価格表示では uiAmount を見せられます。
一方で、送金、残高会計、DeFiコントラクトとの連携では raw amount を使います。

扱う値を分ける

ユーザーが画面で 200 と見ていても、transfer に渡す実数量は 100 かもしれません。
そのため、送金画面では表示数量と実数量の対応を明確にし、内部処理ではERC20の実数量を一貫して使う必要があります。

ウォレットの扱い

ウォレットは、まず supportsInterfaceIScaledUIAmount 対応を確認します。
対応していないトークンは通常のERC20として表示します。

対応しているトークンでは、balanceOfuiMultiplier から表示用残高を計算できます。
IScaledUIAmountBalances に対応している場合は、balanceOfUI を使って表示用残高を直接取得できます。

ウォレットの処理は以下です。

  1. supportsInterface(0xa60bf13d) で対応を確認する
    未対応なら通常のERC20残高を表示します。
  2. balanceOf(account) で実残高を取得する
    送金や内部処理では、この値を正本として扱います。
  3. uiMultiplier() または balanceOfUI(account) で表示用残高を得る
    画面には表示用数量を出し、必要なら実数量も併記します。
  4. newUIMultiplier()effectiveAt() を確認する
    表示倍率の変更予定があれば、反映時刻をユーザーへ示します。

取引所の扱い

取引所は、入出金や内部台帳を実数量で記録します。
表示用数量を台帳の数量として保存すると、倍率更新のたびに過去の入出金履歴と現在残高の整合が崩れます。

画面表示では、内部台帳の実数量に現在の uiMultiplier を掛けます。
ユーザーが表示用数量で注文や出金を入力する場合は、fromUIAmount と同じ計算で実数量へ戻してから処理します。

DeFiコントラクトの扱い

DeFiコントラクトは、ERC20の balanceOftransfertransferFrom をそのまま使います。
利息計算、担保率、LP残高、プール内残高は実数量で扱う必要があります。

表示倍率をDeFiコントラクト内の会計へ混ぜると、同じトークンでもインテグレーターごとに計算結果が変わります。
ERC8056は表示のための拡張であり、オンチェーン会計を倍率で書き換える仕組みではありません。

互換性

ERC8056はERC20と互換性があります。
既存のERC20関数は実数量で動き続け、表示倍率は追加関数として提供されます。

この互換性により、ERC8056未対応のウォレットやDeFiコントラクトでも、通常のERC20としてトークンを扱えます。
ただし、未対応の画面では分割後の表示数量が反映されません。
そのため、発行体や取引所は表示倍率の存在をユーザーに伝え、対応済みUIでは表示用数量と実数量の違いを明確にする必要があります。

参考実装

提案の参考実装では、ERC20、ERC165、所有者権限、4つのインターフェースを組み合わせた ScaledUIToken コントラクトが示されています。
中心となる処理は、現在時刻と effectiveAt を比較して、現在の倍率か予約中の倍率を返す部分です。

uint256 private constant MULTIPLIER_DECIMALS = 1e18;
uint256 private _uiMultiplier = MULTIPLIER_DECIMALS;
uint256 private _newUIMultiplier = MULTIPLIER_DECIMALS;
uint256 private _effectiveAt = 0;

function uiMultiplier() public view returns (uint256) {
    if (block.timestamp >= _effectiveAt) {
        return _newUIMultiplier;
    }

    return _uiMultiplier;
}

この実装では、反映時刻に到達した後だけ予約中の倍率を返します。
_uiMultiplier_newUIMultiplier を分けることで、インテグレーターは現在の倍率と反映予定の倍率を同時に確認できます。

表示用数量への変換は、倍率を掛けて18桁精度で割り戻します。

function toUIAmount(uint256 rawAmount) public view returns (uint256) {
    return (rawAmount * uiMultiplier()) / MULTIPLIER_DECIMALS;
}

function fromUIAmount(uint256 uiAmount) public view returns (uint256) {
    return (uiAmount * MULTIPLIER_DECIMALS) / uiMultiplier();
}

toUIAmount は実数量から表示用数量を計算します。
fromUIAmount は表示用数量から実数量を計算します。
どちらもERC20の実残高を書き換えません。

倍率を更新する関数は、発行体やガバナンスの権限を持つアドレスに制限する必要があります。
参考実装では、将来時刻を指定してから UIMultiplierUpdated イベントを発行する流れになっています。

セキュリティ

倍率変更権限

表示倍率を不正に変更できると、ユーザーが自分の保有量を誤認します。
例えば、悪意ある管理者が倍率を 10.0 に変更すると、実残高は変わっていないのに画面上の数量だけが10倍に見えます。

実装では、倍率変更関数を所有者、ロール、ガバナンスのような信頼できる主体に限定する必要があります。
また、反映時刻を未来に置き、ウォレットや取引所が事前に通知できる余地を持たせる設計が重要です。

オーバーフロー

表示用数量は rawAmount * uiMultiplier で計算します。
rawAmount や倍率が大きい場合、乗算でオーバーフローするリスクがあります。

Solidity 0.8以降のオーバーフロー検出を使うか、乗算と除算の順序を安全に扱うライブラリを使う必要があります。
特に取引所やインデクサーがオフチェーンで計算する場合も、任意精度整数を使い、JavaScriptの通常の number で処理しないことが重要です。

ユーザーの誤認

表示用数量と実数量が異なるため、ユーザーは「ウォレットに見える数量」と「ERC20送金で動く数量」を混同しやすくなります。
ウォレットや取引所は、表示用数量を出す時に、必要に応じて実数量や倍率も確認できるようにします。

特に入出金画面、約定履歴、税務・会計向けCSVでは、どの数量が実数量で、どの数量が表示用数量なのかを明確に分ける必要があります。

オラクル依存

倍率更新を自動化する実装では、外部データやオラクルに依存します。
オラクルが停止したり、誤った倍率を返したりすると、ユーザーに表示される数量が誤ります。

自動更新を採用する場合でも、倍率の上限、下限、反映待ち時間、管理者による停止手段を用意することで、誤更新の影響を抑えられます。

最後に

今回は「ERC20トークンの実残高を変えずに表示数量だけを調整する表示数量スケーリング拡張を定義するERC8056」についてまとめてきました。

ERC8056は、現実資産トークンで必要になりやすい株式分割のような表示変更を、ERC20の実残高を変えずに扱うための提案です。
表示用倍率を標準化することで、ウォレット、取引所、DeFiコントラクトが「表示」と「会計」を分けて扱いやすくなります。
一方で、倍率変更権限やユーザーへの表示方法を誤ると、保有量の誤認につながるため、インテグレーター側の実装ルールも重要です。

他でも色々記事を書いているのでぜひよろしければ読んでいってください!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?