Despliegue
El protocolo se despliega en dos cadenas, en un orden que no es negociable.
Las wallets
Tres roles distintos, nunca la misma clave.
| Wallet | Lo que puede hacer |
|---|---|
| Owner del protocolo | Hacer un upgrade de cualquier módulo salvo los tokens, el lock de liquidez y el deployer de los vaults espejo; conectar la factory; registrar los baskets; fijar las direcciones de escritura única; cambiar los ajustes del protocolo; fijar las listas de exclusiones del airdrop y registrar el OFT de cada acción; sacar activos en caso de emergencia, de inmediato; sacar lo que un módulo guarda por error (rescue); iniciar o cancelar el modo de cierre, y recuperar la liquidez una vez transcurridos sus 30 días |
| Keeper | Disparar las conversiones, los lotes de bridge y el airdrop: abrir ciclos, enviar acciones, colocar las acciones apartadas. Desde el 2026-10-05, también pagar lo que el hook y el lock guardan para un destinatario y cobrar las comisiones de LP, llamadas abiertas a cualquiera |
| Deployer | Desplegar los contratos. Posee la factory hasta que el owner del protocolo acepta la propiedad, y administra el hub remoto hasta que el primer lote del bridge designa allí al owner del protocolo |
Los scripts toman su firmante de la línea de comandos de forge o de una
DEPLOYER_PRIVATE_KEY en bruto desde el entorno. DeployEthereumRail, DeployProtocol,
DeployRemote y DeployBridge aceptan ambas opciones; LaunchProtocol,
RegisterBaskets y CreateMarket solo leen DEPLOYER_PRIVATE_KEY.
Cada difusión (broadcast) se hace con --slow --skip-simulation, desde el décimo bucle de
auditoría. Sin ellos, forge da a cada transacción el gas que contó su propia simulación, a los
precios anteriores a la actualización Glamsterdam de Ethereum, y una creación de contrato
necesita de cuatro a siete veces más después de ella: cada creación se quedaría sin gas. Con
ellos, forge toma la estimación del nodo para cada transacción, una vez minada la anterior. Las
cifras de gas de un dry run tampoco son un presupuesto: con Glamsterdam, DeployProtocol
necesita unos 240 millones de gas, y el lanzamiento de cada mercado de 15 a 24 millones.
El orden
Lo determinan tres restricciones. Cada módulo de Ethereum recibe la dirección de la factory en su construcción, así que la factory va primero, y su owner le conecta el resto después. El router y el oráculo del rail de Ethereum, como el hub del bridge, están vinculados a la factory, así que van después del protocolo. Los dos hubs se fijan mutuamente por dirección predicha.
DeployProtocol: primero la factory, luego el hook, minado para los 14 permisos v4, el lock, el registrador de tenencias, los deployers, la implementación de los vaults, la lens, el router de swap y el contrato del airdrop,AirdropDistributor, todos conectados a la factory. Después, la propiedad pasa al owner del protocolo, que debe aceptarlaDeployEthereumRail, con la dirección de la factory: el oráculo y el router ETH → USDC en Ethereum, que el owner fija en la factoryDeployBridge --sig "predict()": muestra las direcciones que recibirán el hub del bridge y su adaptadorDeployRemoteen Robinhood Chain: primero el hub remoto, fijado a esas direcciones predichas, luego el router de acciones, el oráculo y la implementación de los vaults espejo, que el admin del hub le conecta, con la ruta del airdrop y los adaptadores de acciones cuando se indican (más abajo). Desde el 2026-10-06 fija en cada ejecución las dos políticas de gas del hub remoto para las entregas del airdrop en Ethereum, antes de la ruta del airdrop (más abajo), y las dos salvaguardas del oráculo, y se niega a arrancar sin una decisión sobre la comprobación del secuenciador:SEQUENCER_UPTIME_FEED, el feed de disponibilidad del secuenciador L2 de Chainlink en Robinhood Chain, oSEQUENCER_CHECK_OFF=true, la comprobación desactivada por elección, nunca ambos, conSEQUENCER_GRACE_PERIOD(3 600 segundos por defecto) solo junto a un feed. Chainlink no publica ningún feed así para Robinhood Chain, así que una ejecución en mainnet hoy fijaSEQUENCER_CHECK_OFF=true, y el owner de StockFun fija el feed más tarde (setSequencerUptimeFeed) si se publica uno. Luego el script activa la pausa del oráculo de cada acción (setOraclePauseCheck), tras el hub, el router y el oráculo, para que no se mueva ninguna dirección predichaDeployBridge: el hub del bridge y su adaptador, en las direcciones predichas; el owner designa el adaptador en el hub, una sola vez (setAdapter). Desde el 2026-10-05, el script se detiene antes de desplegar nada cuando la factory ya designa un hub (desde el 2026-10-06, es su primera comprobación, antes de las direcciones predichas) y, cuando su firmante es el owner de la factory, mapea las acciones antes de designar el hubsetBridgeHub, antes del primer mercado. Desde el 2026-10-05 rechaza un hub que no puede transportar un basket ya registrado (UnmappedBridgeStock)addStockMappingen el hub del bridge, para cada acción de un basket- Registrar los baskets:
PlanBridgeBasketsmuestra las llamadas del owner. Desde el 2026-10-06, un basket contiene como máximo cinco acciones: consulta Los baskets LaunchProtocol:$STOCKFUN, cuyo supply completo va a su posición bloqueada, su vault, elBuybackBurnerysetBuybackWallet. Necesita que el contrato del airdrop esté designado antes, lo que haceDeployProtocol: desde el 2026-10-05, incluye al operador del lanzamiento en la lista de exclusiones de$STOCKFUNantes de la acuñación, en la dirección que tomará el token. Desde el 2026-10-06, una ejecución que se detuvo antes de que se designara el mercado del protocolo se reanuda con el token y el vault que dejó (--sig "resume(address,address)"), que se comprueban primero, en lugar de acuñar un segundo$STOCKFUN; una vez designado el mercado del protocolo, el script ya no se ejecuta, y los pasos restantes se hacen a manoregisterStockOften el contrato del airdrop, por parte del owner, para el OFT de cada acción en Ethereum
Cada módulo upgradeable se despliega como dos contratos, primero su implementación y luego
su proxy. predict() los cuenta: el proxy del hub del bridge queda en el nonce + 1 del
deployer, el de su adaptador en el nonce + 3.
StockFun no empareja ningún peer de LayerZero: los peers del OFT de USDG pertenecen a su emisor, y el preflight se limita a comprobarlos.
Desde el 2026-10-06, el script local y el de testnet, LocalRun y DeployTestnetBridge,
solo escriben su archivo de despliegue cuando envían de verdad sus transacciones
(broadcast), y DeployTestnetRail también desde el noveno bucle de auditoría: las
direcciones de un dry run no tienen código. En la testnet, DeployTestnetRail recibe las
mismas tres entradas del secuenciador, todas opcionales (sin feed, la comprobación queda
desactivada, ya que Chainlink tampoco lista ninguno para la testnet), y activa la pausa
del oráculo de cada acción; los scripts de Ethereum dejan ambas salvaguardas
desactivadas. Un keeper de testnet se
ejecuta con KEEPER_REQUIRE_MARKET_OPEN, KEEPER_AIRDROP_AFTER_SESSION y, desde el
2026-10-06, KEEPER_CONVERT_ONCE_PER_WINDOW en false, para que convierta en cada pasada:
consulta El keeper. Desde el séptimo bucle de auditoría, el 2026-10-06, el
keeper comprueba la cadena de cada RPC frente a su configuración antes de arrancar: un
keeper de testnet fija KEEPER_CHAIN_ID=11155111 y, con el bridge,
KEEPER_REMOTE_CHAIN_ID=46630, y un keeper de mainnet KEEPER_CHAIN_ID=1 con 4663. Desde
el octavo bucle de auditoría, ambos son obligatorios: el keeper se niega a arrancar sin
KEEPER_CHAIN_ID, o sin KEEPER_REMOTE_CHAIN_ID junto al bridge. El Worker de datos de
la app comprueba la cadena de sus endpoints de la misma manera, y sus RPC públicos siguen
sus dos ids de cadena, así que un Worker de testnet solo necesita esos.
La ejecución de LayerZero en testnet del 2026-10-06 desplegó el protocolo en Sepolia y en
la testnet de Robinhood Chain (cadena 46630) con los scripts de producción, o con
envoltorios de testnet que conservan su cuerpo, y sus propios tokens, plataformas de
negociación y adaptadores de prueba, en una carpeta de los contratos solo para testnet.
Desde el noveno bucle de auditoría, el preflight también comprueba esa ejecución, a partir
de sus dos archivos, con SEPOLIA_RPC_URL y ROBINHOOD_TESTNET_RPC_URL. Lo que la
ejecución demostró, y lo que no, está en Tests y verificación.
El airdrop
Desde el 2026-10-04, el contrato del airdrop se despliega con el protocolo. Sus ajustes vienen del entorno:
| Script | Variable | Por defecto | Rol |
|---|---|---|---|
DeployProtocol |
AIRDROP_LZ_ENDPOINT |
Ninguno: solo el rail local | Endpoint de LayerZero en Ethereum, para las acciones compradas en Robinhood Chain |
DeployProtocol |
AIRDROP_REMOTE_EID |
30416 con un endpoint | El id de endpoint de LayerZero de Robinhood Chain, el único origen de una entrega |
DeployProtocol |
AIRDROP_CYCLE_LENGTH |
86 400 (24 horas) | Duración de una ventana, en segundos, un número entero de horas |
DeployProtocol |
AIRDROP_CYCLE_OFFSET |
46 800 (13:00 UTC) | Dónde terminan las ventanas, en segundos tras las 00:00 UTC, un número entero de horas: antes de la apertura de la bolsa estadounidense todo el año |
DeployRemote |
AIRDROP_DISTRIBUTOR |
Ninguno | El contrato del airdrop en Ethereum al que envían los vaults espejo |
DeployRemote |
AIRDROP_RECEIVE_GAS, _MIN, _MAX |
650 000, 200 000, 1 500 000 | Desde el 2026-10-06: el gas de lzReceive de cada entrega en Ethereum, además de lo que impone el OFT de la acción, cuando el keeper pide el valor por defecto, y el suelo y el techo de lo que puede pedir |
DeployRemote |
AIRDROP_COMPOSE_GAS, _MIN, _MAX |
1 250 000, 600 000, 4 000 000 | El gas de la llamada de cada entrega al contrato del airdrop, lzCompose, de la misma manera. Hasta el 2026-10-06, una sola cifra, 600 000, fijaba todas las entregas |
DeployRemote |
STOCK_ADAPTERS |
Ninguno | Lista separada por comas, un adaptador LayerZero por cada entrada de STOCKS, cero para una acción sin adaptador |
Son valores iniciales: el owner puede cambiar más tarde el horario (setCycleSchedule) y
el endpoint de LayerZero (setLayerZero). En DeployRemote, AIRDROP_DISTRIBUTOR y
STOCK_ADAPTERS son opcionales: el admin del hub remoto puede fijarlas después
(setAirdrop, setStockAdapter). Las dos políticas de gas se fijan en cada ejecución, a
partir del entorno o de los valores por defecto del hub, antes del distribuidor, cuyo gas
de compose debe quedar dentro de ellas; el admin puede cambiarlas después
(setAirdropReceiveGas, setAirdropComposeGas). Un valor por encima de uint128 detiene
la ejecución. Luego vienen los pasos del owner:
setAirdropDistributoren la factory, que haceDeployProtocol. El owner puede designar otro más adelante; los vaults lo leen en directo, y un distribuidor sustituido conserva cada ciclo reclamable allí donde estáregisterStockOften el contrato del airdrop, para el OFT de cada acción en Ethereum, que debe usar el mismo endpoint de LayerZero que el contratosetExclusions, solo para un token que necesite excluir direcciones además de la dirección de burn: ningún token de mercado lo necesita por defecto; la lista de$STOCKFUN, con su operador del lanzamiento, la fijaLaunchProtocol
Los adaptadores de acciones en sí, uno por acción (el adaptador de bloqueo en Robinhood
Chain y su OFT en Ethereum), no están en el repositorio para mainnet: necesitan el paquete
oft-evm de LayerZero. DeployRemote recibe sus direcciones. La ejecución de LayerZero en
testnet del 2026-10-06 usó adaptadores de prueba, el OFTAdapter de LayerZero sobre
acciones de prueba y el OFT de LayerZero para las acciones wrapped, en su carpeta solo
para testnet.
Cada entrega desde Robinhood Chain se ejecuta en Ethereum en dos llamadas: el
lzReceive del OFT de la acción, que acuña la acción wrapped al contrato del airdrop, y
luego el lzCompose del contrato del airdrop, que la abona. Desde el 2026-10-06, el keeper
indica el gas de ambas en cada envío, elegido a partir de simulaciones en Ethereum (ver
El keeper), y el hub remoto ajusta cada valor a su política, y cero toma el
valor por defecto. Los valores por defecto cubren con un 35 % y un 30 % de margen los
casos más pesados medidos en Sepolia tras la actualización Glamsterdam de Ethereum: allí,
un lzReceive necesita 184 702 de gas hacia un saldo que el contrato del airdrop ya
tiene, y 481 548 para la primera entrega de una acción; un compose, 105 075 cuando el
ciclo ya lista la acción, 433 645 cuando el ciclo que abrió el keeper aún no la lista,
unos 531 600 cuando la entrega es además el primer abono del ciclo, y 962 154 cuando abre
ella misma el ciclo. La entrega más pesada construida en los tests, una apertura frente a
dieciséis holders excluidos con historiales largos que además se lleva cuatro acciones
apartadas, necesita unos 2,8 millones a los precios de Glamsterdam, por debajo del techo
del compose. Antes de Glamsterdam, medido en frío en los tests, una entrega en un ciclo
abierto requería unos 85 000, una que abre un ciclo frente a una dirección excluida unos
275 000, y la más pesada unos 1 016 000. Una entrega sin gas suficiente falla sin perder
nada: se queda almacenada en el endpoint de LayerZero, con las acciones wrapped ya en el
contrato del airdrop cuando solo falló el compose, y cualquiera puede volver a ejecutarla
con más gas. Desde el 2026-10-06, el keeper lo hace él mismo, dentro de su propio límite,
y alerta de un segundo fallo.
La conexión de la factory
La factory se inicializa solo con su owner y sus tres wallets; sus ajustes empiezan con sus valores por defecto. El owner designa todo lo demás después:
setLaunchModules: el hook y el lock, de una vez por todas; ambos deben designar esta factory, y el lock este hooksetDeployers: los dos deployers, que se pueden sustituirsetVaultImplementation: la implementación que hay detrás de los vaults de los mercados creados despuéssetHoldingRecorder: el registrador al que notifican los nuevos tokens de mercadosetAirdropDistributor: el contrato del airdrop al que los vaults entregan sus acciones; debe designar esta factorysetTreasuryRouter,setTreasuryOracle,setSwapRouter,setBridgeHubysetProtocolMarket
No se puede crear ningún mercado hasta que estén designados el lock, una implementación de vault y un registrador de tenencias.
El hub del bridge y el router de swap
setBridgeHub solo se puede fijar una vez. Saltárselo no tiene vuelta atrás. Desde el
2026-10-05 rechaza un hub que no puede traducir todas las acciones de los baskets ya
registrados: mapéalas antes en el hub.
setBridgeHub debe llamarse antes de crear el primer mercado. Cada TreasuryVault
fija la dirección del hub del bridge en su construcción. Un vault creado mientras la
dirección es cero se queda en el rail local para siempre y nunca envía nada a Robinhood
Chain.
setSwapRouter no es de escritura única: el owner puede cambiarlo en cualquier momento. Ya
no afecta a ningún vault: los vaults dejaron de leerlo cuando el buyback del creador se
eliminó del código, el 2026-09-28. Registra la dirección del router de swap oficial, que el
preflight comprueba.
El hook y su dirección minada
La dirección de un hook codifica sus permisos v4 en sus bits bajos: se encuentra por fuerza bruta sobre el salt de CREATE2. Desde el 2026-10-02, la dirección minada es la del proxy del hook, con los 14 bits de permiso activados. Depende del bytecode del proxy y de los argumentos de su constructor, que llevan la dirección de la implementación. Para un nuevo despliegue hay que volver a minarla; un upgrade conserva la dirección.
foundry.toml debe llevar bytecode_hash = "none" y evm_version = "cancun"; si no, la
dirección minada no coincidirá con el contrato desplegado.
Los upgrades
Un upgrade es una llamada del owner del protocolo al proxy del módulo, que designa la
nueva implementación; surte efecto de inmediato. Antes de cada upgrade,
contracts/script/check-storage-layouts.sh compara la nueva disposición del
almacenamiento con la registrada en contracts/storage-layouts/, y falla ante cualquier
cambio que no sea una ampliación por el final. Desde el 2026-10-05 compara cada nivel de
cada struct, tamaños incluidos, y rechaza cualquier cambio en un struct que sea el
elemento de un array de almacenamiento: solo un struct que sea el valor de un mapping, o
la última variable de estado, puede crecer, por su final. --write actualiza los
registros tras un cambio deliberado.
Las políticas de gas del hub remoto llegaron con el noveno bucle de auditoría, el
2026-10-06. Un hub desplegado antes de ellas y actualizado con un upgrade lee ambas
políticas como cero, y un vault espejo actualizado al código correspondiente rechaza todo
envío al airdrop y toda cotización (AirdropGasNotSet) hasta que se fijen ambas. Por
tanto, el orden es: hacer el upgrade del hub remoto, fijar ambas políticas
(setAirdropReceiveGas, setAirdropComposeGas), luego designar la nueva implementación
de los vaults espejo y hacer el upgrade de cada vault espejo, y solo entonces arrancar un
keeper del noveno bucle, que pide las políticas al hub y envía con tres argumentos. Un
keeper anterior sigue enviando mientras tanto con los valores por defecto del hub. Un hub
desplegado con el nuevo código fija los valores por defecto en la inicialización.
Los ajustes de lote del adaptador de USDG llegaron con el décimo bucle de auditoría, el
2026-10-06: el gas de compose que añade cada mercado de un lote del bridge, y el máximo de
mercados que lleva un lote. Un adaptador desplegado antes de ellos y actualizado con un
upgrade lee ambos como cero y rechaza todo lote y toda cotización (BatchGasNotSet) hasta
que se fijen. Por tanto, su upgrade los lleva en la misma transacción (upgradeToAndCall
con setBatchGas(400000, 17)), luego el owner baja su antiguo gas de compose, 1 200 000,
a la base que ahora necesita todo lote (setSettings, 200 000), y solo entonces arranca
un keeper del décimo bucle, que lee el tope en cada lote. Un keeper anterior sigue
funcionando con el adaptador actualizado mientras no haya más de 17 mercados listos a la
vez. El adaptador de la testnet se actualizó de esta manera el 2026-10-06, y su siguiente
lote pasó con su nuevo gas de compose.
Un upgrade que cambia lo que lee el servicio de datos de la app se pone en producción antes que ese servicio. Desde el 2026-10-06, la Lens lleva el estado de cada vault y lo que el hook y el lock le deben, y el Worker y la app que leen esos campos no pueden leer una Lens anterior: haz primero el upgrade de la Lens. El séptimo bucle de auditoría no cambia ni la Lens ni la forma de lo que publica el Worker (esquema 7): su Worker y su app se despliegan en cualquier orden. El octavo cambia la forma (esquema 8: cada precio de acción dice por qué falta, cuando el oráculo de Robinhood Chain lo retiene), no la Lens, y su Worker y su app se siguen desplegando en cualquier orden: una app anterior ignora el motivo, y esta app lee un Worker anterior sin él. El noveno mantiene el esquema 8.
El oráculo del rail de Robinhood, como cualquier TreasuryOracle, empieza con sus dos
salvaguardas desactivadas. Por tanto, un oráculo de sustitución designado en el hub remoto
(setOracle) también empieza con ellas desactivadas, y el admin del hub las vuelve a
activar para él, como hace el script de despliegue: la pausa del oráculo de cada acción, y
el feed del secuenciador si se había fijado uno. Los vaults espejo ya inicializados
conservan el oráculo con el que se inicializaron.
Ajustes
Un ajuste es una llamada del owner del protocolo al módulo que lo guarda o, en Robinhood
Chain, del admin del hub remoto; surte efecto de inmediato y emite un evento. Cada módulo
empieza con los valores por defecto que enumera el Modelo de confianza.
Los adaptadores del bridge empiezan con el gas del despliegue: en el rail de USDG,
COMPOSE_GAS, la parte del último paso de un lote que necesita todo lote, 200 000
por defecto en DeployBridge desde el décimo bucle de auditoría (1 200 000 para
todo el paso hasta entonces), más 400 000 por cada mercado del lote y como máximo 17
mercados por lote (setBatchGas; el gas del lote más grande, como máximo 24 000 000),
junto a un límite de 30 puntos básicos en Curve; en el rail
canónico, el gas de los dos tickets y, desde el 2026-10-05,
los bytes sobre los que se calcula el costo del ticket de depósito
(DEPOSIT_CALLDATA_LENGTH en DeployTestnetBridge; cero toma el valor por defecto,
1 024). El hub remoto empieza con las dos políticas de gas de las entregas del airdrop
(más arriba).
Antes de mainnet
- Un ensayo completo del modo de emergencia: transferencia, pausa, reanudación
- Un primer airdrop pequeño en un vault de verificación, antes de cualquier apertura pública. La ejecución de LayerZero en testnet del 2026-10-06 hizo funcionar el código de StockFun de punta a punta sobre los endpoints, el DVN y el ejecutor de testnet de LayerZero; no demuestra ni el par USDG de Paxos, ni las acciones de Robinhood y sus adaptadores, ni feeds y liquidez reales, ni el gas, las comisiones y la finalidad de mainnet
- Un inventario actualizado de los feeds de precios en la cadena remota
- Verificación de los peers de LayerZero del OFT de USDG y del estado de pausa de USDG, mediante el preflight; desde el octavo bucle de auditoría, el preflight también comprueba las salvaguardas del oráculo frente a su manifiesto, que debe indicar la comprobación del secuenciador, hoy desactivada
- El límite de tamaño de la ruta que toma el USDG de Paxos hacia Robinhood Chain, leído en
la biblioteca de envío de LayerZero (
getExecutorConfig), y el tope de mercados de un lote del bridge ajustado en consecuencia si no es de 10 000 bytes: como máximo (tamaño − 392) ÷ 544 mercados (desde el décimo bucle de auditoría) - Revisión externa: las pruebas formales existentes no cubren el rail cross-chain