デプロイ

プロトコルは2つのチェーンにまたがってデプロイされ、その順序は交渉の余地がありません。

ウォレット

3つの異なるロールがあり、決して同じ鍵を使いません。

ウォレット できること
プロトコルオーナー トークン、流動性のロック、ミラーボールトのデプロイヤーを除くすべてのモジュールのアップグレード、ファクトリーの接続設定、バスケットの登録、一度しか設定できないアドレスの設定、プロトコルの設定の変更、エアドロップの除外リストの設定および各株式のOFTの登録、緊急時の資産の即時の移動、モジュールが誤って保有しているものの取り出し(rescue)、終了モードの開始またはキャンセルとその30日の経過後の流動性の回収
キーパー 変換、ブリッジバッチ、そしてエアドロップのトリガー:サイクルを開く、株式を送る、保留された株式を割り当てる。2026-10-05以降は、フックとロックが受取人のために保持している分の支払いとLP手数料の回収も行う。これらは誰でも行える呼び出し
デプロイヤー コントラクトのデプロイ。プロトコルオーナーが所有権を受け入れるまではファクトリーを所有し、最初のブリッジバッチがリモートハブ上でプロトコルオーナーを指定するまではリモートハブを管理する

スクリプトは、署名者をforgeのコマンドラインから、または環境変数の生のDEPLOYER_PRIVATE_KEYから取得します。DeployEthereumRail、DeployProtocol、DeployRemote、DeployBridgeはそのどちらにも対応しています。LaunchProtocol、RegisterBaskets、CreateMarketはDEPLOYER_PRIVATE_KEYしか読み込みません。

10回目の監査ループ以降、すべてのブロードキャストは--slow --skip-simulation付きで実行します。これがないと、forgeは各トランザクションに自身のシミュレーションが数えたガスを、EthereumのGlamsterdamアップグレード以前の価格で与えますが、アップグレード後のコントラクト作成にはその4〜7倍が必要です。つまり、どの作成もガス切れになります。これを付けると、forgeは前のトランザクションがマイニングされてから、各トランザクションにノードの見積もりを使います。したがってドライランのガスの数字も予算にはなりません。Glamsterdamの下では、DeployProtocolはおよそ2億4,000万ガス、各マーケットのローンチは1,500万〜2,400万ガスを要します。

順序

順序は3つの制約によって決まります。Ethereumのすべてのモジュールはコンストラクト時にファクトリーのアドレスを受け取るため、ファクトリーが最初に来て、その後、そのオーナーが残りをファクトリーに接続します。Ethereum側のレールのルーターとオラクルは、ブリッジハブと同様にファクトリーに結び付けられているため、プロトコルの後に来ます。2つのハブは、予測アドレスによって互いを固定します。

  1. DeployProtocol:最初にファクトリー、次に、v4の14の権限すべてに対応するようマイニングされたフック、ロック、保有量レコーダー、デプロイヤー、ボールトの実装、レンズ、スワップルーター、そしてエアドロップのコントラクトであるAirdropDistributor。すべてファクトリーに接続されます。その後、所有権はプロトコルオーナーに移り、プロトコルオーナーはそれを受け入れる必要があります
  2. DeployEthereumRail(ファクトリーのアドレスを渡す):オラクルと、Ethereum上のETH → USDCルーター。オーナーがこれらをファクトリーに設定します
  3. DeployBridge --sig "predict()":ブリッジハブとそのアダプターに割り当てられるアドレスを出力
  4. Robinhood Chain上のDeployRemote:最初に、それらの予測アドレスに固定されたリモートハブ、次に株式ルーター、オラクル、ミラーボールトの実装。ハブの管理者がこれらをハブに接続し、エアドロップの経路と株式アダプターが与えられていれば、それらも接続します(後述)。2026-10-06以降、このスクリプトは、実行のたびに、Ethereum上のエアドロップ配信のためのリモートハブの2つのガスのポリシーを、エアドロップの経路より前に設定し(後述)、オラクルの2つのガードも設定します。また、シーケンサーのチェックについての決定がなければ起動を拒否します。SEQUENCER_UPTIME_FEED(Robinhood Chain上のChainlinkのL2シーケンサーのアップタイムフィード)か、SEQUENCER_CHECK_OFF=true(意図的にチェックをオフにする)のどちらかで、両方を同時に指定することは決してできず、SEQUENCER_GRACE_PERIOD(デフォルトで3,600秒)はフィードと一緒にしか指定できません。ChainlinkはRobinhood Chainのためのそのようなフィードを公開していないため、現時点のメインネットでの実行はSEQUENCER_CHECK_OFF=trueを設定し、そのようなフィードが公開されれば、StockFunのオーナーが後からそれを設定します(setSequencerUptimeFeed)。その後、スクリプトは、ハブ、ルーター、オラクルの後で、各株式のオラクル一時停止チェックをオンにします(setOraclePauseCheck)。そのため、予測アドレスはどれも動きません
  5. DeployBridge:予測アドレスに置かれる、ブリッジハブとそのアダプター。オーナーは、ハブ上でアダプターを一度だけ指定します(setAdapter)。2026-10-05以降、ファクトリーがすでにハブを指定している場合、このスクリプトは何もデプロイする前に停止し(2026-10-06以降、これは予測アドレスより前に行われる最初の確認です)、署名者がファクトリーを所有している場合は、ハブを指定する前に株式を対応付けます
  6. setBridgeHub — 最初のマーケットより前に。2026-10-05以降、すでに登録されたバスケットを運べないハブは拒否されます(UnmappedBridgeStock)
  7. すべてのバスケットの株式について、ブリッジハブ上でaddStockMappingを呼び出す
  8. バスケットを登録:PlanBridgeBasketsがオーナーの呼び出しを出力。2026-10-06以降、バスケットに含められる株式は最大5つです。バスケットを参照してください
  9. LaunchProtocol:供給量の全量がロックされたポジションに入る$STOCKFUN、そのボールト、BuybackBurnerとsetBuybackWallet。エアドロップのコントラクトが先に指定されている必要があり、これはDeployProtocolが行います。2026-10-05以降、このスクリプトは、ミントの前に、トークンが取ることになるアドレスについて、ローンチの実行者を$STOCKFUNの除外リストに載せます。2026-10-06以降、プロトコルのマーケットが指定される前に止まった実行は、2つ目の$STOCKFUNをミントするのではなく、残したトークンとボールトを先に確認したうえで、それらを使って再開します(--sig "resume(address,address)")。プロトコルのマーケットが指定された後は、このスクリプトはもう実行されず、残りの手順は手作業で行います
  10. オーナーによる、エアドロップのコントラクト上でのregisterStockOft。Ethereum上の各株式のOFTについて行います

アップグレード可能な各モジュールは、実装、次にプロキシという2つのコントラクトとしてデプロイされます。predict()はこれらを数えに入れています。ブリッジハブのプロキシはデプロイヤーのnonce + 1で、そのアダプターのプロキシはnonce + 3でデプロイされます。

StockFunはLayerZeroのピアを一切ペアリングしません。USDG OFTのピアはその発行体に属しており、プリフライトはそれを確認するだけです。

2026-10-06以降、ローカル用とテストネット用のスクリプトであるLocalRunとDeployTestnetBridgeは、実際にトランザクションをブロードキャストするときにだけ、デプロイファイルを書き込みます。9回目の監査ループ以降はDeployTestnetRailも同様です。ドライランで得られるアドレスにはコードがないからです。テストネットでは、DeployTestnetRailが同じ3つのシーケンサーの入力をすべて任意で受け取り(フィードがなければチェックはオフのままです。Chainlinkはテストネットについてもそのようなフィードを掲載していません)、各株式のオラクル一時停止チェックをオンにします。Ethereumのスクリプトは、両方のガードをオフのままにします。テストネットのキーパーは、KEEPER_REQUIRE_MARKET_OPEN、KEEPER_AIRDROP_AFTER_SESSION、そして2026-10-06以降はKEEPER_CONVERT_ONCE_PER_WINDOWをfalseにして実行され、ループの周回ごとに変換を行います。キーパーを参照してください。7回目の監査ループ以降(2026-10-06)、キーパーは起動する前に、各RPCのチェーンを自身の設定と照合します。テストネットのキーパーはKEEPER_CHAIN_ID=11155111を、ブリッジを使う場合はKEEPER_REMOTE_CHAIN_ID=46630も設定し、メインネットのキーパーはKEEPER_CHAIN_ID=1と4663を設定します。8回目の監査ループ以降は両方が必須です。KEEPER_CHAIN_IDがない場合、またはブリッジを使うのにKEEPER_REMOTE_CHAIN_IDがない場合、キーパーは起動を拒否します。アプリのデータWorkerも同じ方法でエンドポイントのチェーンを確認し、その公開RPCは2つのチェーンIDに従うため、テストネットのWorkerに必要なのはその2つだけです。

2026-10-06のLayerZeroのテストネット実行は、本番用のスクリプト、またはその本体を保ったテストネット用のラッパーと、コントラクトのテストネット専用フォルダーにある独自のテスト用トークン、取引の場、アダプターによって、SepoliaとRobinhood Chainのテストネット(チェーン46630)にプロトコルをデプロイしました。9回目の監査ループ以降、プリフライトは、その2つのファイルと、SEPOLIA_RPC_URLおよびROBINHOOD_TESTNET_RPC_URLが与えられれば、その実行も確認します。この実行が何を証明し、何を証明しなかったかは、テストと検証にあります。

エアドロップ

2026-10-04以降、エアドロップのコントラクトはプロトコルとともにデプロイされます。その設定は環境変数から与えられます。

スクリプト 変数 デフォルト 役割
DeployProtocol AIRDROP_LZ_ENDPOINT なし:ローカルのレールのみ Robinhood Chain上で購入された株式のための、Ethereum上のLayerZeroのエンドポイント
DeployProtocol AIRDROP_REMOTE_EID エンドポイントがある場合は30416 Robinhood ChainのLayerZeroのエンドポイントID。配信の唯一の送信元
DeployProtocol AIRDROP_CYCLE_LENGTH 86,400(24時間) 時間枠の長さ(秒)。1時間の整数倍
DeployProtocol AIRDROP_CYCLE_OFFSET 46,800(13:00 UTC) 時間枠が終わる位置(00:00 UTCからの秒数)。1時間の整数倍で、年間を通じて米国市場の取引開始前
DeployRemote AIRDROP_DISTRIBUTOR なし ミラーボールトが送付する先の、Ethereum上のエアドロップのコントラクト
DeployRemote AIRDROP_RECEIVE_GAS、_MIN、_MAX 650,000、200,000、1,500,000 2026-10-06以降:キーパーがデフォルト値を求めた場合の、Ethereum上での各配信のlzReceiveのガス(株式のOFTが強制するガスへの上乗せ分)と、キーパーが求めることのできる値の下限と上限
DeployRemote AIRDROP_COMPOSE_GAS、_MIN、_MAX 1,250,000、600,000、4,000,000 各配信のエアドロップのコントラクトに対する呼び出しlzComposeのガス。仕組みは同じ。2026-10-06までは、1つの数値(600,000)がすべての配信のガスを決めていた
DeployRemote STOCK_ADAPTERS なし カンマ区切りのリスト。STOCKSの各エントリーにつきLayerZeroアダプター1つ。アダプターのない株式はゼロ

これらは初期値です。オーナーは後から、スケジュール(setCycleSchedule)とLayerZeroのエンドポイント(setLayerZero)を変更できます。DeployRemoteでは、AIRDROP_DISTRIBUTORとSTOCK_ADAPTERSは任意です。リモートハブの管理者が後から設定することもできます(setAirdrop、setStockAdapter)。2つのガスのポリシーは、実行のたびに、環境変数またはハブのデフォルト値から、分配コントラクトより前に設定されます。分配コントラクトのcomposeのガスは、その範囲内に収まっていなければならないからです。管理者は後からそれらを変更できます(setAirdropReceiveGas、setAirdropComposeGas)。uint128を超える値は実行を止めます。続いて、オーナーの手順です。

  • ファクトリー上のsetAirdropDistributor。DeployProtocolが行います。オーナーは後から別のコントラクトを指定できます。ボールトはそれをその都度読み取り、置き換えられた分配コントラクトでも、すべてのサイクルはそのコントラクト上で請求可能なままです
  • エアドロップのコントラクト上でのregisterStockOft。Ethereum上の各株式のOFTについて行い、そのOFTはコントラクト自身のLayerZeroのエンドポイントを使っていなければなりません
  • setExclusions。バーンアドレスのほかに除外すべきアドレスがあるトークンについてのみ行います。デフォルトでは、そのようなマーケットのトークンはありません。ローンチの実行者を含む$STOCKFUNのリストは、LaunchProtocolが設定します

株式アダプターそのもの(株式ごとに1つ:Robinhood Chain上のロックボックス型アダプターと、Ethereum上のそのOFT)は、メインネット用のものはリポジトリにありません。LayerZeroのoft-evmパッケージを必要とするからです。DeployRemoteはそのアドレスを受け取ります。2026-10-06のLayerZeroのテストネット実行は、テスト用のもの、つまりテスト用の株式の上のLayerZeroのOFTAdapterと、ラップされた株式のためのLayerZeroのOFTを、そのテストネット専用フォルダーで使いました。

Robinhood Chainからの各配信は、Ethereum上で2つの呼び出しとして実行されます。ラップされた株式をエアドロップのコントラクトにミントする、株式のOFTのlzReceiveと、それを計上する、エアドロップのコントラクトのlzComposeです。2026-10-06以降、キーパーは、Ethereum上でのシミュレーションから選んだ両方のガスを、送付のたびに指定し(キーパーを参照してください)、リモートハブは各値をそのポリシーの範囲内に収め、ゼロはデフォルト値を意味します。デフォルト値は、EthereumのGlamsterdamアップグレードの後にSepoliaで測定した最も重いケースを、それぞれ35 %と30 %の余裕をもってカバーします。そこでは、lzReceiveは、エアドロップのコントラクトがすでに保有している残高への配信で184,702ガス、株式の最初の配信で481,548ガスを必要とします。composeは、サイクルがすでにその株式を列挙している場合に105,075、キーパーが開いたサイクルがまだその株式を列挙していない場合に433,645、配信がそのサイクルの最初の計上でもある場合に約531,600、配信自身がサイクルを開く場合に962,154を必要とします。テストで構築した最も重い配信、つまり長い履歴を持つ16の除外されたホルダーに対してサイクルを開き、さらに4つの保留された株式を一緒に運ぶ配信は、Glamsterdamの価格でおよそ280万ガスを必要とし、composeの上限を下回ります。Glamsterdam以前には、テストでコールドの状態で測定すると、開いているサイクルへの配信が約85,000、除外されたアドレスが1つある状態でサイクルを開く配信が約275,000、最も重いものが約1,016,000でした。ガスが不足した配信は、何も失うことなく失敗します。配信はLayerZeroのエンドポイント上に保存されたままで、composeだけが失敗した場合は、ラップされた株式はすでにエアドロップのコントラクト上にあり、誰でもより多くのガスで再実行できます。2026-10-06以降は、キーパーが自身の上限の範囲内で自らそれを行い、2回目の失敗にはアラートを出します。

ファクトリーの接続設定

ファクトリーは、オーナーと3つのウォレットだけで初期化され、その設定はデフォルト値から始まります。それ以外はすべて、その後でオーナーが指定します。

  • setLaunchModules:フックとロック。一度限りの設定です。どちらもこのファクトリーを指定していなければならず、ロックはこのフックを指定していなければなりません
  • setDeployers:2つのデプロイヤー。置き換えることができます
  • setVaultImplementation:その後に作成されるマーケットのボールトの背後に置かれる実装
  • setHoldingRecorder:新しいマーケットのトークンの報告先となる保有量レコーダー
  • setAirdropDistributor:ボールトが株式を引き渡す先のエアドロップのコントラクト。このファクトリーを指定していなければなりません
  • setTreasuryRouter、setTreasuryOracle、setSwapRouter、setBridgeHub、setProtocolMarket

ロック、ボールトの実装、保有量レコーダーが指定されるまで、マーケットは作成できません。

ブリッジハブとスワップルーター

setBridgeHubは一度しか設定できません。設定し損ねると、取り返しがつきません。2026-10-05以降、すでに登録されたバスケットのすべての株式を対応付けられないハブは拒否されます。先にハブ上でそれらを対応付けてください。

setBridgeHubは、最初のマーケットが作成される前に呼び出す必要があります。すべてのTreasuryVaultは、コンストラクト時にブリッジハブのアドレスを固定します。アドレスがゼロのときに作成されたボールトは、永久にローカルのレールにとどまり、Robinhood Chainへ何かを送ることは決してありません。

setSwapRouterは、一度しか設定できない項目ではありません。オーナーはいつでも変更できます。もはやどのボールトにも影響しません。2026-09-28にクリエイターバイバックがコードから削除された時点で、ボールトはこれを読み取らなくなりました。これは公式のスワップルーターのアドレスを記録するもので、そのアドレスはプリフライトが確認します。

フックとマイニングされたアドレス

フックのアドレスは、v4の権限を下位ビットにエンコードしています。アドレスは、CREATE2のソルトを総当たりすることで見つけます。2026-10-02以降、マイニングされるのはフックのプロキシのアドレスで、14の権限ビットすべてが立っています。このアドレスは、プロキシのバイトコードと、実装のアドレスを含むそのコンストラクタ引数に依存します。新しくデプロイする場合は再マイニングが必要です。アップグレードしてもアドレスは変わりません。

foundry.tomlにはbytecode_hash = "none"とevm_version = "cancun"を記載する必要があります。そうしないと、マイニングしたアドレスがデプロイされたコントラクトと一致しません。

アップグレード

アップグレードとは、プロトコルオーナーがモジュールのプロキシに対して行う、新しい実装を指定する呼び出しです。アップグレードはただちに発効します。アップグレードの前には毎回、contracts/script/check-storage-layouts.shが、新しいストレージレイアウトをcontracts/storage-layouts/に記録されているものと比較し、追加以外の変更があれば失敗します。2026-10-05以降、このスクリプトは、すべての構造体をサイズも含めてあらゆる深さで比較し、ストレージ配列の要素である構造体への変更はすべて拒否します。拡張できるのは、マッピングの値である構造体か最後の状態変数だけで、それもその末尾に限られます。--writeは、意図的な変更の後に記録を更新します。

リモートハブのガスのポリシーは、2026-10-06の9回目の監査ループとともに導入されました。それより前にデプロイされ、その後アップグレードされたハブは、両方のポリシーをゼロとして読み取り、対応するコードにアップグレードされたミラーボールトは、両方が設定されるまで、エアドロップへのすべての送付と見積もりを拒否します(AirdropGasNotSet)。そのため、順序は次のとおりです。リモートハブをアップグレードし、両方のポリシーを設定し(setAirdropReceiveGas、setAirdropComposeGas)、次に新しいミラーボールトの実装を指定して各ミラーボールトをアップグレードし、その後で初めて、ハブにポリシーを問い合わせて3つの引数で送付する9回目のループのキーパーを起動します。その間、それ以前のキーパーは、ハブのデフォルト値で送付を続けます。新しいコードでデプロイされたハブは、初期化時にデフォルト値を設定します。

USDGアダプターのバッチ設定は、2026-10-06の10回目の監査ループで導入されました。ブリッジバッチの各マーケットが加えるcomposeのガスと、1つのバッチが運ぶマーケットの最大数です。それ以前にデプロイされ、アップグレードされたアダプターは、両方をゼロと読み、それらが設定されるまで、すべてのバッチと見積もりを拒否します(BatchGasNotSet)。そのため、アダプターのアップグレードは同じトランザクションでそれらを設定し(upgradeToAndCallとsetBatchGas(400000, 17))、次にオーナーが古いcomposeのガス1,200,000を、今ではすべてのバッチが必要とする基本分に下げ(setSettings、200,000)、それから初めて、バッチごとに上限を読み取る10回目のループのキーパーを起動します。それ以前のキーパーは、同時に準備ができているマーケットが17以下である限り、アップグレードされたアダプターで動作し続けます。テストネットのアダプターは2026-10-06にこの方法でアップグレードされ、次のバッチは新しいcomposeのガスで通りました。

アプリのデータサービスが読み取る内容を変えるアップグレードは、そのサービスより先に本番に反映されます。2026-10-06以降、Lensは各ボールトの状態と、フックとロックがそのボールトに対して負っている額を含んでおり、それらのフィールドを読み取るWorkerとアプリは、それより古いLensを読み取れません。先にLensをアップグレードしてください。7回目の監査ループは、Lensも、Workerが公開するものの形(スキーマ7)も変えません。そのWorkerとアプリは、どちらを先にデプロイしても構いません。8回目は形を変えますが(スキーマ8:Robinhood Chainのオラクルが株式の価格を差し止めている場合、各株式の価格はそれが欠けている理由を示します)、Lensは変えず、そのWorkerとアプリも、どちらを先にデプロイしても構いません。古いアプリは理由を無視し、このアプリは理由のない古いWorkerを読み取ります。9回目はスキーマ8を維持します。

Robinhoodレールのオラクルは、他のTreasuryOracleと同様に、2つのガードがオフの状態で始まります。そのため、リモートハブ上で指定された代わりのオラクル(setOracle)も、それらがオフの状態で始まり、ハブの管理者は、デプロイスクリプトと同じように、そのオラクルについてそれらをもう一度オンにします。各株式のオラクル一時停止チェックと、設定されていた場合はシーケンサーのフィードです。すでに初期化されたミラーボールトは、初期化されたときのオラクルを保持します。

設定

設定とは、プロトコルオーナーが、またはRobinhood Chainではリモートハブの管理者が、その設定を保持するモジュールに対して行う呼び出しです。設定はただちに発効し、イベントを発行します。すべてのモジュールは、信頼モデルに記載されたデフォルト値から始まります。ブリッジアダプターは、デプロイ時のガスから始まります。USDGのレールではCOMPOSE_GAS、つまりバッチの最後のステップのうちすべてのバッチが必要とする部分(10回目の監査ループ以降、DeployBridgeでのデフォルトは200,000。それまではステップ全体で1,200,000)に、バッチのマーケットごとに400,000を加え、1つのバッチは最大17マーケット(setBatchGas。最大のバッチのガスは最大でも24,000,000です)と、30ベーシスポイントのCurveの許容幅、正規ブリッジのレールでは2つのチケットのガスと、2026-10-05以降は、入金のチケットのコストの計算に使うバイト数です(DeployTestnetBridgeのDEPOSIT_CALLDATA_LENGTH。ゼロの場合はデフォルトの1,024)。リモートハブは、エアドロップ配信の2つのガスのポリシーとともに始まります(前述)。

メインネットの前に

  • 緊急モードの完全なリハーサル:移転、一時停止、一時停止の解除
  • 一般公開の前に、検証用ボールトで小規模な初回エアドロップを行う。2026-10-06のLayerZeroのテストネット実行は、LayerZeroのテストネットのエンドポイント、DVN、エグゼキューターの上で、StockFunのコードをエンドツーエンドで実行しました。この実行は、PaxosのUSDGのペアも、Robinhoodの株式とそのアダプターも、実際のフィードと流動性も、メインネットのガス、手数料、ファイナリティも証明しません
  • リモートチェーン上の価格フィードに関する最新の調査
  • プリフライトによる、USDG OFTのLayerZeroのピアと、USDGの一時停止状態の検証。8回目の監査ループ以降、プリフライトは、オラクルのガードもそのマニフェストと照合します。マニフェストは、シーケンサーのチェックの状態(現時点ではオフ)を明示しなければなりません
  • PaxosのUSDGがRobinhood Chainへ向かう経路のサイズ制限。LayerZeroの送信ライブラリから読み取り(getExecutorConfig)、それが10,000バイトでなければ、ブリッジバッチのマーケット数の上限をそれに合わせて設定します。最大で(サイズ − 392) ÷ 544マーケットです(10回目の監査ループ以降)
  • 外部レビュー — 既存の形式証明はクロスチェーンのレールをカバーしていない