Appearance
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を基準として使用 |