メインコンテンツまでスキップ

アルゴリズム

HyperPredictV1Pairの役割

HyperPredictV1Pairは、単一の原資産と取引間隔(intervalSeconds)に紐付けられたオンチェーンのペアです。各ペアは以下の責任を負います:

  • オペレーター(自動化ボット)がPythオラクルから価格を取得し、ラウンド(epoch)を順次進行させることを許可する
  • ベット参加者のステークとポジション(Bull/Bear)を受け入れ、ledgerおよびuserRoundsマップ内に保存する
  • ラウンドが正常に終了するたびに、報酬の基準額(rewardBaseCalAmount)と分配額(rewardAmount)を計算する
  • 無効なラウンドやオラクルの停止中に資金を保護し、必要な場合に返金を許可する

以下のセクションでは、ラウンドのライフサイクル、Pythの統合、安全メカニズム、および報酬アルゴリズムをその順序で整理します。

ラウンドのライフサイクル

各ラウンドにはStart → Lock → Closeの3つのフェーズがあり、すべてのフェーズはintervalSecondsで区切られます。ベット期間はstartTimestampからlockTimestampまでで、価格はcloseTimestampまで観測され、executeRoundがシステムを次のステップに進めます。

通常ラウンドのタイムライン

  • 最初のラウンドをブートストラップするために、genesisStartRoundgenesisLockRoundをそれぞれ1回呼び出します。
  • ジェネシスの後、executeRoundはラウンドnに対して1つのトランザクションで3つのアクションを処理します:
    1. 最新のオラクル価格を使用してラウンドn-1をロックします。
    2. ラウンドn-2をクローズし、最終価格を確定します。
    3. 新しいベットのためにラウンドnを開始します。
  • 連続した呼び出しが遅延した場合でも安全な停止を可能にするため、ファクトリはグローバルなbufferSecondsを強制します。bufferSeconds内に完了しないフェーズは拒否されます。

Pythオラクルの統合とフォールバック

_getPriceFromOracleはPythのgetPriceNoOlderThanに依存しているため、bufferSeconds内に公開された価格のみが受け入れられます。安全性は以下によって保証されます:

  • 公開時間(publishTime)をoracleLatestRoundIdとして保存し、オペレーターが誤って古いデータを使用するのを防ぎます
  • PythのexpoPYTH_PRICE_DECIMALS (=8)に正規化し、負の値に対してもオーバーフローチェックを追加します
  • 価格が取得できない、または更新がbufferSecondsを超えて遅延した場合、oracleCalledfalseに保ちます。これにより、後述の返金パスがトリガーされます

オラクルラウンドの欠落

価格取得が失敗した場合、ラウンドは「欠落」とマークされます。claimable条件を満たさないため、ベット参加者はrefundableパスを介して全ステーク額を回収できます。オペレーターはpause/unpauseを呼び出すか、ジェネシスのステップを再実行することで安全に再開できます。

ベットの受け入れと状態管理

  • betBullbetBearwhenNotPausednonReentrantによって保護され、notContract修飾子によってアクセスがEOAのみに制限されます。
  • 各ラウンドについて、ステークはround.totalAmountとサイド固有のバケット(bullAmount/bearAmount)に追加されます。同じラウンド内でポジションを混ぜることは禁止されています。
  • ダストスパムをブロックするために、ベットはfactory.minBetAmount()以上でなければなりません。
  • ledger[epoch][user]には、ベットamountとそのclaimedステータスが保存され、どちらもgetUserRoundsを介してページ分割できます。
  • ラウンドがoracleCalled == falseで終了した場合、block.timestamp > closeTimestamp + bufferSecondsが成立すると、ユーザーは返金を要求できます。

報酬計算アルゴリズム

ラウンドn-2の報酬は、オペレーターがexecuteRoundを呼び出すときに確定します。コントラクトは以下の式を使用します:

treasuryAmt = total * treasuryFee / 10000
rewardAmount = total - treasuryAmt
rewardBaseCalAmount = closePrice > lockPrice ? bullAmount : bearAmount
  • treasuryFeetreasuryFeeWithReferralreferralFeeはファクトリレベルで設定されます(hyperpredict-contract-v1/config/HyperPredictV1Factory.tsを参照)。BSCでは、値はそれぞれ3%1%1%です。
  • closePrice == lockPrice(引き分け)の場合、rewardAmountは0になり、全員が返金を受けます。
  • 勝者の基本ペイアウトはuserAmount / rewardBaseCalAmount * rewardAmountです。この式は、ベット参加者が後で紹介リベートの対象となった場合でも、オンチェーンでの計算を決定論的に保つために、常にtreasuryFee(BSCでは3%)を使用します。
  • ラウンドが無効化された場合、_calculateRewardsは早期に終了し、ポットは返金可能になります。

紹介の配分

紹介の処理は請求時まで延期され、ベット参加者に登録された紹介者がいる場合にのみ_applyReferralBonus内で実行されます:

  • referralFee(1%)は、紹介者に資金を供給するプールを定義します。ユーザーの分け前はreferrerBonus = total * referralFee / 10000 * userBet / rewardBaseCalAmountで、これは勝者のペイアウトから差し引かれ、紹介者に送金されます。
  • その差し引きを相殺し、紹介者がいる場合は常に財務がtreasuryFeeWithReferral(1%)のみを請求するようにするため、勝者もリベートを受け取ります。リベートプールは(treasuryFee - treasuryFeeWithReferral)(BSCでは2%)で、勝者の取り分はwinnerBonus = total * (treasuryFee - treasuryFeeWithReferral) / 10000 * userBet / rewardBaseCalAmountです。この金額は、請求が実行されるときに累積された財務残高から引き落とされます。
  • ベット参加者に最終的に支払われる報酬はbaseReward + winnerBonus - referrerBonusです。紹介者がいない場合、_applyReferralBonusは早期に終了し、ベット参加者は単にbaseRewardを受け取るため、財務は全3%を保持します。

この遅延計算により、紹介者がいるベット参加者はより安い手数料体系(1%の財務手数料+1%の紹介料)に落ち着き、紹介者がいないベット参加者は3%の均一な財務手数料にとどまることが保証されます。

請求時の実行

ユーザーがclaimを呼び出すと、_claimは各epochを順番に評価します:

  1. oracleCalled == trueでエントリがclaimableの場合、勝利サイドのbaseRewardを計算します。
  2. _applyReferralBonusを実行して紹介の分割を決済し、winnerBonusと紹介者報酬を確定します。
  3. refundableが成立している間にoracleCalled == falseの場合、単に元のステークを返します。
  4. すべてのaddedRewardを合計し、合計をネイティブ通貨で送信し、紹介ボーナスが累積した場合は、それらを別の転送で送信し、ReferralPaidをemitします。

具体例

A: Bullに100 USDCをステーク
B: Bearに100 USDCをステーク
C: 紹介者(Aを招待)
treasuryFee = 3%
treasuryFeeWithReferral = 1%
referralFee = 1%
ラウンド結果:Bullが勝利
  • totalAmount = 200 USDC
  • treasuryAmount (初期) = 200 * 0.03 = 6 USDC
  • rewardAmount = 200 - 6 = 194 USDC
  • rewardBaseCalAmount = bullAmount = 100 USDC
  • AのbaseReward = 100 / 100 * 194 = 194 USDC
  • 紹介プールreferralPool = 200 * 0.01 = 2 USDCなのでreferrerBonus = 2 USDC
  • 財務リベートプールtreasuryRefundPool = 200 * (0.03 - 0.01) = 4 USDCなのでwinnerBonus = 4 USDC
  • 勝者は194 + 4 - 2 = 196 USDCを受け取ります
  • 紹介者は2 USDCを受け取ります
  • 財務は6 - 4 = 2 USDCを保持し、これは紹介されたベットの1%のスケジュールに一致します

ベット参加者に紹介者がいなかった場合、_applyReferralBonusはリベートパスを完全にスキップします:Aは194 USDCのbaseRewardのみを請求し、財務は全3%(6 USDC)を保持します。Bは、オラクルが失敗しない限りポジションがアウトオブザマネーで終了したため負けます。オラクルが失敗した場合は、両サイドがステークを返金できます。

安全性とフェイルセーフ

  • 役割の分離: onlyOperatorはラウンドの進行を駆動し、onlyAdminは財務アクション(claimTreasury, recoverToken)を処理します。
  • Pausable + Genesis: pauseで異常な状態を停止し、genesisStartRound/genesisLockRoundを介してクリーンに再起動することで、重複または未定義のラウンドを回避します。
  • ReentrancyGuard & non-contract gate: ペイアウトパスを含むすべての関数は再入不可であり、EOAのみが許可され、マルチコールコントラクトをブロックします。
  • バッファの強制: _safeLockRound_safeEndRoundbufferSeconds外の操作を拒否し、タイミングゲームや価格操作を停止します。
  • 資金保護: claimable/refundable条件は、二重請求を防ぎながら、オラクルのダウンタイム中の元本返還を保証します。
  • オラクル入力の検証: getPriceNoOlderThanは古いデータを破棄し、負の値やオーバーフローシナリオをサニタイズします。

これらのメカニズムが連携することで、HyperPredictV1Pairは完全にオンチェーンの予測市場を安全に、自律的に、そして透明なペイアウトと紹介会計で実行できます。