Skip to content
On this page

L1-6: Rebasing Token(弾力的供給トークン)

1. 課題

AMPL (Ampleforth) に類似した弾力的供給ERC20トークンを作成します。総供給量はrebaseによって調整可能で、すべての保有者の残高が比例的に変化します。

例:総供給 10M → rebase(-9M) → 総供給 1M、各保有者の残高は元の1/10になります。

2. 背景

AMPLの目標は「購買力が安定した」通貨を作り出すことです。供給量の調整によって価格目標を達成します:

  • 価格 > $1 → 正のrebase(供給増加、保有者の残高が増加)
  • 価格 < $1 → 負のrebase(供給減少、保有者の残高が減少)

技術的には、すべての保有者を走査して残高を変更することは不可能(Gas = ∞)であり、間接的なアプローチが必要です。

3. 解決策

コアメカニズム:GONs + スケーリング係数

内部表現 (GONs):    _gonBalances[addr]     // rebaseで変化しない
スケーリング係数:    _gonsPerFragment       // rebase時に変化
外部表現 (表示値):   balanceOf = _gonBalances / _gonsPerFragment

Rebase操作

solidity
function rebase(int256 amount) external onlyOwner {
    uint256 currentTotal = totalSupply();  // GONs / _gonsPerFragment
    uint256 newTotal = currentTotal + amount;  // (正のrebase) または currentTotal - decreaseBy (負のrebase)
    _gonsPerFragment = _gonTotalSupply / newTotal;
    // _gonBalancesを一切変更する必要なし!
}

なぜ機能するのか

  • 正のrebase: 総供給 10M → 11M、_gonsPerFragmentは1e18から_gonTotalSupply / 11Mに変化(小さくなる)
  • balanceOf = _gonBalances / _gonsPerFragment(分子は不変、分母が小さくなる = 結果が大きくなる)

4. 遭遇した落とし穴

4.1 transfer/transferFromの金額を変換する必要がある

ユーザーが渡すamount表示値であり、内部的にGONsに変換する必要があります:

solidity
function transfer(address to, uint256 amount) public override returns (bool) {
    uint256 gonAmount = amount * _gonsPerFragment;
    _transferGon(msg.sender, to, gonAmount);
    emit Transfer(msg.sender, to, amount);
    return true;
}

4.2 allowanceはrebaseの影響を受けない

OZ ERC20のallowanceは表示値を保存しており、GONsではありません。transferFromはこれを正しく処理する必要があります:

solidity
_spendAllowance(from, msg.sender, amount); // 表示値でallowanceを消費
_transferGon(from, to, gonAmount);         // GONsで転送

4.3 整数除算による精度損失

複数回のrebase後、_gonsPerFragmentに誤差が蓄積する可能性があります。_gonTotalSupply / newTotalには丸め誤差があります。

5. 落とし穴の原因

5.1

transferが変換を行わない場合、ユーザーが100トークンを送金しても実際には100 GONsしか転送されません。しかし_gonsPerFragmentは > 1 または < 1 の可能性があり、実際の金額が期待値と大きく異なる結果になります。

5.2

allowanceのセマンティクス:ユーザーが承認するのは「表示値」であり、GONsではありません。もしallowanceもGONsでスケーリングされると、承認量がrebaseに応じて変動してしまいます(想定外の動作)。

5.3

Solidityの整数除算は切り捨てです。rebaseごとに丸め誤差が蓄積され、balanceOfの合計がtotalSupplyと一致しなくなる可能性があります。

6. 落とし穴の解決方法

  • すべての外部インターフェース(transfer/transferFrom/approve)のパラメータとイベントには表示値を使用
  • 内部ストレージと計算にはGONsを使用
  • allowanceの保存と消費には表示値を使用(_spendAllowance呼び出し時に表示値を渡す)
  • 精度損失に対して:高精度(1e18を初期_gonsPerFragmentとして)を使用し、丸めの影響を低減

7. 技術的ポイント

ポイント説明
GONsパターン内部固定単位 + 外部可変比率
AMPLの原理弾力的供給による購買力の安定化
ERC20オーバーライドbalanceOf / totalSupply / transfer / transferFrom をすべて再実装
Allowanceの分離allowanceはrebaseの影響を受けない
精度処理18桁精度 = 1e18を基準として使用

Built with AiAda