価格オラクルが止まったら?DeFiの停止範囲とフォールバックの落とし穴
約26分で読めます
約26分
目次(タップで折りたたみ)
あるレンディングプロトコルの開発チームが、障害対応の方針を話し合っています。価格オラクルがおかしくなったらどうするか。最初に出た案は、「とりあえず全部止める」でした。
一見、安全そうに見えます。けれども担保付きのプロトコルでは、これが危険な方向に働きます。全部止めれば、清算まで止まります。清算を止めている間も価格は動き、不良債権(回収できなくなる貸し付け)は増え続けます。
オラクルは、ブロックチェーンの外にある価格などのデータをコントラクトへ届ける仕組みです。価格オラクルに頼るレンディング、デリバティブ、担保付き発行のプロトコルでは、障害のときに次の3つを決めておく必要があります。
- 何を止め、何を続けるか
- 別の価格源へ切り替えるのは安全か。攻撃者が切り替えを好きなときに起こせないか
- いつ、どうやって元に戻すか
以下では、この開発チームがコントラクトに書き込む順に、3つを実装の水準まで具体的にしていきます。清算の成立条件やHealth Factor(借り手の健全さを示す指標)はHealth Factor と清算ボットの記事で扱っています。
この記事で使う言葉
- フォールバック:主たる手段が使えないときに、機能を落とした代わりの手段へ切り替えること
- サーキットブレーカー:異常に気づいたら処理を自動で止め、被害の広がりを抑える仕組み
- 縮退運転:危ない操作を止め、安全な操作だけで動かし続けること
- heartbeat:価格が動かなくても、必ず値を更新する最大の間隔
- 裁定:価格のずれを利用して利益を得る取引
何を止めるかは、プロトコル全体ではなく操作ごとに決める
清算は、プロトコルの支払い能力を守る仕組みです。止めれば不良債権が増えます。逆に、誤った価格のまま清算を通せば、本当は健全な利用者のポジションが不当に処分されます。だから、止めるかどうかはプロトコル単位では決められません。操作ごとに決めます。
判断の軸は、「その操作は、オラクルが間違っていたときに、プロトコルのリスクを増やすか、減らすか」です。
リスクを減らす方向の操作、つまり返済と担保の追加は、価格が信頼できない間も止めません。止めてしまうと、利用者は自分のポジションを立て直す手段を失います。そして、復帰した後の清算を待つだけになります。操作ごとの扱いは次のとおりです。
| 操作(価格の使われ方) | 異常時の扱い | そのまま許可した場合に起きること |
|---|---|---|
| 新規借入(担保の評価) | 停止 | 過大評価された担保で借り、返さない |
| 担保の追加(健全化の方向にのみ影響) | 継続 | 実質的なリスク増加はない |
| 返済(原則として不要) | 継続 | 実質的なリスク増加はない |
| 担保の引き出し(残余担保の評価) | 停止 | 過小評価された負債のまま担保を抜かれる |
| 清算(担保と負債の双方) | 猶予時間の経過後に再開 | 誤った価格による不当清算が確定する |
| 新規建玉・増玉(参照価格) | 停止 | 有利な古い価格で建てられる |
| 決済・償還(確定価格) | 遅延決済に切り替え | 古い価格で受渡しが確定する |
この考え方は、実際の実装にも表れています。Aave v3のPriceOracleSentinelが公開している判定は、isBorrowAllowed()とisLiquidationAllowed()の2つだけです。返済や担保の供給は対象にしていません。止めるのを借入と清算に絞り、立て直しの方向の操作は通し続ける設計だと読み取れます。
「オラクル障害」を5種類に分け、それぞれの気づき方と対応を決める
障害をひとまとめにすると、判定の式が1本になります。すると、どれか1つの症状にしか効きません。少なくとも次の5つは、別々に扱います。
| 類型(症状) | オンチェーンでの検知 | 基本の対応 |
|---|---|---|
| 更新停止(値は読めるが古い) | updatedAt の経過時間が閾値超過 | 操作別の停止 |
| 取得不能(呼び出しがrevert=実行の取り消しで失敗する) | 外部呼び出しの失敗 | 読み取り経路を分離し、返済系を守る |
| 異常値(値は新しいが現実と乖離) | 自前の上下限、他の価格源との乖離幅 | 停止と手動確認 |
| 鮮度の選択(有効期間内の任意の値を使われる) | 提出された更新の publishTime | 許容鮮度の短縮と遅延決済 |
| 前提の崩壊(値は正常だが使ってよい状況でない) | Sequencer稼働フィード、市場状態 | 猶予時間つきの停止 |
取得不能:見落とされがちだが、実際に起こる
Chainlinkはデータフィード選定のガイダンスで、フィードが廃止された場合の動きを説明しています。latestAnswerは0を返し、latestRoundDataは「no data available」でrevertします。
価格の取得が例外で落ちることを想定していないコントラクトでは、この時点で全機能が止まります。返済も担保の追加も含めて、です。
鮮度の選択:pull型に固有の弱点
価格の更新には2つの方式があります。提供者がコントラクトへ書き込む方式(push型)と、利用者が署名済みの更新を取引に添えて持ち込む方式(pull型)です。鮮度の選択は、pull型に固有の問題です。
Pyth NetworkはBest Practicesで注意を促しています。利用者が、制約を満たす範囲で好きな更新を選べること。これは実質的に、遅延(レイテンシ)と同じ弱点になるというのです。対策として、注文の作成と執行を分けること、保有期間を強制することを挙げています。許容する鮮度を長く取るほど、攻撃者が選べる価格の幅は広がります。
前提の崩壊:L2のSequencerの停止や、市場の休場
代表例は、L2のSequencer(取引を受け付けて順番を決める役割)の停止です。ChainlinkのL2 Sequencer Uptime Feedsは、稼働状態を0(up)と1(down)で返すフィードを提供しています。サンプルでは、GRACE_PERIOD_TIME = 3600(1時間)を、復帰後の待ち時間として使います。
市場そのものが閉まっている場合もあります。ChainlinkのData Streamsでは、レポートスキーマv4でmarketStatus(0=Unknown、1=Closed、2=Open)が定義されています。スキーマの版の注意は、後半の「実装時の落とし穴」にまとめました。Sequencer停止時のサービス継続そのものはSequencer停止時のサービス継続設計で詳しく扱っています。
異常の判定は、鮮度・範囲・整合を別々の条件として書く
3つの異常は、それぞれ対応が違います。
- 鮮度切れ:時間が解決するかもしれない
- 範囲外:設定ミスか、本当の暴落かを見分ける必要がある
- 他の価格源とのずれ:どちらが正しいかは、チェーン上では決められない
だから、3つを1つの関数にまとめてもかまいませんが、判定の結果は別々に持ちます。
次の例は、heartbeatが3600秒のフィードから、鮮度と範囲を確かめて価格を読むものです。
// 例: heartbeat 3600秒のフィードに対する読み取り
uint256 private constant HEARTBEAT = 3600;
uint256 private constant STALENESS_SLACK = 900; // 更新遅延の許容
uint256 private constant MIN_BOUND = 1e8; // 資産ごとに設定
uint256 private constant MAX_BOUND = 1e13;
function _readPrice(AggregatorV3Interface feed) internal view returns (uint256) {
(, int256 answer, , uint256 updatedAt, ) = feed.latestRoundData();
// 鮮度
require(updatedAt != 0, "oracle: incomplete round");
require(block.timestamp - updatedAt <= HEARTBEAT + STALENESS_SLACK, "oracle: stale");
// 範囲
require(answer > 0, "oracle: non-positive");
uint256 price = uint256(answer);
require(price >= MIN_BOUND && price <= MAX_BOUND, "oracle: out of bounds");
return price;
}
heartbeatちょうどに更新が出ても、それがチェーンに取り込まれるまでには少し時間がかかります。そのため、鮮度の閾値をheartbeatと同じ値にすると、正常な更新の遅れでも判定に引っかかります。そこで、heartbeatに少し余裕を足した値を閾値にします。その余裕の間だけは、古い価格で動くことを受け入れます。
heartbeatと、更新のきっかけになる変動幅(deviation threshold)は、フィードごと・チェーンごとに違います。使うすべてのフィードの実際の値を確かめてから、定数を決めます。
範囲の上下限は、フィードの側には頼れません。ChainlinkのData Feeds API Referenceは、minAnswer / maxAnswerについて、ほとんどのData Feedではもう使われていないと書いています。そして、独自のサーキットブレーカーが必要かを評価し、実装するよう求めています。だから、上下限は自前で持ちます。
この下限が実害を生んだ事例として、よく参照される分析があります。2022年のLUNA急落のとき、価格が下限に張り付きました。その結果、BSC上のVenusで、実勢より高い価格のまま借入ができたというものです。複数の監査コンテストの指摘(たとえばsherlock-audit 2023-02-blueberry issue #18)で繰り返し参照されています。
これは監査コンテストでの二次的な分析で、公式の事故報告ではありません。ただ、構造ははっきりしています。下限に張り付いた値は、鮮度の判定を通ってしまいます。上のコードでupdatedAtしか見ていない実装が、なぜ危ないかを示しています。
try/catchによるフォールバックは、攻撃者から起動できる
Solidityでフォールバックを書くとき、いちばん自然に見えるのがtry/catchです。しかし、この形には落とし穴があります。次の例は、推奨しない形です。
// 推奨しない形
function getPrice() public view returns (uint256) {
try PRIMARY.latestRoundData()
returns (uint80, int256 a, uint256, uint256, uint80)
{
return uint256(a);
} catch {
// 呼び出し側の都合で到達できる
return FALLBACK.getPrice();
}
}
Solidityの公式ドキュメントは、外部呼び出しが失敗する理由について注意しています。意図的なエラーではなく、ガス不足が原因のこともある。呼び出す側は常に少なくとも1/64のガスを手元に残すので、呼ばれた側がガス切れになっても、呼び出す側にはガスが残る、というものです(Solidity v0.8.30 Control Structures)。
これはEVMの仕様で、実装の側では無効にできません。攻撃者は、ちょうどよいガス量を指定してトランザクションを送れます。主オラクルの呼び出しだけがガス切れになり、catch節とその後の処理は最後まで動く量です。
つまり、切り替えを起こすかどうかを、呼び出す側に握られるのです。主オラクルが完全に正常でも、好きなときにフォールバックの価格を使わせられます。切り替え先がDEXのTWAP(一定期間の時間加重平均価格)のように動かしやすい価格源なら、そこを前もって動かしておくだけで攻撃が成り立ちます。
対策は、catch節で代わりの価格を返さないことです。catch節では「取得できなかった」という結果だけを返します。その状態をどう扱うかは、呼び出し元の操作ごとに決めます。あわせて、呼び出す前に十分なガスが残っていることを求めます。
uint256 private constant ORACLE_GAS_FLOOR = 200_000;
function _tryPrice() internal view returns (bool ok, uint256 price) {
require(gasleft() >= ORACLE_GAS_FLOOR, "oracle: insufficient gas");
try PRIMARY.latestRoundData()
returns (uint80, int256 a, uint256, uint256 u, uint80)
{
if (a <= 0 || u == 0) return (false, 0);
if (block.timestamp - u > HEARTBEAT + STALENESS_SLACK) return (false, 0);
return (true, uint256(a));
} catch (bytes memory) {
// 価格を作らず、取得の成否だけを返す
return (false, 0);
}
}
ガスの下限の値は、主オラクルの読み取りに実際にかかるガスに、余裕を足して決めます。この下限で攻撃が不可能になるわけではありません。「うっかり少ないガスでフォールバックへ落ちる」道をふさぐためのものです。catch節そのものの落とし穴は、後半にまとめています。
フォールバックを許してよいのは、切り替え先を攻撃者が選べないときだけ
切り替えてよいかは、次の4つの条件をすべて満たすかで判断します。1つでも欠けるなら、切り替えを作るより「止める」ほうが安全です。
- 切り替えの条件が、チェーン上で客観的に決まる:経過時間やフィードの状態など、呼び出し側のガス量や実行の順番で変わらない値で判定します。
- 切り替え先が独立している:同じノード群、同じ取引所、同じ価格源に頼る2つのフィードは、冗長化になりません。
- 切り替え後の価格が、操作ごとに慎重な方向へ寄せられる:担保の評価には低いほうの価格、負債の評価には高いほうの価格を使います。Pythが信頼区間について説明している向きと同じです。受け取る担保には価格から信頼区間の幅だけ低い値(μ-σ)、負う債務には信頼区間の幅だけ高い値(μ+σ)を使います。
- 切り替え中は、通常の運転に戻さない:フォールバックが動いている間も、リスクを増やす操作は止めたままにします。
1の条件がチェーン上で閉じている例が、Aave v3の AaveOracle.getAssetPriceです。資産の価格フィードが未設定(address(0))のとき、またはlatestAnswer()が0以下のときに、フォールバックのオラクルへ渡します。呼び出し側の条件は入りません。
この実装は、価格を取る層に、鮮度によるrevertを置いていません。鮮度や状況の判定は、さきほどのPriceOracleSentinelの側に分けられています。価格を読む道と、操作を許すかを決める道を分ける、という設計判断として参考になります。
一方、この構造には運用上の注意もあります。価格フィードが未設定の資産は、自動的にフォールバックの道へ落ちます。新しい資産の設定の順番を誤ると、ふだん誰も確かめていない道が本番で使われます。
Aave v3のフォールバックオラクルについても、デプロイ後に外部のリサーチャーから指摘が公開されています。価格フィードを設定する前の資産が一時的にフォールバック経由になり、操作されうるというものです。フォールバックは「めったに動かないコード」ほど、レビューとテストが薄くなりがちです。設計のときから、そのつもりで備えます。
構成ごとの評価は次のとおりです。古い価格が固定されてしまう構成と、攻撃者が好きなときに起動できる構成は採用しません。
| 構成 | 切替条件の決まり方/切替先の独立性 | 評価 |
|---|---|---|
| 取得失敗時に直近価格を使い続ける | 内部で自動/なし(同一の価格源) | 採用しない。古い価格が固定される |
try/catchで即座に代替価格を返す | 呼び出し側のガスに依存/— | 採用しない。任意に起動できる |
| 2つの価格源の保守的な側を常時採用 | 切替なし/あり | 基本形。切替分岐そのものが存在しない |
| 経過時間で別プロバイダへ切替 | オンチェーンで客観的/あり | 条件付きで採用できる |
| 経過時間でDEXのTWAPへ切替 | オンチェーンで客観的/あるが操作耐性が低い | 縮退運転に限定する |
手間と安全性のつり合いで言えば、「2つの価格源のうち慎重な側を常に使う」構成も候補です。担保の評価と債務の評価では慎重な方向が逆になりうるので、操作ごとに選びます。
切り替えの分岐がないので、まとめて消える問題があります。切り替えのバグ、切り替え中だけ起きる食い違い、フォールバックの道のテスト漏れです。
その代わり、ふだんから2つの価格源を読む分のガスがかかります。片方が壊れた時点で、評価が慎重すぎる方向に振れます。どちらを取るかは、対象のプロトコルの取引の頻度とガスの予算しだいです。オラクルの署名・鮮度・訂正といった手前の設計はRWA・金融システムのオラクル設計で扱っています。
元に戻すときは、「試しに少し通す」ではなく猶予時間を置く
一般的なWebシステムのサーキットブレーカーは、3つの状態を行き来します。通常(CLOSED)、遮断(OPEN)、試しに一部を通す(HALF_OPEN)です。resilience4jでは、遮断から試しの状態へ移るまでの待ち時間の既定が60秒、試しに通す呼び出しの数の既定が10件です。少数の試しの呼び出しで復旧を確かめ、成功すれば通常に戻します。
チェーン上では、試しの呼び出しに当たるのは本物のトランザクションです。そして、最初に誰が出すかを選べません。復帰直後の1件目を送るのは、多くの場合、その瞬間にいちばん得をする参加者です。止まっている間に積み上がった価格のずれは、戻った瞬間に一気に処理されます。だから、この形はチェーン上ではそのまま使えません。
代わりに使うのが、猶予時間です。「復旧した」ではなく、「復旧してから一定の時間が経った」を条件にします。そうすれば、利用者がポジションを立て直す時間を作れます。AaveのPriceOracleSentinelは、これを次の1行で表しています。
function _isUpAndGracePeriodPassed() internal view returns (bool) {
(, int256 answer, , uint256 lastUpdateTimestamp, ) = _sequencerOracle.latestRoundData();
return answer == 0 && block.timestamp - lastUpdateTimestamp > _gracePeriod;
}
どのタイムスタンプを基準にするかには注意が要ります(後半の「実装時の落とし穴」)。
猶予時間を、ふだんの設計に組み込む方法もあります。SkyのOSM(Oracle Security Module)は、価格をpokeで取り込み、hop(既定はONE_HOUR)だけ遅らせてから有効にします。目的は、オラクルへの攻撃に気づいて対応する時間を確保することだと説明されています。異常のときだけでなく、ふだんから1時間遅れの価格で動きます。
stop()で更新を止め、void()で持っている値を消す操作も用意されています。ただし同じ資料は、注意も書いています。pokeは前回の呼び出しから1時間後ではなく、「時刻の区切りが変わった時点」で呼べます。そのため、不正な価格がhopより短い遅れで有効になりうるのです。
戻し方の方式を比べると、次のようになります。
| 方式(復帰の判定) | 向く場面 | 代償 |
|---|---|---|
| 猶予時間(復旧から一定時間の経過) | 清算・借入の再開 | 猶予中も不良債権が増える |
| 常時の遅延フィード(常に一定時間前の価格を使う) | 担保評価、ガバナンス反応時間の確保 | 平常時から価格が古い |
| 段階的な再開(操作・資産ごとに順次解除) | 原因が特定できている場合 | 解除順序の設計と運用が必要 |
| 権限者による解除(マルチシグ=複数署名などの承認) | 原因不明時、想定外の事象 | 復旧が担当者の可用性に依存する |
実際には、1つではなく組み合わせます。たとえば「Sequencerの復旧は猶予時間で自動、価格の異常値は権限者が解除、清算は資産ごとに段階的に再開」といった形です。機能ごとに再開の条件を設ける考え方は、暗号資産交換所のインシデント後サービス再開設計とも共通します。
出金のサーキットブレーカーは、オラクルとは別に持つ
ここまでは、「価格を信じてよいか」の話でした。これとは別に、「一定の時間にいくら出ていくか」を見る仕組みを持つ設計があります。
オラクルの異常は、資金が流れ出る原因の1つにすぎません。オラクルが完全に正常でも、コントラクトの別のバグで流出は起こります。判定を分けておけば、どちらかの想定が外れても、もう一方が働きます。
この考え方を標準にしようとしたのが、ERC-7265です。資産ごとに流出の速さの上限を決め、一定期間の流出が閾値を超えたら、プロトコル全体の出金を一時的に止めるインターフェースを定めるものでした。超えたときの動きとして、出金をrevertする方式と、猶予期間の間いったん預かってから精算する方式の両方が考えられています。
ただし、採用の状況には注意が要ります。この提案のEIP pull request(ethereum/EIPs #7265)は、2023年7月3日に作られ、閉じられたまま(closed、mergedAtは空)です。ERCの正式なリポジトリethereum/ERCsにも、erc-7265.mdはありません。
番号が広く紹介されているので確定した標準に見えますが、実際は取り込まれていない提案なのです。準拠したライブラリが将来使えると当てにせず、流出の速さを制限する考え方だけを自前で実装します。
自前で作る場合に決めることは、主に4つです。
- 上限の測り方:TVL(Total Value Locked、プロトコルに預けられた資産の総額)に対する比率にするか、絶対額にするか。比率にすると、TVLが減った局面で上限も一緒に下がります。
- 集計の期間:決まった区切りより、「直近◯時間」のように常に動く集計期間(ローリングウィンドウ)の方が、区切りの切れ目を狙われにくくなります。
- 超えたときの動き:revertは作りが単純な代わりに、正常な大口の利用者も巻き込みます。いったん預かる方式は使い勝手を保てますが、「誰がどの条件で返すか」を先に決めないと、預かった資産がたまったままになります。
- 上限を変えられる人と、その手順
これは流出を遅らせる仕組みで、確定したトランザクションを取り消す仕組みではありません。すでに外に出た資産は、コントラクトの操作では戻せません。残りを守り、対応の時間を作るための仕組みです。緊急停止と復帰の運用の設計は、Hyperliquid AIエージェントのRisk Engine設計でも具体例を扱っています。
前もって決めるのは、閾値の数字より「誰が何を根拠に動かせるか」
閾値は後から調整できます。でも、止める権限、止める細かさ、戻す条件は、事故の最中には設計できません。次の項目は、実装と同時に決めておきます。
| 決めること | 設定の例 | 決めていないと起きること |
|---|---|---|
| 鮮度の閾値 | フィードごとのheartbeat+許容遅延 | 正常時の誤停止、または無期限に古い価格の使用 |
| 価格の上下限 | 資産ごとの絶対値の上下限 | 異常値がそのまま評価に入る |
| 停止の粒度 | 資産別、操作別 | 全停止しか選べず、返済まで止まる |
| 停止できる主体 | リスク管理ロール、マルチシグ | 合意形成を待つ間に損失が拡大する |
| 復帰の条件 | 猶予時間、再開する操作の順序 | 復帰直後に清算と裁定が集中する |
| 記録する内容 | 使用した価格、判定結果、経路、時刻 | 事後に何が起きたか説明できない |
| 監視の対象 | フィード廃止告知、更新間隔、価格源の間の乖離 | 廃止や更新停止に気づくのが利用者より遅れる |
権限の設計では、「止められること」より「止めても立て直しの操作が続くこと」を先に確かめます。全機能を止める1つのスイッチしかないと、実際の事故では押しにくくなり、判断が遅れます。
止める権限・戻す権限・アップグレードの権限の分け方と、停止から段階的な解除までの状態の移り方はスマートコントラクトの緊急停止設計で詳しく扱っています。監視の実装は、DeFiポジション監視と清算リスク対策の考え方が使えます。
フィードの廃止について、Chainlinkはさきほどのガイダンスで、前倒しが必要な場合を除き2週間前に知らせるとしています。そのうえで、利用者の側で、秩序立てて依存をやめる(orderly deprecation)手段を用意しておくべきだと述べています。告知を受け取る先が、個人のメール1本になっていないか。これは実装より先に確かめられます。
本番の前に確かめたい異常のケース
正常なケースのテストが通っていても、次の項目はわざわざ作らないと確かめられません。本番ネットワークの状態を写した試験環境(フォーク)で、オラクルを試験用の偽物(モック)に差し替えれば、多くは再現できます。
updatedAtを閾値を超えるまで進めた状態で、清算が誤って起きず、返済と担保の追加は成功する。latestRoundDataがrevertする状態でも、返済と担保の追加の道が生きている。- 呼び出しのガスを絞って主オラクルだけをガス切れにしたとき、フォールバックの価格が使われない。
- Sequencerの稼働フィードを停止(1)から稼働(0)へ戻した直後、猶予時間の間は借入と清算が拒否される。
- 2つの価格源のずれが閾値を超えたとき、慎重な側の価格が選ばれる(有利な側ではない)。
- 止まっている間に価格が大きく動いた後、戻ったときに清算の要求が集中しても、処理が詰まらない。
- 出金の速さの上限に達したとき、想定した動き(revertか、いったん預かる)になり、預かった分を精算する道がある。
- フィードの廃止の告知を受け取る監視が動き、担当者以外にも届く。
プロトコルを採用する側として評価するなら、これらの動きを仕様書ではなく、実装とテストで確かめられるかが判断の材料になります。評価の進め方は企業向けDeFiプロトコルのデューデリジェンス実務で整理しています。
よくある質問
鮮度の閾値は、フィードのheartbeatと同じ値にしてよいですか
heartbeatは「これを超えたら必ず更新する」上限です。そのうえ、更新のトランザクションが取り込まれるまでにも時間がかかります。同じ値にすると、正常な更新の遅れでも判定に引っかかるので、余裕を足すのが基本形です。
閾値はheartbeatに一定の余裕を足した値にし、その差の間は古い価格で動くことを許すと、はっきり決めておきます。余裕をどこまで取れるかは、その価格で許す操作のリスクの大きさで変わります。清算の判定と表示用の価格で、別々の閾値を持つ構成もありえます(Chainlink公式のガイダンスにもとづく整理です)。
オラクルを2つ使えば冗長化になりますか
冗長化になるのは、2つの価格源が独立していて、切り替えの条件を攻撃者に選ばれない場合だけです。同じ取引所群の価格を見る2つのフィードは、価格源が壊れたときに同時に壊れます。
片方が壊れたときに「動いているほう」へ自動で切り替える作りでは、どちらが動いていると判定されるかを攻撃者が操れると、実質的に価格を選ばせることになります。
切り替えの分岐を作らず、2つのうち操作ごとに慎重な側を常に使う構成なら、分岐を操られる問題は減らせます。ただし、価格源の誤り、行き過ぎた清算、サービスの停止のリスクは、別に評価が要ります。
価格が取れない間、直近に取れた価格を使い続けてよいですか
価格が取れない状況は、市場が動いていない状況ではありません。むしろ価格が大きく動いている局面ほど、更新の遅れや停止は起こりやすくなります。だから、リスクを増やす操作では、使い続けないのが基本形です。
直近の価格を使い続けるのは、実質的に「その時点の価格で固定する」のと同じです。実勢とのずれが広がるほど、ずれを利用する取引だけが集まります。
一方、返済や担保の追加のように、評価が有利な方向にしか効かない操作では、直近の価格で処理を続ける判断も成り立ちます。ここでも、操作ごとに扱いを分けることが判断の基準です。
実装時の落とし穴
ここからは、さきほどの開発チームが実際にコードを書くときに踏みやすい、細かい注意をまとめます。
Chainlinkの関数とフィールド
latestRoundDataの戻り値answeredInRoundは、APIリファレンスで「Deprecated」と書かれています。過去の記事や監査の指摘で見かけるansweredInRound >= roundIdの比較を、鮮度の保証として新しい実装に持ち込む根拠にはなりません。- Data Streamsのレポートスキーマv4は非推奨とされ、RWA向けの最新はv8です。
- Sequencerの猶予時間の基準:Aaveの
_isUpAndGracePeriodPassedは、latestRoundDataの4番目の戻り値、つまりupdatedAtを読んでいます。一方、Chainlinkの公式サンプルは3番目のstartedAtを使い、そこからの経過時間をGRACE_PERIOD_TIMEと比べます。どちらを基準にするかで「復旧した瞬間」の定義が変わります。自分で書くなら、フィードの更新の動きを確かめてから、はっきり選びます。
try/catchが捕まえられないもの
Solidityの同じドキュメントは、次の点も述べています。戻り値をデコードする途中でエラーが起きた場合は、catch節では捕まりません。低レベルのcatch (bytes memory)節を用意しないと、try/catchそのものがrevertすることもあります。「例外を握りつぶせている」という思い込みは、いつも成り立つとは限りません。
公式資料の原文
- Pyth(鮮度の選択):「functionally equivalent to latency」
- Chainlink(
minAnswer/maxAnswer):「This value is no longer used on most Data Feeds.」「Evaluate if your use case for Data Feeds requires a custom circuit breaker and implement it」 - Solidity(ガス不足):「Also, it could be due to an out-of-gas situation and not a deliberate error condition: The caller always retains at least 1/64th of the gas in a call and thus even if the called contract goes out of gas, the caller still has some gas left.」
- Sky OSM(目的):「ensure that there is time to detect and react to an Oracle attack」
XTELAができること
私たちは、価格オラクルに依存するプロトコルやプロダクトについて、操作ごとの停止範囲の表、鮮度・範囲・整合の判定条件とその数値、フォールバック構成の是非を設計し、コントラクトに実装します。本番の状態を複製した環境で、ガス切れによるフォールバック起動、Sequencer復旧直後、2つの価格源の乖離といった異常系を再現するテストも組み立てます。貴社のプロダクトで障害時の止め方と戻し方を決めたい場合は、お問い合わせフォームからご相談ください。
参考資料
- Chainlink Documentation: Selecting Quality Data Feeds(2026年9月24日確認)
- Chainlink Documentation: Data Feeds API Reference(
minAnswer/maxAnswer、answeredInRound。2026年9月24日確認) - Chainlink Documentation: Using Data Feeds on L2 Networks(Sequencer Uptime Feeds)(2026年9月24日確認)
- Chainlink Documentation: Report Schema Crypto Advanced (v3) / Report Schema v4 (RWA, deprecated)(2026年9月24日確認)
- Pyth Network Documentation: Best Practices(信頼区間、
getPriceNoOlderThan、pull型のレイテンシ等価性。2026年9月24日確認) - aave-v3-core:
PriceOracleSentinel.sol(2026年9月24日確認) - aave-v3-core:
AaveOracle.sol(2026年9月24日確認) - Sky Protocol Docs: OSM (Oracle Security Module) / makerdao/osm(2026年9月24日確認)
- Solidity v0.8.30 Documentation: Control Structures(try/catch、1/64ガス留保)(2026年9月24日確認)
- resilience4j: CircuitBreaker(CLOSED / OPEN / HALF_OPEN、既定値。2026年9月24日確認)
- ethereum/EIPs pull request #7265: Add EIP: Circuit Breaker(状態 closed、未マージ。2026年9月24日確認)
- sherlock-audit 2023-02-blueberry judging issue #18(
minAnswer張り付きに関する二次的な分析。2026年8月16日確認)
資料の確認日と注意
Chainlink、Pyth Network、Aave、Sky(旧MakerDAO)、Solidityの一次資料は、2026年9月24日に確認し直しました。Data Streamsのスキーマの版、resilience4jの既定値、ERC-7265の状態、OSMの注記、よくある質問のChainlinkのガイダンスも、2026年9月24日時点の内容です。一般的な技術設計の解説であり、仕様・既定値・提案の状態は更新されえます。実装する時点の一次資料を確認してください。