Deploy

Il deploy del protocollo avviene su due chain, in un ordine che non è negoziabile.

I wallet

Tre ruoli distinti, mai la stessa chiave.

Wallet Cosa può fare
Owner del protocollo Fare l'upgrade di ogni modulo tranne i token, il lock della liquidità e il deployer dei vault speculari; collegare i moduli alla factory; registrare i basket; impostare gli indirizzi a scrittura singola; cambiare le impostazioni del protocollo; impostare le liste di esclusione dell'airdrop e registrare l'OFT di ogni azione; spostare asset in caso di emergenza, subito; portare fuori ciò che un modulo detiene per errore (rescue); avviare o annullare la modalità di chiusura, e recuperare la liquidità una volta trascorsi i suoi 30 giorni
Keeper Attivare conversioni, batch del bridge e l'airdrop: aprire i cicli, inviare le azioni, collocare le azioni messe da parte. Dal 2026-10-05 anche pagare ciò che l'hook e il lock conservano per un destinatario e incassare le commissioni LP, chiamate aperte a chiunque
Deployer Fare il deploy dei contratti. Possiede la factory finché l'owner del protocollo non ne accetta la proprietà, e amministra l'hub remoto finché il primo batch del bridge non vi indica l'owner del protocollo

Gli script prendono il loro firmatario dalla riga di comando di forge o da una DEPLOYER_PRIVATE_KEY in chiaro nell'ambiente. DeployEthereumRail, DeployProtocol, DeployRemote e DeployBridge accettano entrambe le soluzioni; LaunchProtocol, RegisterBaskets e CreateMarket leggono solo DEPLOYER_PRIVATE_KEY.

Ogni trasmissione (broadcast) si fa con --slow --skip-simulation, dal decimo ciclo di audit. Senza, forge dà a ogni transazione il gas che la sua simulazione ha contato, ai prezzi di prima dell'aggiornamento Glamsterdam di Ethereum, e la creazione di un contratto ne richiede da quattro a sette volte tanto dopo di esso: ogni creazione resterebbe senza gas. Con questi flag, forge prende la stima del nodo per ogni transazione, una volta minata la precedente. Le cifre di gas di un dry run non sono quindi un budget: con Glamsterdam, DeployProtocol richiede circa 240 milioni di gas, e il lancio di ogni mercato da 15 a 24 milioni.

L'ordine

Tre vincoli lo determinano. Ogni modulo Ethereum riceve l'indirizzo della factory alla costruzione, quindi la factory viene per prima, e il suo owner vi collega il resto in seguito. Il router e l'oracolo del rail Ethereum, come il bridge hub, sono legati alla factory, quindi vengono dopo il protocollo. I due hub si fissano a vicenda tramite indirizzo previsto.

  1. DeployProtocol: prima la factory, poi l'hook, minato per tutti i 14 permessi v4, il lock, il registratore della detenzione, i deployer, l'implementazione dei vault, la lens, lo swap router e il contratto dell'airdrop, AirdropDistributor, tutti collegati alla factory. La proprietà passa poi all'owner del protocollo, che deve accettarla
  2. DeployEthereumRail, con l'indirizzo della factory: l'oracolo e il router ETH → USDC su Ethereum, che l'owner imposta sulla factory
  3. DeployBridge --sig "predict()": stampa gli indirizzi che riceveranno il bridge hub e il suo adapter
  4. DeployRemote su Robinhood Chain: prima l'hub remoto, fissato su quegli indirizzi previsti, poi lo stock router, l'oracolo e l'implementazione dei vault speculari, che l'admin dell'hub vi collega, con la rotta dell'airdrop e gli adapter delle azioni quando vengono forniti (sotto). Dal 2026-10-06 imposta a ogni esecuzione le due politiche di gas dell'hub remoto per le consegne dell'airdrop su Ethereum, prima della rotta dell'airdrop (sotto), e le due protezioni dell'oracolo, e si rifiuta di partire senza una decisione sul controllo del sequencer: SEQUENCER_UPTIME_FEED, il feed di uptime del sequencer L2 di Chainlink su Robinhood Chain, o SEQUENCER_CHECK_OFF=true, il controllo disattivato per scelta, mai entrambi, con SEQUENCER_GRACE_PERIOD (3.600 secondi di default) solo accanto a un feed. Chainlink non pubblica alcun feed del genere per Robinhood Chain, quindi un'esecuzione sulla mainnet oggi imposta SEQUENCER_CHECK_OFF=true, e l'owner di StockFun imposta il feed in seguito (setSequencerUptimeFeed) se ne viene pubblicato uno. Lo script attiva poi la pausa dell'oracolo di ogni azione (setOraclePauseCheck), dopo l'hub, il router e l'oracolo, così nessun indirizzo previsto si sposta
  5. DeployBridge: il bridge hub e il suo adapter, agli indirizzi previsti; l'owner indica l'adapter sull'hub, una sola volta (setAdapter). Dal 2026-10-05 lo script si ferma prima di fare il deploy di qualsiasi cosa quando la factory indica già un hub (dal 2026-10-06 è il suo primo controllo, prima degli indirizzi previsti) e, quando il suo firmatario è l'owner della factory, mappa le azioni prima di indicare l'hub
  6. setBridgeHub — prima del primo mercato. Dal 2026-10-05 rifiuta un hub che non può trasportare un basket già registrato (UnmappedBridgeStock)
  7. addStockMapping sul bridge hub, per ogni azione di un basket
  8. Registrare i basket: PlanBridgeBaskets stampa le chiamate dell'owner. Dal 2026-10-06 un basket comprende al massimo cinque azioni: vedi I basket
  9. LaunchProtocol: $STOCKFUN, la cui intera supply va nella sua posizione bloccata, il suo vault, il BuybackBurner e setBuybackWallet. Richiede il contratto dell'airdrop già indicato, cosa che fa DeployProtocol: dal 2026-10-05 mette l'operatore del lancio nella lista di esclusione di $STOCKFUN prima del conio, sull'indirizzo che il token assumerà. Dal 2026-10-06 un'esecuzione che si è fermata prima che il mercato del protocollo fosse indicato riprende con il token e il vault che ha lasciato (--sig "resume(address,address)"), verificati per primi, invece di coniare un secondo $STOCKFUN; una volta indicato il mercato del protocollo, lo script non gira più, e i passaggi rimanenti si fanno a mano
  10. registerStockOft sul contratto dell'airdrop, da parte dell'owner, per l'OFT di ogni azione su Ethereum

Ogni modulo upgradabile viene deployato come due contratti, prima la sua implementazione e poi il suo proxy. predict() li conta: il proxy del bridge hub arriva al nonce + 1 del deployer, quello del suo adapter al nonce + 3.

StockFun non accoppia alcun peer LayerZero: i peer dell'OFT USDG appartengono al suo emittente, e il preflight si limita a verificarli.

Dal 2026-10-06 lo script locale e quello di testnet, LocalRun e DeployTestnetBridge, scrivono il loro file di deploy solo quando inviano davvero le loro transazioni (broadcast), e DeployTestnetRail anche lui dal nono ciclo di audit: gli indirizzi di un dry run non contengono codice. Sulla testnet, DeployTestnetRail riceve gli stessi tre input del sequencer, tutti facoltativi (senza feed il controllo resta disattivato, dato che Chainlink non ne elenca nessuno nemmeno per la testnet), e attiva la pausa dell'oracolo di ogni azione; gli script di Ethereum lasciano disattivate entrambe le protezioni. Un keeper di testnet gira con KEEPER_REQUIRE_MARKET_OPEN, KEEPER_AIRDROP_AFTER_SESSION e, dal 2026-10-06, KEEPER_CONVERT_ONCE_PER_WINDOW impostate a false, così converte a ogni giro: vedi Il keeper. Dal settimo ciclo di audit, il 2026-10-06, il keeper verifica la chain di ogni RPC rispetto alla sua configurazione prima di partire: un keeper di testnet imposta KEEPER_CHAIN_ID=11155111 e, con il bridge, KEEPER_REMOTE_CHAIN_ID=46630, e un keeper di mainnet KEEPER_CHAIN_ID=1 con 4663. Dall'ottavo ciclo di audit entrambi sono obbligatori: il keeper si rifiuta di partire senza KEEPER_CHAIN_ID, o senza KEEPER_REMOTE_CHAIN_ID accanto al bridge. Il Worker dati dell'app verifica allo stesso modo la chain dei suoi endpoint, e i suoi RPC pubblici seguono i suoi due id di chain, così un Worker di testnet ha bisogno solo di quelli.

L'esecuzione su testnet con LayerZero del 2026-10-06 ha deployato il protocollo su Sepolia e sulla testnet di Robinhood Chain (chain 46630) con gli script di produzione, o con wrapper di testnet che ne mantengono il corpo, e i propri token, sedi di esecuzione e adapter di test, in una cartella dei contratti riservata alla testnet. Dal nono ciclo di audit il preflight verifica anche quell'esecuzione, a partire dai suoi due file, con SEPOLIA_RPC_URL e ROBINHOOD_TESTNET_RPC_URL. Ciò che l'esecuzione ha dimostrato, e ciò che non ha dimostrato, è in Test e verifica.

L'airdrop

Dal 2026-10-04 il contratto dell'airdrop viene deployato con il protocollo. Le sue impostazioni vengono dall'ambiente:

Script Variabile Default Ruolo
DeployProtocol AIRDROP_LZ_ENDPOINT Nessuno: solo il rail locale Endpoint LayerZero su Ethereum, per le azioni comprate su Robinhood Chain
DeployProtocol AIRDROP_REMOTE_EID 30416 con un endpoint L'id dell'endpoint LayerZero di Robinhood Chain, l'unica origine di una consegna
DeployProtocol AIRDROP_CYCLE_LENGTH 86.400 (24 ore) Durata di una finestra, in secondi, un numero intero di ore
DeployProtocol AIRDROP_CYCLE_OFFSET 46.800 (13:00 UTC) Dove terminano le finestre, in secondi dopo le 00:00 UTC, un numero intero di ore: prima dell'apertura USA tutto l'anno
DeployRemote AIRDROP_DISTRIBUTOR Nessuno Il contratto dell'airdrop su Ethereum a cui inviano i vault speculari
DeployRemote AIRDROP_RECEIVE_GAS, _MIN, _MAX 650.000, 200.000, 1.500.000 Dal 2026-10-06: il gas di lzReceive di ogni consegna su Ethereum, oltre a ciò che impone l'OFT dell'azione, quando il keeper chiede il default, e il minimo e il massimo di ciò che può chiedere
DeployRemote AIRDROP_COMPOSE_GAS, _MIN, _MAX 1.250.000, 600.000, 4.000.000 Il gas della chiamata di ogni consegna sul contratto dell'airdrop, lzCompose, allo stesso modo. Fino al 2026-10-06 un'unica cifra, 600.000, valeva per ogni consegna
DeployRemote STOCK_ADAPTERS Nessuno Lista separata da virgole, un adapter LayerZero per ogni voce di STOCKS, zero per un'azione che non ne ha

Questi sono valori iniziali: l'owner può cambiare in seguito la programmazione dei cicli (setCycleSchedule) e l'endpoint LayerZero (setLayerZero). Su DeployRemote, AIRDROP_DISTRIBUTOR e STOCK_ADAPTERS sono facoltative: l'admin dell'hub remoto può impostarle in seguito (setAirdrop, setStockAdapter). Le due politiche di gas vengono impostate a ogni esecuzione, dall'ambiente o dai default dell'hub, prima del distributore, il cui gas di compose deve rientrare in esse; l'admin può cambiarle in seguito (setAirdropReceiveGas, setAirdropComposeGas). Un valore oltre uint128 interrompe l'esecuzione. Seguono i passaggi dell'owner:

  • setAirdropDistributor sulla factory, eseguito da DeployProtocol. L'owner può indicarne un altro in seguito; i vault lo leggono in tempo reale, e un distributore sostituito mantiene ogni ciclo riscuotibile dove si trova
  • registerStockOft sul contratto dell'airdrop, per l'OFT di ogni azione su Ethereum, che deve usare lo stesso endpoint LayerZero del contratto
  • setExclusions, solo per un token che ha bisogno di escludere indirizzi oltre all'indirizzo di burn: di default nessun token di mercato; la lista di $STOCKFUN, con il suo operatore del lancio, la imposta LaunchProtocol

Gli adapter delle azioni, uno per azione — l'adapter lockbox su Robinhood Chain e il suo OFT su Ethereum — non sono nel repository per la mainnet: richiedono il pacchetto oft-evm di LayerZero. DeployRemote ne riceve gli indirizzi. L'esecuzione su testnet con LayerZero del 2026-10-06 ha usato adapter di test, l'OFTAdapter di LayerZero su azioni di test e l'OFT di LayerZero per le azioni wrapped, nella sua cartella riservata alla testnet.

Ogni consegna da Robinhood Chain viene eseguita su Ethereum in due chiamate: il lzReceive dell'OFT dell'azione, che conia l'azione wrapped al contratto dell'airdrop, poi il lzCompose del contratto dell'airdrop, che la accredita. Dal 2026-10-06 il keeper indica il gas di entrambe a ogni invio, scelto da simulazioni su Ethereum (vedi Il keeper), e l'hub remoto riporta ogni valore entro la sua politica, e zero prende il default. I default coprono con un margine del 35 % e del 30 % i casi più pesanti misurati su Sepolia dopo l'aggiornamento Glamsterdam di Ethereum: lì un lzReceive richiede 184.702 gas verso un saldo che il contratto dell'airdrop detiene già, e 481.548 per la primissima consegna di un'azione; un compose 105.075 quando il ciclo elenca già l'azione, 433.645 quando il ciclo aperto dal keeper non la elenca ancora, circa 531.600 quando la consegna è anche il primo accredito del ciclo, e 962.154 quando apre essa stessa il ciclo. La consegna più pesante costruita nei test, un'apertura con sedici holder esclusi dalla lunga storia che porta con sé anche quattro azioni messe da parte, richiede circa 2,8 milioni ai prezzi di Glamsterdam, sotto il massimo del compose. Prima di Glamsterdam, misurato a freddo nei test, una consegna in un ciclo aperto richiedeva circa 85.000, una che apre un ciclo con un indirizzo escluso circa 275.000, e la più pesante circa 1.016.000. Una consegna rimasta senza gas fallisce senza perdere nulla: resta memorizzata sull'endpoint di LayerZero, con le azioni wrapped già sul contratto dell'airdrop quando è fallito solo il compose, e chiunque può rieseguirla con più gas. Dal 2026-10-06 lo fa il keeper stesso, entro un proprio limite, e segnala con un'allerta un secondo fallimento.

Il collegamento della factory

La factory viene inizializzata solo con il suo owner e i suoi tre wallet; le sue impostazioni partono dai valori di default. Tutto il resto lo indica l'owner in seguito:

  • setLaunchModules: l'hook e il lock, una volta per tutte; entrambi devono indicare questa factory, e il lock questo hook
  • setDeployers: i due deployer, che possono essere sostituiti
  • setVaultImplementation: l'implementazione dietro i vault dei mercati creati in seguito
  • setHoldingRecorder: il registratore a cui i nuovi token di mercato inviano le loro notifiche
  • setAirdropDistributor: il contratto dell'airdrop a cui i vault consegnano le loro azioni; deve indicare questa factory
  • setTreasuryRouter, setTreasuryOracle, setSwapRouter, setBridgeHub e setProtocolMarket

Nessun mercato può essere creato finché il lock, un'implementazione dei vault e un registratore della detenzione non sono stati indicati.

Il bridge hub e lo swap router

setBridgeHub può essere impostato una sola volta. Un errore su di esso non si può annullare. Dal 2026-10-05 rifiuta un hub che non sa tradurre ogni azione dei basket già registrati: mappale prima sull'hub.

setBridgeHub deve essere chiamato prima che venga creato il primo mercato. Ogni TreasuryVault fissa l'indirizzo del bridge hub alla costruzione. Un vault creato mentre l'indirizzo è zero resta per sempre sul rail locale e non invia mai nulla verso Robinhood Chain.

setSwapRouter non è a scrittura singola: l'owner può cambiarlo in qualsiasi momento. Non vincola più alcun vault: i vault hanno smesso di leggerlo quando il buyback del creatore è stato rimosso dal codice, il 2026-09-28. Registra l'indirizzo dello swap router ufficiale, che il preflight verifica.

L'hook e il suo indirizzo minato

L'indirizzo di un hook codifica i suoi permessi v4 nei bit meno significativi: lo si trova con un brute force sul salt CREATE2. Dal 2026-10-02 l'indirizzo minato è quello del proxy dell'hook, con tutti i 14 bit di permesso impostati. Dipende dal bytecode del proxy e dagli argomenti del suo costruttore, che contengono l'indirizzo dell'implementazione. Un nuovo deploy va minato di nuovo; un upgrade mantiene l'indirizzo.

foundry.toml deve contenere bytecode_hash = "none" e evm_version = "cancun", altrimenti l'indirizzo minato non corrisponderà al contratto deployato.

Gli upgrade

Un upgrade è una chiamata dell'owner del protocollo sul proxy del modulo, che indica la nuova implementazione; ha effetto subito. Prima di ogni upgrade, contracts/script/check-storage-layouts.sh confronta il nuovo layout dello storage con quello registrato in contracts/storage-layouts/, e fallisce su qualsiasi modifica che non sia un'aggiunta. Dal 2026-10-05 confronta ogni livello di ogni struct, dimensioni comprese, e rifiuta qualsiasi modifica a una struct che sia l'elemento di un array dello storage: solo una struct che sia il valore di un mapping, o l'ultima variabile di stato, può crescere, in coda. --write rigenera i layout registrati dopo una modifica voluta.

Le politiche di gas dell'hub remoto sono arrivate con il nono ciclo di audit, il 2026-10-06. Un hub deployato prima di esse e poi aggiornato con un upgrade legge entrambe le politiche come zero, e un vault speculare aggiornato al codice corrispondente rifiuta ogni invio all'airdrop e ogni quotazione (AirdropGasNotSet) finché entrambe non sono impostate. L'ordine è quindi: fare l'upgrade dell'hub remoto, impostare entrambe le politiche (setAirdropReceiveGas, setAirdropComposeGas), poi indicare la nuova implementazione dei vault speculari e fare l'upgrade di ogni vault speculare, e solo allora avviare un keeper del nono ciclo, che chiede le politiche all'hub e invia con tre argomenti. Un keeper precedente continua nel frattempo a inviare con i default dell'hub. Un hub deployato con il nuovo codice imposta i default all'inizializzazione.

Le impostazioni di batch dell'adapter USDG sono arrivate con il decimo ciclo di audit, il 2026-10-06: il gas di compose che aggiunge ogni mercato di un batch del bridge, e il massimo di mercati che porta un batch. Un adapter deployato prima di esse e poi aggiornato con un upgrade le legge entrambe come zero e rifiuta ogni batch e ogni quotazione (BatchGasNotSet) finché non sono impostate. Il suo upgrade quindi le porta nella stessa transazione (upgradeToAndCall con setBatchGas(400000, 17)), poi l'owner abbassa il suo vecchio gas di compose, 1.200.000, alla base che ora richiede ogni batch (setSettings, 200.000), e solo allora avvia un keeper del decimo ciclo, che legge il tetto a ogni batch. Un keeper precedente continua a funzionare con l'adapter aggiornato finché non ci sono più di 17 mercati pronti insieme. L'adapter della testnet è stato aggiornato così il 2026-10-06, e il suo batch successivo è passato con il suo nuovo gas di compose.

Un upgrade che cambia ciò che legge il servizio dati dell'app va in produzione prima di quel servizio. Dal 2026-10-06 la Lens riporta lo stato di ogni vault e ciò che l'hook e il lock gli devono, e il Worker e l'app che leggono quei campi non sanno leggere una Lens precedente: fai prima l'upgrade della Lens. Il settimo ciclo di audit non cambia né la Lens né la forma di ciò che il Worker pubblica (schema 7): il suo Worker e la sua app si deployano in qualsiasi ordine. L'ottavo cambia la forma (schema 8: ogni prezzo di azione dice perché manca, quando l'oracolo di Robinhood Chain lo trattiene), non la Lens, e il suo Worker e la sua app si deployano ancora in qualsiasi ordine: un'app più vecchia ignora il motivo, e questa app legge un Worker più vecchio senza di esso. Il nono mantiene lo schema 8.

L'oracolo del rail Robinhood, come qualsiasi TreasuryOracle, parte con le sue due protezioni disattivate. Un oracolo sostitutivo indicato sull'hub remoto (setOracle) parte quindi anch'esso con le protezioni disattivate, e l'admin dell'hub le riattiva per esso, come fa lo script di deploy: la pausa dell'oracolo di ogni azione, e il feed del sequencer se ne era stato impostato uno. I vault speculari già inizializzati conservano l'oracolo con cui sono stati inizializzati.

Le impostazioni

Un'impostazione è una chiamata dell'owner del protocollo sul modulo che la detiene o, su Robinhood Chain, dell'admin dell'hub remoto; ha effetto subito ed emette un evento. Ogni modulo parte dai valori di default elencati in Modello di fiducia. Gli adapter del bridge partono dal gas del deploy: sul rail USDG COMPOSE_GAS, la parte dell'ultimo passo di un batch che richiede ogni batch, 200.000 di default in DeployBridge dal decimo ciclo di audit (1.200.000 per l'intero passo fino ad allora), più 400.000 per ogni mercato del batch e al massimo 17 mercati per batch (setBatchGas; il gas del batch più grande, al massimo 24.000.000), accanto a un limite su Curve di 30 punti base; sul rail canonico il gas dei due ticket e, dal 2026-10-05, i byte su cui si calcola il costo del ticket di deposito (DEPOSIT_CALLDATA_LENGTH in DeployTestnetBridge; zero prende il valore di default, 1.024). L'hub remoto parte con le due politiche di gas delle consegne dell'airdrop (sopra).

Prima della mainnet

  • Una prova completa della modalità di emergenza: trasferimento, pausa, fine della pausa
  • Un primo piccolo airdrop su un vault di verifica, prima di qualsiasi apertura al pubblico. L'esecuzione su testnet con LayerZero del 2026-10-06 ha fatto girare il codice di StockFun da un capo all'altro sugli endpoint, sul DVN e sull'executor di testnet di LayerZero; non dimostra né la coppia USDG di Paxos, né le azioni di Robinhood e i loro adapter, né feed e liquidità reali, né il gas, le commissioni e la finalità della mainnet
  • Un rilevamento aggiornato dei feed di prezzo sulla chain remota
  • Verifica dei peer LayerZero dell'OFT USDG e dello stato di pausa di USDG, tramite preflight; dall'ottavo ciclo di audit il preflight verifica anche le protezioni dell'oracolo rispetto al suo manifest, che deve indicare il controllo del sequencer, oggi disattivato
  • Il limite di dimensione del percorso che l'USDG di Paxos segue verso Robinhood Chain, letto dalla libreria di invio di LayerZero (getExecutorConfig), e il tetto di mercati di un batch del bridge impostato di conseguenza se non è di 10.000 byte: al massimo (dimensione − 392) ÷ 544 mercati (dal decimo ciclo di audit)
  • Revisione esterna — le prove formali esistenti non coprono il rail cross-chain