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.

  1. 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 aceptarla
  2. DeployEthereumRail, con la dirección de la factory: el oráculo y el router ETH → USDC en Ethereum, que el owner fija en la factory
  3. DeployBridge --sig "predict()": muestra las direcciones que recibirán el hub del bridge y su adaptador
  4. DeployRemote en 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, o SEQUENCER_CHECK_OFF=true, la comprobación desactivada por elección, nunca ambos, con SEQUENCER_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 fija SEQUENCER_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 predicha
  5. DeployBridge: 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 hub
  6. setBridgeHub, antes del primer mercado. Desde el 2026-10-05 rechaza un hub que no puede transportar un basket ya registrado (UnmappedBridgeStock)
  7. addStockMapping en el hub del bridge, para cada acción de un basket
  8. Registrar los baskets: PlanBridgeBaskets muestra las llamadas del owner. Desde el 2026-10-06, un basket contiene como máximo cinco acciones: consulta Los baskets
  9. LaunchProtocol: $STOCKFUN, cuyo supply completo va a su posición bloqueada, su vault, el BuybackBurner y setBuybackWallet. Necesita que el contrato del airdrop esté designado antes, lo que hace DeployProtocol: desde el 2026-10-05, incluye al operador del lanzamiento en la lista de exclusiones de $STOCKFUN antes 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 mano
  10. registerStockOft en 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:

  • setAirdropDistributor en la factory, que hace DeployProtocol. 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á
  • registerStockOft en el contrato del airdrop, para el OFT de cada acción en Ethereum, que debe usar el mismo endpoint de LayerZero que el contrato
  • setExclusions, 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 fija LaunchProtocol

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 hook
  • setDeployers: los dos deployers, que se pueden sustituir
  • setVaultImplementation: la implementación que hay detrás de los vaults de los mercados creados después
  • setHoldingRecorder: el registrador al que notifican los nuevos tokens de mercado
  • setAirdropDistributor: el contrato del airdrop al que los vaults entregan sus acciones; debe designar esta factory
  • setTreasuryRouter, setTreasuryOracle, setSwapRouter, setBridgeHub y setProtocolMarket

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