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.
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 accettarlaDeployEthereumRail, con l'indirizzo della factory: l'oracolo e il router ETH → USDC su Ethereum, che l'owner imposta sulla factoryDeployBridge --sig "predict()": stampa gli indirizzi che riceveranno il bridge hub e il suo adapterDeployRemotesu 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, oSEQUENCER_CHECK_OFF=true, il controllo disattivato per scelta, mai entrambi, conSEQUENCER_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 impostaSEQUENCER_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 spostaDeployBridge: 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'hubsetBridgeHub— prima del primo mercato. Dal 2026-10-05 rifiuta un hub che non può trasportare un basket già registrato (UnmappedBridgeStock)addStockMappingsul bridge hub, per ogni azione di un basket- Registrare i basket:
PlanBridgeBasketsstampa le chiamate dell'owner. Dal 2026-10-06 un basket comprende al massimo cinque azioni: vedi I basket LaunchProtocol:$STOCKFUN, la cui intera supply va nella sua posizione bloccata, il suo vault, ilBuybackBurneresetBuybackWallet. Richiede il contratto dell'airdrop già indicato, cosa che faDeployProtocol: dal 2026-10-05 mette l'operatore del lancio nella lista di esclusione di$STOCKFUNprima 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 manoregisterStockOftsul 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:
setAirdropDistributorsulla factory, eseguito daDeployProtocol. 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 trovaregisterStockOftsul contratto dell'airdrop, per l'OFT di ogni azione su Ethereum, che deve usare lo stesso endpoint LayerZero del contrattosetExclusions, 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 impostaLaunchProtocol
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 hooksetDeployers: i due deployer, che possono essere sostituitisetVaultImplementation: l'implementazione dietro i vault dei mercati creati in seguitosetHoldingRecorder: il registratore a cui i nuovi token di mercato inviano le loro notifichesetAirdropDistributor: il contratto dell'airdrop a cui i vault consegnano le loro azioni; deve indicare questa factorysetTreasuryRouter,setTreasuryOracle,setSwapRouter,setBridgeHubesetProtocolMarket
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