#はじめに
TITANの価値が1日で約40億分の1になった(40億円が1円になる。。。)ということで、何が起こったのかまとめます。ブロックチェーンエンジニアの自分としては、いろいろと学ぶことが多くありました。
#ステーブルコイン
その前に、ステーブルコインについて最低限の知識が必要です。
仮想通貨(暗号資産)は価値変動が激しく、ボラリティが高いです。
例えば、2021年4月にビットコインは700万円ほどになりましたが、その一か月後には、400万円ほどになっています。
このように、ドルや円のような法定通貨と比較すると価格変動が激しくその実用性には課題が残ります。
この問題を解決するのが、ステーブルコインです。ステーブル(Stable)とは「安定している」といった意味で、「安定したコイン(仮想通貨)」という意味合いです。
ステーブルコインとして、価格を安定させる手法は、下記3つです。
-
法定通貨担保型
ドルや円といった法定通貨を担保にしたステーブルコインです。 法定通貨との交換比率を固定化することで、価格の安定化を目指しているといえます。 -
仮想通貨担保型
DAIが有名ですが、特定の仮想通貨を担保に発行されます。担保となった仮想通貨に価格を連動させることでボラティリティを抑える仕組みになっています。 -
無担保型
法定通貨担保型、仮想通貨担保型と違い、担保されるものはありません。
特定のアルゴリズムで通貨量を調整することで、ボラティリティを抑える仕組みとなっています。
ステーブルコイン:IRON
さて、今回問題となったステーブルコインのIRONはどういう性質をもつかというと、**IRONは自身の価値を担保するために、ステーブルコインの「USD Coin(USDC)」と、独自のトークンであるTITANを組み合わせています。**例えば「1IRONにつき75セントのUSDCと25セントのTITAN」のようになります。
償還ができない!?
多くのユーザーがIRONを清算し、それに伴ってTITANが売られたことで、TITANの価格が落ち始めました。そして、さらに多くの保有者がTITANの売却に走ることで、ずんずん下がりました。
ただ、IRONは「1IRONにつき75セントのUSDCと25セントのTITAN」と償還できるようにしていました。IRONの償還部分を担うコードは下記になっています。(コントラクトのソースへ飛ぶ)
function redeem(
uint256 _dollar_amount,
uint256 _share_out_min,
uint256 _collateral_out_min
) external {
require(redeem_paused == false, "Redeeming is paused");
(, uint256 _share_price, , , uint256 _ecr, , , uint256 _redemption_fee) =
ITreasury(treasury).info();
uint256 _collateral_price = getCollateralPrice();
require(_collateral_price > 0, "Invalid collateral price");
require(_share_price > 0, "Invalid share price");
uint256 _dollar_amount_post_fee =
_dollar_amount - ((_dollar_amount * _redemption_fee) / PRICE_PRECISION);
uint256 _collateral_output_amount = 0;
uint256 _share_output_amount = 0;
if (_ecr < COLLATERAL_RATIO_MAX) {
uint256 _share_output_value =
_dollar_amount_post_fee - ((_dollar_amount_post_fee * _ecr) / PRICE_PRECISION);
_share_output_amount = (_share_output_value * PRICE_PRECISION) / _share_price;
}
if (_ecr > 0) {
uint256 _collateral_output_value =
((_dollar_amount_post_fee * _ecr) / PRICE_PRECISION) / (10**missing_decimals);
_collateral_output_amount =
(_collateral_output_value * PRICE_PRECISION) /
_collateral_price;
}
// Check if collateral balance meets and meet output expectation
uint256 _totalCollateralBalance = ITreasury(treasury).globalCollateralBalance();
require(_collateral_output_amount <= _totalCollateralBalance, "<collateralBalance");
require(
_collateral_out_min <= _collateral_output_amount &&
_share_out_min <= _share_output_amount,
">slippage"
);
if (_collateral_output_amount > 0) {
redeem_collateral_balances[msg.sender] =
redeem_collateral_balances[msg.sender] +
_collateral_output_amount;
unclaimed_pool_collateral = unclaimed_pool_collateral + _collateral_output_amount;
}
if (_share_output_amount > 0) {
redeem_share_balances[msg.sender] =
redeem_share_balances[msg.sender] +
_share_output_amount;
unclaimed_pool_share = unclaimed_pool_share + _share_output_amount;
}
last_redeemed[msg.sender] = block.number;
// Move all external functions to the end
IDollar(dollar).poolBurnFrom(msg.sender, _dollar_amount);
if (_share_output_amount > 0) {
_mintShareToCollateralReserve(_share_output_amount);
}
}
この中で**「require(_share_price > 0, “Invalid share price”);」**の部分ですが、_share_priceはTITANの価格が代入されますが、TITANの価格が0ドルになったことで、償還できないという事態に陥りました。
ブロックチェーンの開発をしている身としては、価格が0になることなんてあるのかよ。という思いもありますが、実際にこういうことが起こりうるんですね。
考察
ステーブルコインといいつつ、こういった問題は起きるんですね。結局、法定通貨を担保としないと、なかなか価格は安定しないのではないかな?
エンジニアの観点からすると、コントラクトでこうした問題を見つけると、アップデートできるようにしておかないといけないなと感じました。