みなさんこんにちは!スマートコントラクトの開発や、ブロックチェーンのセキュリティの世界へようこそ。
今回は、DeFi(分散型金融)の世界でたびたび話題になる「価格操作攻撃(オラクル・マニピュレーション)」について、一歩ずつ優しく紐解いていきたいと思います。「なんだか難しそうな名前だな…」と感じるかもしれませんが、大丈夫です!身近な例えを交えながら、攻撃が起きる仕組みと、それを防ぐための大切なポイントを一緒に見ていきましょう。
—
1. 身近な例えで理解する「価格操作攻撃」
いきなり難しいブロックチェーン用語を使うのはお休みして、まずは私たちの身近な世界に置き換えて考えてみましょう。
想像してみてください。あなたは近所の商店街で、お米を売る小さなお店を営んでいます。そのお店では、お米の値段を決めるルールとして「たった一軒の隣町の小さなお店が、今まさに売りに出しているお米の値段」をそのまま参考にするルールにしていました。とてもシンプルで効率的ですよね。
ある日、そこに「怪盗フラッシュローン」という大泥棒が現れました。泥棒は銀行から数秒間だけ大金(例えば1億円)を「一瞬だけ」借りて、その隣町の小さなお店に走ります。そして、そのお店にあるお米をすべて、自分の持っている大金で買い占めてしまいました。
お店からお米がなくなると、どうなるでしょうか?
経済の基本ルール通り、品薄になった隣町のお店では、お米の値段が一時的に跳ね上がり、「1袋1,000万円!」というトンデモナイ価格になってしまいます。
さて、ここであなたの店に戻りましょう。あなたの店は「隣町のいまの値段を参考にする」というルールでしたよね。隣町のお米が1袋1,000万円になっているのを見たあなたのシステムは、「うちで売っているお米も、いまや1袋1,000万円の価値があるんだ!」と勘違いしてしまいます。
泥棒は、あなたの店に「価値が跳ね上がったボロボロの価値のないもの(実際は大して価値のないトークン)」をほんの少しだけ差し出し、「お前の店のお米は1袋1,000万円なんだから、この価値のないものと交換しろよ!」と迫り、あなたの店にある全財産を持ち去ってしまいました。これが、数秒の間にすべてが行われる「価格操作攻撃」の恐ろしい手口です。
—
2. DeFiの世界で何が起きているのか?
ブロックチェーンの世界に置き換えてみましょう。DeFiの世界では、暗号資産(仮想通貨)の価格を知るために「価格オラクル(Oracle)」と呼ばれる仕組みを使います。
ここで一番やってはいけないアンチパターンが、「たった一つのDEX(分散型取引所)の、その瞬間のプール残高(スポット価格)をそのままオラクルとして信頼してしまうこと」です。
DEXの価格は、プールのトークン比率(流動性)によって簡単に揺らいでしまいます。先ほどの例のように、攻撃者は「フラッシュローン(担保なしで一瞬だけ巨額の資金を借りられる仕組み)」を悪用して、プール内の価格を一時的にめちゃくちゃに歪め、その歪んだ価格のままスマートコントラクトから別の資金をだまし取るのです。
—
3. 対策:どうやって身を守ればいいの?
「じゃあ、どうやって自分のコントラクトを守ればいいの?」と思いますよね。安心してください。ちゃんとした防犯対策が用意されています。
一番の基本は、「一箇所だけの、その瞬間の価格を信じないこと」です。
1. TWAP(時間加重平均価格)を使う:
ある一瞬の価格を見るのではなく、「過去一定時間(例えば過去30分間)の平均価格」を参照するようにします。これなら、泥棒が数秒間だけ無理やり価格を吊り上げても、平均値にならされるため、攻撃のダメージを大幅に薄めることができます。
2. 信頼できる外部オラクル(Chainlinkなど)を導入する:
世界中の複数の取引所からデータを集約し、不正な操作が極力できない堅牢な価格配信サービスを利用するのが、現在の業界標準の防犯対策です。
—
4. 実装例:危険なコードと安全なコードを見比べてみよう
それでは、実際のSolidity(スマートコントラクトを書くプログラミング言語)のコードを見ながら、何が危険で、どう直すべきなのかを確認してみましょう。
❌ 危険なコード(単一のDEXのスポット価格をそのまま使っている例)
次のコードは、UniswapなどのDEXから「いまこの瞬間の価格」をそのまま取得して処理を行っている、非常に危険な状態の例です。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IUniswapV2Pair {
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
}
contract VulnerableLendingProtocol {
address public dexPair;
constructor(address _dexPair) {
dexPair = _dexPair;
}
// 危険:その瞬間のプール残高から直接価格を計算している
function getDangerousPrice() public view returns (uint256) {
(uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(dexPair).getReserves();
// 単純な割り算は、フラッシュローンによる価格操作(リザーブの改変)に非常に弱い
require(reserve0 > 0, "Reserve zero");
return uint256(reserve1) * 1e18 / uint256(reserve0);
}
}
この実装だと、先ほどお話した通り、攻撃者にフラッシュローンで reserve0 や reserve1 のバランスを一時的にめちゃくちゃにされ、算出される価格を意のままに操られてしまいます。
—
⭕ 安全な対策コード(TWAPや信頼できるオラクルを意識する)
安全な設計では、価格の急激な変動を受け付けない仕組み(TWAPやChainlinkなどのセキュアなオラクル)を組み込みます。ここでは、Chainlinkの価格フィードを安全に参照する基本的なアプローチを例に見てみましょう。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// Chainlinkの価格フィード用のインターフェース
interface AggregatorV3Interface {
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
contract SecureLendingProtocol {
AggregatorV3Interface public priceFeed;
// コンストラクタで信頼できるオラクルアドレスを設定します
constructor(address _priceFeedAddress) {
priceFeed = AggregatorV3Interface(_priceFeedAddress);
}
// 安全:複数の価格を集約したオラクルから最新かつ検証された価格を取得する
function getSecurePrice() public view returns (int256) {
(
uint80 roundId,
int256 price,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
) = priceFeed.latestRoundData();
// データの鮮度や有効性をしっかりとチェックします(古いデータや異常値の排除)
require(price > 0, "Invalid price from oracle");
require(updatedAt > 0, "Round not complete");
require(answeredInRound >= roundId, "Stale price data");
return price;
}
}
このように、生のプールデータに直接依存するのではなく、分散化され検証されたオラクルを利用したり、時間経過による平均値(TWAP)を活用することで、フラッシュローンを使った一撃必殺の価格操作攻撃からプロトコルをしっかりと守ることができます。
—
5. まとめ
今回は、DeFiの流動性プールにおける価格操作攻撃のメカニズムと、その対策についてご紹介しました。
- 「一瞬の価格」や「たった一箇所のデータ」を盲信しない!
- フラッシュローンによる短時間の価格歪みに耐えられる仕組み(TWAPや堅牢なオラクル)を取り入れる!
この2つを意識するだけでも、スマートコントラクトのセキュリティレベルはグッと上がります。セキュリティの世界は奥が深いですが、一歩ずつ仕組みを理解していけば必ず強くなれます。
それでは、また次回のセキュリティ解説でお会いしましょう!安全で楽しい開発ライフを!
コメント