Il keeper
Un worker offchain che, una volta ogni 24 ore, attiva la conversione di ogni tesoreria e poi il suo airdrop. Non decide nulla. Il suo loop fa un giro ogni cinque minuti di default e, dal 2026-10-06, converte l'ETH di un vault una volta per finestra dell'airdrop, al suo primo giro dopo la chiusura della finestra: vedi "La cadenza di conversione" sotto. Il suo passaggio dell'airdrop è scritto dal 2026-10-05: vedi sotto.
Cosa fa
- Una volta ogni 24 ore, dopo la chiusura della finestra, alle 13:00 UTC di default, prima dell'apertura USA tutto l'anno, individua i vault che detengono almeno la soglia di conversione, 0,1 ETH di default, al suo primo giro dopo la chiusura; un vault al di sotto attende allora la finestra successiva, anche se i trade lo portano oltre la soglia più tardi nella giornata. Gli acquisti del ciclo avvengono durante la sessione che segue, e i suoi invii, di default, una volta terminata quella sessione. Il cash che un vault detiene ancora da un ciclo precedente, USDC o USDG, prosegue, soglia o no: dal sesto ciclo di audit, il 2026-10-06, l'USDC di un vault una volta dopo ciascuna delle sue conversioni di ETH e al massimo una volta per finestra negli altri casi, l'USDG di un vault speculare a ogni giro (vedi "La cadenza di conversione")
- Propone i percorsi di swap e calcola gli importi minimi da una quotazione fresca; dal 2026-10-05 i vault applicano quei minimi a ciò che arriva effettivamente. Dal 2026-10-06 ogni vault su Ethereum viene quotato sul proprio router, quello con cui è stato creato
- Dimensiona ogni passaggio, in modo che la sede di esecuzione possa eseguirlo entro il limite del vault (vedi sotto)
- Chiama la conversione, il batch del bridge, poi l'acquisto sulla chain remota; dal
2026-10-05 è l'unico che può inviare il batch del bridge (
bridgeReady). Sul rail canonico chiede la quotazione della commissione del batch al doppio dell'ultima base fee (quoteBridgeAt), e l'eccesso gli viene restituito. Dal nono ciclo di audit ripiega sulla quotazione di un hub più vecchio solo quando l'hub stesso risponde di non averne, mai quando un nodo resta in silenzio: un silenzio trattiene il batch per un giro, sul rail canonico, la cui commissione segue la base fee, con un'allerta, e sul rail USDG solo con un avvertimento. Dal decimo ciclo di audit, il 2026-10-06, un batch sul rail USDG porta al massimo il tetto di mercati dell'adapter, 17 di default, che il keeper legge a ogni batch: i mercati che attendono da più tempo partono per primi, e gli altri tengono il loro USDC nei loro vault per il suo giro successivo, così nessuno attende per sempre (vedi Il rail Robinhood). Fino ad allora ogni mercato pronto andava nell'unico batch, il cui gas di compose fisso un batch grande poteva esaurire - Deploya in anticipo il vault speculare di ogni nuovo mercato su Robinhood Chain prima del
suo primo batch (
predeploy), cosa che dal 2026-10-05 possono fare solo lui e l'owner di StockFun. L'hub remoto apprende chi è il keeper da un batch, quindi prima del primo l'owner di StockFun deploya in anticipo il vault del primo mercato. Il keeper deploya in anticipo solo quando la sua chiave su Robinhood Chain è quella che l'hub remoto conosce come keeper, o quella del suo admin. Un mercato il cui vault il keeper non può deployare in anticipo (prima del primo batch, dopo un cambio di keeper che l'hub remoto non ha ancora appreso, o quando il deploy anticipato fallisce) viene lasciato fuori dal batch, con un avviso, e gli altri mercati passano. Un mercato il cui vault speculare il keeper non riesce a leggere quel giorno attende il ciclo successivo, perché un mercato inviato alla cieca potrebbe far fallire l'intero batch - Monitora le consegne: un trasferimento non arrivato, un ticket da rieseguire, un vault
speculare non ancora deployato. Un trasferimento che il keeper non ha potuto misurare
su Robinhood Chain al momento della partenza viene confermato solo dagli eventi dell'hub
remoto stesso, mai da un saldo letto in seguito, e sul rail canonico, dal 2026-10-06, lo
è ogni trasferimento: l'hub remoto paga le sue registrazioni con cash comune, così un
saldo può crescere con il denaro di un altro batch. Legge quegli eventi poche porzioni
alla volta, da dove si è fermata la ricerca di ciascun trasferimento; dal settimo ciclo
di audit, il 2026-10-06, solo fino a qualche blocco sotto l'ultimo della chain, così un
evento negli ultimi blocchi viene letto a un ciclo successivo invece di essere perso
dietro un nodo in ritardo di uno o due blocchi. Dall'ottavo ciclo di audit segue un batch
del bridge solo una volta che il blocco che lo contiene è profondo altrettanti blocchi
(
KEEPER_LOG_LAG_BLOCKS), a partire dalla sua ricevuta riletta in quel momento: gli identificatori di un batch su Robinhood Chain vengono da quel blocco, e una riorganizzazione dei blocchi più recenti di Ethereum che spostava il batch lasciava il keeper a seguire identificatori che non esistono mai. Dal decimo ciclo di audit la sua allerta per un trasferimento non accreditato in tempo dice, sul rail USDG, a che punto è il messaggio del batch sull'endpoint di Robinhood Chain: non ancora consegnato lì; composto, con l'accredito a seguire; o memorizzato, con il suo USDG sull'hub remoto e registrato da nessuna parte finché qualcuno non riesegue a mano l'ultimo passo con più gas, e l'allerta dà l'hash del messaggio memorizzato. Fino ad allora dava solo ciò che registra l'hub remoto, il cui zero si leggeva come "non è arrivato nulla" quando un ultimo passo rimasto senza gas aveva lasciato il cash sull'hub - Inoltra subito, al suo webhook di allerta, ogni trasferimento di emergenza che vede, su
ogni vault, il bridge hub, il contratto dell'airdrop, l'hub remoto e ogni vault
speculare. Dal decimo ciclo di audit nomina i contratti che sorveglia a gruppi, una
richiesta di log per gruppo, ciascuna delle quali nomina al massimo
KEEPER_LOG_MAX_SELECTORSindirizzi e valori di evento (1.000 di default), e supera un tratto di blocchi solo una volta letto ogni suo gruppo. Fino ad allora un'unica richiesta li nominava tutti: una volta che il registro dei mercati aveva superato ciò che un endpoint accetta (2.000 indirizzi e valori di evento sui nodi di Robinhood Chain, nove indirizzi sull'endpoint pubblico di Sepolia), ogni richiesta veniva rifiutata e nessun trasferimento di emergenza veniva più inoltrato - Dal decimo ciclo di audit legge ciò che ogni giro richiede di ogni mercato (l'ETH di un vault, il suo USDC e la sua finestra, i debiti, l'ultimo airdrop) tramite Multicall3, cento mercati per chiamata, allo stesso blocco che ogni lettura usava da sola; una lettura che non torna viene fatta da sola, come prima, e mai presa per uno zero. Fino ad allora ogni mercato mai creato costava le sue letture una per una a ogni giro, inattivo o no, e 2.000 mercati inattivi facevano durare un giro quanto l'intervallo tra due
- Sul rail canonico, dal sesto ciclo di audit, segue entrambi i ticket di ogni batch
finché non sa che ciascuno è stato eseguito, qualunque sia l'accredito del suo
trasferimento, a ogni ciclo, in sessione o no, e riesegue quello ancora vivo. Un ticket
di cui non si sa che sia stato eseguito sei ore dopo la partenza del suo batch, di
default (
KEEPER_TICKET_ALERT_HOURS), è oggetto di un'unica allerta, e lo stesso vale per uno scomparso alla sua scadenza o dopo senza che se ne sia vista l'esecuzione, o la cui creazione è fallita. Fino ad allora il keeper smetteva di seguire i ticket di un batch non appena i suoi mercati erano accreditati, il che poteva lasciar scadere un deposito che aveva mancato la sua esecuzione automatica. Dal settimo ciclo di audit legge ogni ticket nell'ordine in cui lo fa l'SDK di Arbitrum: prima la ricevuta della sua creazione, poi la sua esecuzione automatica, poi se esiste ancora a un blocco non anteriore alla sua creazione; dall'ottavo ciclo di audit, a un blocco qualche blocco sotto l'ultimo (KEEPER_REMOTE_LOG_LAG_BLOCKS), o al suo blocco di creazione quando è successivo, un blocco che ogni nodo di un endpoint ha. Un deposito creato tra due delle sue letture, o visto da due nodi a un blocco di distanza, non viene mai preso per eseguito mentre è ancora vivo - Lancia un avviso dopo fallimenti ripetuti su un vault, contando separatamente il suo passaggio su Ethereum e quello su Robinhood Chain, così un vault che fallisce ogni giorno su un lato viene segnalato. Dal settimo ciclo di audit attende al massimo dieci secondi il suo webhook di allerta e ne legge la risposta: un'allerta che il webhook non accetta viene conservata, cento al massimo, nel suo file di stato, e inviata di nuovo ai cicli successivi, così arriva in ritardo invece che mai. Il messaggio porta i campi che leggono Slack e Discord, ciascuno i propri. Dall'ottavo ciclo di audit si conserva una sola voce per allerta distinta: un'allerta lanciata di nuovo mentre attende (un vault che fallisce a ogni ciclo) viene contata, non conservata due volte. Il messaggio dice quando l'allerta è stata lanciata la prima e l'ultima volta e quante volte, e un'allerta consegnata in ritardo comincia dicendolo. Dal nono ciclo di audit le allerte conservate partono nell'ordine in cui sono state lanciate per ultime: un'allerta lanciata di nuovo passa dietro le altre, così ciò che si dice per ultimo di un argomento è il suo stato più recente (una sorveglianza che fallisce, si riprende e fallisce di nuovo mentre il webhook è fuori servizio viene consegnata come "in errore" per ultima, dove prima finiva su "ripresa"). Oltre le cento, viene scartata per prima solo un'allerta lanciata a ogni ciclo finché dura la sua causa (un vault che non riesce a convertire, il batch del bridge che fallisce), così un'allerta isolata, un trasferimento di emergenza per esempio, e l'ultima parola di ogni episodio non vengono mai scalzate. Dal decimo ciclo di audit il testo di un errore, nel suo log, nelle sue allerte e nel suo file di stato, porta di un indirizzo RPC solo lo schema e l'host, mai il resto, dove i provider mettono la chiave
- Dal nono ciclo di audit, per un minuto dopo che una delle sue transazioni è stata minata, legge ciò che quella transazione ha cambiato al suo blocco, mai all'ultimo, e vi stima il gas di ciò che invia dopo. L'esecuzione su testnet ha mostrato perché: un batch del bridge inviato subito dopo la conversione dello stesso giro veniva simulato su un nodo di un endpoint con bilanciamento del carico un blocco indietro rispetto alla conversione, non vedeva nulla da passare dal bridge e lanciava una falsa allerta. Un nodo indietro rispetto a quel blocco ora risponde con un errore, e il batch attende un giro con un avvertimento. Ogni transazione del keeper parte inoltre con la sua stima di gas moltiplicata per 1,25, da quando l'aggiornamento Glamsterdam di Ethereum ha reso più pesanti le chiamate dentro le sue transazioni
- Dal nono ciclo di audit, un orario letto da una chain che nessuna data può contenere (l'inizio di un feed, la fine di una finestra, la scadenza di un ticket) si legge "an unknown time" invece di far fallire la lettura che lo ha incontrato
- Dal settimo ciclo di audit, verifica prima del suo primo ciclo che ogni RPC serva la chain indicata dalla sua configurazione, e altrimenti si rifiuta di partire. Scoperta più tardi, una chain sbagliata ferma il lavoro su quella chain con un'allerta; una chain il cui identificatore non si può leggere attende, e il lavoro sull'altra chain prosegue. Dall'ottavo ciclo di audit entrambi gli identificatori di chain devono essere impostati (sotto), e un file di stato scritto per un'altra chain resta intatto finché il keeper non ha letto quale chain serve il suo RPC (vedi "Riavvii")
- Dall'ottavo ciclo di audit, legge le due protezioni dei feed di prezzo dell'oracolo dei vault speculari su Robinhood Chain: finché l'oracolo trattiene ogni prezzo per il sequencer, gli acquisti remoti attendono, con un'allerta, e un'azione in un'operazione societaria attende da sola (vedi "Quando l'oracolo trattiene i prezzi")
- Paga il cash in attesa sull'hub remoto con uno sweep, su entrambi i rail: le registrazioni i cui token sono arrivati, e ciò che ha raggiunto l'hub mentre era in pausa. Sul rail USDG si astiene dallo sweep finché l'hub remoto attende che l'owner di StockFun sistemi i conti dopo un trasferimento di emergenza, cosa che segnala una sola volta, e riprende quando i conti sono sistemati. Dal 2026-10-05 fa lo sweep anche di una quota che un vault speculare ha rifiutato, non appena quel vault accetta di nuovo il cash; sul rail canonico invia quello sweep solo quando pagherebbe qualcosa
- Attiva il
BuybackBurner, contando il saldo del buyback accreditato sull'hook, che il burner ritira prima di comprare - Una volta per finestra, dopo la sessione USA di default, invia le azioni di ogni mercato al contratto dell'airdrop, senza mai stabilire la ripartizione: vedi sotto
- Dal 2026-10-05, a ogni ciclo, qualunque sia la sessione, paga ciò che l'hook e il lock
della liquidità conservano per un destinatario che lo ha rifiutato: ciò che l'hook deve
al vault di un mercato (
payTreasury), e le quote di un incasso di commissioni che il lock conserva per un vault o un creatore (payOwed). Ogni pagamento viene prima simulato e inviato solo quando paga qualcosa. Entrambe le chiamate sono aperte a chiunque - Dal 2026-10-05 incassa le commissioni LP di un pool creato con una commissione
(
collectFees, aperta a chiunque), al massimo una volta al giorno per pool. I pool di StockFun non applicano commissioni LP di default, per cui di default non c'è nulla da incassare
La cadenza di conversione
Il fondatore ha deciso il 2026-09-27 che l'ETH di un vault si converte una volta per ciclo giornaliero, subito prima dell'airdrop, e solo se il vault detiene la soglia a quel controllo. Il keeper vi si attiene dal 2026-10-06; fino ad allora convertiva l'ETH di un vault a ogni giro della sessione non appena il vault deteneva la soglia.
- La finestra. Il ciclo è la finestra dell'airdrop: si chiude quando lo dice il
contratto dell'airdrop (24 ore che si chiudono alle 13:00 UTC di default), compreso il
mercato di
$STOCKFUN; finché la factory non indica alcun contratto dell'airdrop, secondo quell'orario di default - Un controllo per finestra. Il loop fa sempre un giro ogni cinque minuti di default, e ogni giro termina con un resoconto del ciclo. La soglia si controlla al primo giro che raggiunge il vault dopo la chiusura della finestra. Se il saldo è pari o superiore, l'ETH si converte, una volta sola: il vault stesso registra la sua ultima conversione di ETH, e quel dato, letto dalla chain, informa i giri successivi, così un riavvio non cambia nulla. Al di sotto, l'ETH attende la finestra successiva, anche se i trade lo portano oltre la soglia più tardi nella giornata; il keeper conserva quel controllo nel suo file di stato. Dal 2026-10-06 lo conserva come la fine della finestra per cui è stato fatto il controllo, mai come l'ora del proprio orologio, che un orologio un po' sfasato attorno alla chiusura poteva discostare dalla finestra della chain; un controllo scritto da un keeper più vecchio non conta, così nella finestra dell'aggiornamento un vault del genere viene controllato di nuovo. Dal sesto ciclo di audit il controllo legge il saldo del vault dopo la chiusura della finestra: un giro che iniziava prima della chiusura e finiva dopo registrava il saldo letto prima di essa, e un vault che aveva superato la soglia in tempo saltava quella finestra
- Una conversione che non può partire al controllo (un feed ETH/USD fermo, una sede di esecuzione che non riesce a eseguirla entro il limite del vault, una transazione fallita, una finestra che il keeper non riesce a leggere) viene ritentata ai giri successivi della stessa finestra. Una finestra che non riesce a leggere conta come un fallimento del passaggio dell'ETH, oggetto di allerta dopo fallimenti ripetuti, e l'USDC del vault continua comunque a muoversi
- L'USDC, dal sesto ciclo di audit. L'USDC già in un vault prosegue, soglia o no, al proprio ritmo: il suo acquisto su Ethereum, o il suo rilascio in un batch del bridge, parte una volta dopo ciascuna delle conversioni di ETH del vault, e al massimo una volta per finestra negli altri casi (una donazione, un rimborso, ciò che ha lasciato un passaggio fallito o dimezzato). Conta solo un passaggio andato a buon fine; uno che fallisce viene ritentato ai giri successivi. La decisione del fondatore del 2026-09-27 si estende così all'USDC: fino ad allora poche unità di USDC inviate a un vault prima di ogni giro facevano inviare al keeper un acquisto, o un intero batch del bridge con la sua commissione, a ogni giro
- Cosa parte a ogni giro: gli acquisti su Robinhood Chain. Il saldo di un vault speculare non distingue una consegna da una donazione, e i tetti di ogni acquisto distribuiscono di proposito una consegna grande su più giri. L'ETH che una conversione dimezzata lascia (la sede di esecuzione non è riuscita a eseguire l'intero saldo entro il limite) attende la finestra successiva
- Le esecuzioni locali e di testnet impostano
KEEPER_CONVERT_ONCE_PER_WINDOWa false, e allora convertono, comprano e passano dal bridge a ogni giro; ovunque altrove resta attiva, il suo default
Altrove in questa pagina, "a ogni ciclo" significa a ogni giro del loop, come nel resoconto del ciclo; solo il ciclo giornaliero dell'airdrop è la finestra.
Un guasto alla volta
Dal 2026-10-05 i contratti lasciano fallire una tratta, un'azione, un mercato o una consegna senza fermare gli altri, e segnalano ciò che è fallito con un evento. Il keeper legge quegli eventi e lancia un'allerta su ciò che resta bloccato; il suo stesso lavoro è suddiviso allo stesso modo.
- Una tratta d'acquisto. La tratta di ogni azione viene pianificata a sé: una tratta
che non si può pianificare viene saltata, e le altre tratte proseguono. Una tratta che il
vault segnala come fallita (
LegFailed) conserva il proprio cash riservato alla sua azione; il keeper non la conta mai come convertita, e lancia un'allerta dopo tre fallimenti consecutivi di quell'azione in quel vault (KEEPER_ALERT_AFTER_FAILURES). Quando non si può pianificare nessuna tratta di un vault, il vault fallisce nel suo insieme, come prima - Un mercato di un batch del bridge. Un mercato che il bridge hub lascia fuori da un
batch (
MarketSkipped) conserva il suo USDC nel suo vault per un batch successivo. Lasciato fuori da tre batch consecutivi (KEEPER_BRIDGE_ALERT_AFTER_SKIPS), è oggetto di un'unica allerta, con il motivo decodificato e la sua soluzione. Dal decimo ciclo di audit un mercato che il tetto dell'adapter lascia fuori da un batch attende il giro successivo, annotato nel log, e apre quel batch; un batch che l'adapter rifiuta (oltre il suo tetto, o un adapter aggiornato senza le sue impostazioni di batch) è oggetto di un'allerta con il motivo indicato e i mercati del batch stesso - Una consegna rifiutata. Una quota che il token cash ha rifiutato a un vault speculare
(
DeliveryRefused) è oggetto di un'unica allerta, con la sua soluzione, finché l'hub remoto non deve più nulla di essa a quel mercato - Un vault illeggibile. Un vault le cui funzioni di lettura non rispondono, dopo un
upgrade difettoso per esempio, viene lasciato fuori da quel ciclo; gli altri vault
convertono. È oggetto di un'unica allerta dopo tre cicli consecutivi
(
KEEPER_ALERT_AFTER_FAILURES) - Un debito che persiste. Finché un vault, o l'hook per la quota di un creatore, continua a rifiutare l'ETH che gli è dovuto, il keeper lancia un'allerta per episodio indicando che il pool attende l'owner di StockFun (un upgrade correttivo), e registra la fine dell'episodio non appena il debito viene pagato, dal keeper o da chiunque altro
Ciò che solo l'owner di StockFun può risolvere è "in attesa dell'admin": un'allerta per episodio, mai contata come un fallimento, e il resto prosegue nel frattempo.
Il resoconto del ciclo include ora i debiti pagati e quelli ancora dovuti, le tratte
fallite, i pool le cui commissioni LP sono state incassate e, per il passaggio
dell'airdrop, i cicli aperti, le ricerche di azioni messe da parte eseguite, i vault che
hanno inviato e le consegne ancora in transito. Dal 2026-10-06 un invio in una finestra
senza detenzioni idonee, che il contratto dell'airdrop mette da parte, viene contato a
parte (airdropHeldAside), non più con i vault che hanno inviato; sul rail canonico conta
le riesecuzioni di ticket inviate (redeemed) e i ticket ancora seguiti
(ticketsWatched). Dal settimo ciclo di audit conta anche i mercati il cui invio
all'airdrop attende di valere il suo costo (airdropBelowCost) e le allerte conservate
per il webhook (alertsUndelivered), e dice quando l'identificatore di una chain non è
ancora verificato (deferred: chain-unverified).
Quando l'oracolo trattiene i prezzi
Dal 2026-10-06 l'oracolo dei vault speculari può trattenere un prezzo (vedi Il rail Robinhood), e dall'ottavo ciclo di audit il keeper lo legge prima di quotare qualsiasi cosa:
- Il sequencer. Una volta per ciclo chiede all'oracolo cosa dice il suo controllo del sequencer. Finché il sequencer è fermo, ripartito da non più del periodo di grazia, o il suo feed di uptime non si può leggere, nessuna azione può essere valorizzata: l'acquisto del vault attende, non si quota nulla e nulla conta come un fallimento. Un'allerta apre l'episodio, conservata nel file di stato perché un riavvio non la rilanci, e un'altra dice quando gli acquisti riprendono. Il controllo è disattivato su Robinhood Chain finché Chainlink non vi pubblica un feed di uptime, quindi oggi nulla di tutto questo può accadere
- Un'operazione societaria. Un'azione il cui token mette in pausa il suo oracolo viene lasciata fuori dall'acquisto fin dall'inizio, annotata nel log, con il suo USDG conservato per essa; non conta mai per l'allerta dei fallimenti, e le altre azioni del basket vengono acquistate. Una tratta che il vault segnala come fallita per quel motivo, tra il piano del keeper e il suo acquisto, è prevista e nemmeno essa viene contata
- Il motivo, detto. Dove il limite di una tratta non si può leggere perché una
protezione ne trattiene il prezzo, il keeper registra il motivo nel log, su entrambe le
chain, invece di un vault che non riesce a valorizzare la tratta. Una protezione che non
riesce a leggere (un oracolo di prima delle protezioni) non trattiene nulla: decide il
limite proprio di ogni tratta, come prima. Dal nono ciclo di audit vengono decodificati
anche gli altri errori dell'oracolo: un feed non aggiornato si legge
StalePricenel log e nel motivo di una tratta fallita, dove prima si leggeva un codice non decodificato
Il passaggio dell'airdrop
Il lato contratto è implementato dal 2026-10-04, e il passaggio del keeper dal 2026-10-05.
A ogni ciclo, prima di verificare la sessione, il keeper segue anzitutto le consegne già
inviate. Poi, finché la factory indica un contratto dell'airdrop, prende un mercato alla
volta, compreso il mercato di $STOCKFUN:
- Una volta per finestra. Un vault ha finito con la finestra quando il suo ultimo
invio (
lastAirdropAt) è uguale o successivo alla fine della finestra in cui cadrebbe ora un invio (currentCycleEnd). Entrambi si leggono dalla chain, per cui un riavvio non cambia nulla. Di default il keeper invia solo dopo la fine della sessione USA del giorno, in modo che gli acquisti del giorno partano in un unico invio, nella finestra terminata quel giorno, e solo quando il vault detiene azioni; dal settimo ciclo di audit, solo le azioni che valgono quanto costa inviarle (sotto). Dal decimo ciclo di audit, con quel default, un vault letto dopo la chiusura senza nulla da inviare viene riletto solo quando si apre la sessione successiva, o quando la finestra avanza, sui rail i cui acquisti seguono la sessione di New York (non quello di Ondo): ciò che detiene viene dai suoi acquisti, fatti in sessione. Non finché un suo acquisto attende ancora la sua ricevuta, che può arrivare in serata: quel vault viene letto a ogni giro, e ciò che l'acquisto porta va nella finestra terminata quel giorno - Prima le azioni messe da parte, anche senza nulla da inviare. Se il mercato tiene
azioni da parte,
assignUnassigned, al massimo cinque chiamate per ciclo, finché non sono collocate, il che apre il ciclo della finestra che le riceve, o non resta alcuna finestra terminata da esaminare. Dal 2026-10-06 il keeper esegue questa ricerca che il vault abbia o no qualcosa da inviare, e che il vault sia in pausa o no: fino ad allora la eseguiva solo prima di un invio, così un mercato che aveva fatto un trade e poi era rimasto fermo teneva il suo primo airdrop, messo da parte, fuori dalla portata dei suoi holder fino a un nuovo trade o finché qualcuno non eseguiva la ricerca a mano. Una ricerca non conclusa rimanda al ciclo successivo un invio in una finestra senza detenzioni idonee. Dal sesto ciclo di audit, finché il vault non ha nulla da inviare, una ricerca che non collocherebbe nulla (nessuna finestra può ancora ricevere le azioni) viene inviata solo quando la ricerca del mercato è indietro dimaxWindowsPerAssignfinestre rispetto all'ultima finestra, 30 di default: una ricerca al mese invece di una al giorno per sempre. Una ricerca che colloca le azioni parte sempre. Dal settimo ciclo di audit "nulla da inviare" significa nulla che valga la pena inviare, secondo la stessa regola dell'invio: sul rail del bridge, la polvere che l'OFT non può trasportare, che ogni invio lascia nel vault speculare, non conta nulla, e il limite vale anche lì - Il ciclo, solo prima di un invio.
openCycle, così ogni consegna si limita ad aggiungersi al ciclo; mai per una finestra in cui non si invia nulla, e dal settimo ciclo di audit solo per un invio che valga il suo costo, apertura compresa. Una finestra senza detenzioni idonee non è un errore: le azioni vengono allora messe da parte - L'invio. Sul rail locale,
sendToAirdropsulTreasuryVaultdel mercato. Sul rail del bridge,sendToAirdropsul vault speculare, pagando la sua commissione LayerZero quotata (quoteSendToAirdrop) più un margine, il 10 % di default; il vault restituisce l'eccedenza. Dal nono ciclo di audit l'invio e ogni sua quotazione portano il gas che le sue consegne ricevono su Ethereum, che sceglie il keeper (sotto). Prima di quell'invio il keeper verifica che l'hub remoto invii al contratto dell'airdrop della factory, che ogni azione abbia un adapter e che l'adapter risponda, e che l'OFT dell'azione su Ethereum sia registrato sul contratto dell'airdrop. Dal settimo ciclo di audit l'invio elenca solo le azioni che valgono il loro costo, con la sua commissione quotata di nuovo per esse - Le consegne. Sul rail del bridge, la consegna di ogni azione viene seguita finché l'endpoint LayerZero su Ethereum non ha eseguito il suo ultimo passo, che accredita il contratto dell'airdrop. Dal nono ciclo di audit una consegna bloccata su quell'endpoint viene rieseguita dal keeper stesso (sotto). Una consegna non accreditata entro 60 minuti di default lancia un'allerta, che riporta i comandi che eseguono a mano i suoi passi; chiunque può eseguirli
Ogni azione va a sé: un'azione il cui saldo non si può leggere (un congelamento
dell'emittente), una senza adapter o il cui OFT non è registrato, una il cui adapter non
risponde (peers(): nessun contratto al suo indirizzo, o non è un'app LayerZero; dal
2026-10-06, fino ad allora faceva fallire l'intero invio del mercato), dall'ottavo ciclo di
audit una la cui commissione LayerZero il suo adapter non riesce a quotare (sotto), e una
che il vault segnala di non aver potuto inviare (AirdropSendFailed) vengono lasciate
fuori, con
un'allerta, e le altre partono. Anche ogni mercato va a sé: uno che continua a fallire è
oggetto di un'unica allerta dopo tre fallimenti consecutivi, e il mercato successivo
prosegue. Dal 2026-10-06 una ricerca di azioni messe da parte che continua a fallire ha
un'allerta propria, che ne indica la soluzione: chiunque può chiamare assignUnassigned
per quel mercato.
"In attesa dell'admin", qui, comprende un contratto dell'airdrop in pausa, un'azione che manca al contratto dell'airdrop dopo un trasferimento di emergenza (quell'azione attende nei vault, le altre partono), un hub remoto che invia a un contratto dell'airdrop diverso da quello della factory e, dal nono ciclo di audit, un hub remoto i cui limiti di gas delle consegne non sono impostati, o il cui massimo è sotto ciò di cui una consegna ha bisogno.
Ciò che vale la pena inviare
Dal settimo ciclo di audit, il 2026-10-06, il keeper invia solo ciò che vale quanto costa inviarlo. Fino ad allora partiva ogni azione sopra lo zero: un regalo appena sopra la polvere al vault di un mercato che nessuno scambia faceva aprire al keeper il ciclo di quella finestra e pagare un messaggio LayerZero, o un invio su Ethereum, ogni giorno.
- Ciò che un invio porta. Un'azione il cui saldo si può leggere ed è sopra lo zero; sul rail del bridge deve avere anche un adapter e una quotazione propria sopra lo zero. La quotazione del vault è zero sia per la polvere che l'OFT non può trasportare sia per un'azione il cui adapter non riesce a quotarne l'invio, quindi dall'ottavo ciclo di audit il keeper interroga l'adapter stesso dell'azione, sull'invio che il vault costruirebbe: la polvere non viene mai inviata né segnalata; un'azione la cui quotazione fallisce, il cui invio fallirebbe, viene lasciata fuori con un'allerta che dice perché; e un'azione che il keeper non riesce a interrogare parte come prima, e decide il suo invio. Fino ad allora un'azione del genere veniva presa per polvere e tolta da ogni invio senza una parola
- Il suo valore. Ogni azione viene valutata con l'oracolo stesso del suo vault, all'ultima risposta del suo feed di prezzo, aggiornato o no, dato che è una stima e mai un limite; i costi, pagati in ETH, vengono convertiti in dollari con il feed ETH/USD dello stesso oracolo
- Il suo costo proprio. Ogni azione deve valere almeno il proprio costo: la sua commissione LayerZero sul rail del bridge, dove ogni azione è un messaggio a sé, o la sua quota del gas dell'invio sul rail locale. Un regalo che non vale il proprio messaggio non viaggia mai insieme a un invio reale
- L'intero invio. Le azioni che passano devono valere insieme l'intero invio, più
l'apertura della finestra (
openCycle) quando il suo ciclo non è ancora aperto. Sul rail locale, dall'ottavo ciclo di audit, l'apertura si conta una sola volta: la stima propria dell'invio, presa a ciclo chiuso, la contiene già, quindi si aggiunge solo il costo base della transazione di apertura; contata due volte, tratteneva per giorni un invio che valeva un po' più di quanto costava. Altrimenti non ne parte nessuna, e il ciclo non viene aperto - Ciò che attende. Ciò che non parte resta nel vault e parte in una finestra successiva, con ciò che si accumula. Un mercato il cui invio attende viene contato nel resoconto del ciclo e registrato nel log una volta per finestra
- Ciò che non si può leggere. Un prezzo, una commissione o un costo di gas che il keeper non riesce a leggere lascia partire l'invio, come prima della regola: una lettura che fallisce non trattiene mai un invio reale
KEEPER_AIRDROP_MIN_VALUE_BPS fissa il multiplo: 10.000, una volta il costo, di default,
così un invio che vale almeno quanto costa parte, qualunque sia la sua dimensione al di
sopra del costo; un valore più alto trattiene gli invii più piccoli finché non valgono quel
multiplo, e 0 invia qualsiasi cosa sia portata. La stessa regola decide se un vault ha
qualcosa da inviare per la ricerca di azioni messe da parte vista sopra. L'audit l'ha
adottata come suo default raccomandato; decide solo quando parte un'azione, mai quanto,
cosa o dove.
Il gas della consegna su Ethereum
Il 2026-10-06 Sepolia ha attivato l'aggiornamento Glamsterdam di Ethereum, che fa costare a un nuovo slot di storage circa cinque volte il suo gas precedente, e ogni consegna dell'airdrop del primo ciclo dell'esecuzione su testnet con LayerZero è rimasta senza gas presso l'executor di LayerZero: un invio pagava solo il gas dell'ultimo passo della consegna, e il gas del suo primo passo era quello che impone l'owner dell'adapter LayerZero dell'azione (l'emittente, sulla mainnet). Dal nono ciclo di audit, su decisione del fondatore dello stesso giorno:
- Il keeper simula ogni consegna su Ethereum prima dell'invio: il contratto del token dell'azione che la riceve, e il contratto dell'airdrop che la accredita, nello stato in cui la consegna li troverà. Chiede il fabbisogno dell'azione più pesante moltiplicato per 1,25, meno il gas che l'adapter LayerZero dell'azione impone già per il primo passo
- Quando una simulazione non può girare, prende ciò che hanno usato su Ethereum le ultime consegne del mercato, poi i default dell'hub remoto
- L'hub remoto lo limita. L'owner di StockFun fissa un default, un minimo e un massimo per ciascuno dei due passi (vedi Il rail Robinhood); qualsiasi cosa chieda il keeper viene mantenuta tra di essi. Una richiesta che il massimo taglia sotto il fabbisogno di una consegna è "in attesa dell'admin": l'invio parte comunque, e una consegna che resta bloccata viene rieseguita (sezione successiva)
- Per gli holder non cambia nulla. Il keeper decide solo il gas che una consegna riceve, mai quanto viene inviato, dove o a chi
- Scelto di nuovo solo quando serve. Una coppia di gas scelta dalla simulazione di ogni azione viene mantenuta per la finestra finché restano uguali le azioni, lo stato del ciclo della finestra e la possibilità che una consegna arrivi dopo la fine della finestra successiva; una che una simulazione non ha potuto dare viene scelta di nuovo a ogni giro. Dal decimo ciclo di audit un invio che porta solo la polvere che il bridge non può trasportare non legge nulla dell'ambiente della consegna, e una coppia simulata una volta aperto il ciclo della finestra, uno stato che non torna mai indietro, viene riutilizzata senza rileggerlo
Una consegna bloccata su Ethereum
Una consegna rimasta senza gas non perde nulla: resta sull'endpoint di LayerZero su Ethereum, con il suo primo passo verificato e non eseguito, o il suo ultimo passo memorizzato e non eseguito, finché qualcuno non la riesegue con più gas. Dal nono ciclo di audit lo fa il keeper stesso:
- Legge a ogni ciclo il passo di ogni consegna sull'endpoint. Una volta concluso il turno
dell'executor (il suo fallimento, creduto solo se viene dall'executor stesso di
LayerZero, o dieci minuti), riesegue il passo bloccato dalla sua chiave Ethereum, con il
fabbisogno simulato moltiplicato per 1,25, e mai più di
KEEPER_AIRDROP_REEXECUTION_MAX_GAS, 4.000.000 di default - Una che non riesce a inviare (la sua simulazione fallisce, il suo fabbisogno supera il limite, alla sua chiave manca ETH) è oggetto di un'unica allerta, con il comando per eseguirla a mano, e viene ritentata a ogni ciclo
- Una che fallisce di nuovo sulla chain, con il passo ancora bloccato, è il secondo
fallimento: oggetto di un'allerta, con il comando, e mai più inviata dal keeper. Lo
decide solo su una lettura del passo che risponde, e solo una volta che il blocco
dell'esecuzione fallita è profondo qualche blocco (
KEEPER_LOG_LAG_BLOCKS), così né un nodo indietro di un blocco né una riorganizzazione di quel blocco possono farlo desistere - La chiave Ethereum del keeper paga queste esecuzioni: con Glamsterdam un ultimo passo che apre un ciclo richiede circa un milione di gas
Il keeper sceglie quando, e il gas di una consegna entro i limiti dell'owner, nient'altro. Un invio che arriva dopo l'orario di chiusura successivo viene misurato sulla finestra del giorno dopo.
Sette variabili governano il passaggio: KEEPER_AIRDROP (attiva di default),
KEEPER_AIRDROP_AFTER_SESSION (attiva di default; disattivata su una testnet che ignora la
sessione), KEEPER_AIRDROP_FEE_MARGIN_BPS (1.000), KEEPER_AIRDROP_TRANSIT_MINUTES (60),
dal settimo ciclo di audit KEEPER_AIRDROP_MIN_VALUE_BPS (10.000), e dal nono
KEEPER_AIRDROP_REEXECUTION_MAX_GAS (4.000.000) e KEEPER_AIRDROP_LZ_EXECUTOR (vuota:
l'executor di LayerZero sulla chain del keeper, l'unico chiamante alle cui allerte di
fallimento il keeper crede).
Riavvii: il file di stato
Dal 2026-10-05 il keeper scrive ciò che segue da un ciclo all'altro in un file di stato
(KEEPER_STATE_DIR): le consegne dell'airdrop e i trasferimenti del bridge in transito, le
transazioni in attesa di una ricevuta, i suoi episodi di allerta e le sue serie di
fallimenti; dal sesto ciclo di audit anche i ticket del rail canonico che segue, il punto
in cui si è fermata la ricerca di ciascun trasferimento e quando è partito per l'ultima
volta l'USDC di ogni vault; dal settimo, le allerte che il suo webhook non ha ancora
accettato. Un file scritto da un keeper più vecchio si carica così com'è.
Scrive il file dopo ogni ciclo e ogni invio, e lo legge una sola volta all'avvio. Un
riavvio non ne dimentica nulla: una consegna il cui ultimo passo fallisce dopo un riavvio
viene ancora segnalata, una transazione inviata prima non viene mai inviata di nuovo, e un
episodio già segnalato non viene segnalato due volte. Un file scritto per un'altra factory
viene messo da parte, e il keeper parte vuoto, con un avvertimento. Dall'ottavo ciclo di
audit un file scritto per un'altra chain viene prima lasciato com'è, né letto né
sovrascritto, finché il keeper non ha letto quale chain serve il suo RPC: se serve la chain
configurata, il file era di un'altra chain e viene messo da parte allora; se no, l'errore
è nell'identificatore configurato, il keeper si rifiuta di partire e, una volta corretto,
il keeper riprende il suo file, compresi i depositi che seguiva. Fino ad allora un
identificatore digitato male costava il file prima che il keeper si rifiutasse di partire.
Le allerte conservate sono una per allerta distinta dallo stesso ciclo; un file scritto da
un keeper più vecchio si carica così com'è. Dal nono ciclo di audit il file conserva anche
il pacchetto di ogni consegna dell'airdrop e le sue esecuzioni da parte del keeper, il gas
usato dalle ultime consegne del mercato e l'ultima riesecuzione di ogni ticket canonico
fatta dal keeper; un file scritto da un keeper più vecchio si carica ancora così com'è.
Dal 2026-10-06:
- Ogni invio è su disco prima della sua attesa. Ogni transazione parte con il nonce che la chain dà all'indirizzo del keeper subito prima, e viene scritta nel file di stato, con il suo hash e il suo nonce, appena torna l'hash, prima che se ne attenda la ricevuta: un keeper interrotto mentre attende la ritrova dal suo hash al riavvio invece di inviarla di nuovo
- Una transazione persa viene abbandonata. Una che nessun nodo conosce più quattro
volte
KEEPER_RECEIPT_ALERT_MSdopo il suo invio (due ore di default) viene scartata con un'allerta, così non trattiene più gli invii del suo tipo; il suo nonce resta libero, e la transazione successiva del keeper lo prende. L'allerta per una transazione non ancora minata dice come sostituirla - Scritto per intero. Un disco troppo pieno per ricevere il file è un errore, oggetto di allerta, e l'ultimo file valido resta; fino ad allora una copia troncata poteva sostituirlo
- Un keeper per cartella. Il keeper prende un lock nella sua cartella di stato
(
keeper.lock): un secondo keeper sulla stessa cartella si rifiuta di partire e nomina il primo. Un lock il cui keeper non c'è più viene rilevato; uno lasciato da un keeper su un altro host no, e l'operatore lo cancella una volta che quel keeper non gira più. Un dry run non prende alcun lock
Il dimensionamento dei passaggi
Dal 2026-10-01 il vault converte l'importo indicato dal keeper, non il suo intero saldo. Il keeper lo dimensiona:
| Passaggio | Importo |
|---|---|
| ETH → USDC | L'intero saldo, dimezzato finché la quotazione non rispetta il limite del vault, mai sotto la soglia di conversione; dal 2026-10-06 il resto attende la finestra successiva |
| Un'azione su pool Uniswap su Ethereum | La riserva dell'azione, dimezzata al massimo quattro volte; una tratta che ancora non rispetta il limite attende il ciclo successivo, con la sua riserva intatta |
| Un'azione sul rail Ondo opzionale | La riserva dell'azione, con un tetto al limite di nozionale della sessione. Dal 2026-10-06 una tratta che Ondo quota sotto il limite del vault non viene mai inviata: la quotazione gratuita si legge prima di qualsiasi attestazione, e non appena un'attestazione torna sotto il limite, non se ne chiede più nessuna per quell'azione fino alla finestra successiva |
| Un'azione su Robinhood Chain | La riserva dell'azione, limitata singolarmente da un tetto fisso e, quando il suo pool può essere misurato, da una quota della profondità di quel pool |
Ciò che non viene speso attende nel vault il ciclo successivo (per l'ETH, la finestra successiva); il cash di un'azione resta riservato a quell'azione.
Dalla pipeline di sicurezza del 2026-10-01, ogni acquisto trattiene n−1 unità sull'ultima azione del basket, con n pari al numero di azioni, su ogni rail che compra azioni. I vault assegnano all'ultima azione il resto dell'arrotondamento dei pesi, quindi poche unità di cash che arrivano tra la lettura del keeper e la sua transazione possono ridurre quella sola riserva fino a n−2 unità; spendere l'importo letto farebbe fallire l'intera chiamata, e chiunque potrebbe provocarlo con un trasferimento di polvere. Ciò che viene trattenuto resta riservato per la chiamata successiva. Qualsiasi altro chiamante dei vault dovrebbe mantenere lo stesso margine. Una correzione nei contratti, che cambia la regola di allocazione documentata, attende la decisione dell'owner. Dal 2026-10-06 un vault che non detiene più di quelle poche unità di USDC (quattro al massimo, dato che un basket comprende al massimo cinque azioni) non viene più visitato per esse: attendono il prossimo USDC che il suo ETH porterà.
Il burn di $STOCKFUN viene dimensionato allo stesso modo: una tranche il cui impatto sul
prezzo supera il tetto del keeper viene dimezzata, mai sotto la soglia del keeper, e il
resto viene bruciato nei cicli successivi. Dalla pipeline di sicurezza del 2026-10-01,
anche una tranche che il pool $STOCKFUN non riesce a eseguire per intero viene
dimezzata; prima, il burn falliva per quel ciclo. Nessun altro fallimento viene ritentato.
Cosa non può fare
Scegliere un asset fuori dal basket. Spostare il cash riservato a un'azione su un'altra azione. Allentare un limite oracolo. Inviare un asset altrove rispetto a dove lo invia il contratto. Prelevare alcunché. Scegliere chi riceve un airdrop o quanto porta, o assegnarsi una quota.
La sua attivazione è riservata per impedire il sandwiching, non perché ci si fidi del keeper: ogni chiamata che fa viene eseguita dentro i limiti dei vault.
Il ciclo
calendario NYSE} C -->|no| W[Attendi] C -->|sì| P{Contratto in pausa?} P -->|sì| W P -->|no| A{Primo controllo del vault
in questa finestra: ≥ 0.1 ETH?} A -->|no, ETH in attesa
della finestra successiva| N[Mercato successivo] A -->|sì| E[ETH → USDC, limite 50 bps] E --> B[USDC → USDG → bridge] B --> R{Arrivato sulla chain remota?} R -->|no| M[Monitora, riesegui il ticket] R -->|sì| K[Compra le azioni, limite 200 bps] K --> AD[Invio al contratto di airdrop] AD --> N
L'orario, la soglia e i limiti del diagramma sono le impostazioni di default. Il keeper fa un giro ogni cinque minuti; l'ETH di un vault si controlla una volta per finestra, al suo primo giro dopo la chiusura, mentre l'USDC che già detiene prosegue una volta dopo ogni conversione e al massimo una volta per finestra negli altri casi. L'invio al contratto dell'airdrop avviene una volta per finestra, dopo la sessione di default; i debiti, le commissioni LP, le consegne e, dal sesto ciclo di audit, i ticket del rail canonico vengono gestiti a ogni ciclo, qualunque sia la sessione.
La sua configurazione
Tutto passa per variabili d'ambiente: gli RPC di entrambe le chain, la chiave privata del
keeper, gli indirizzi dei contratti, l'intervallo, e le leve di sicurezza — dry run,
esecuzione singola, mercato aperto obbligatorio. Dal 2026-10-05 anche: le variabili
del passaggio dell'airdrop, sopra; l'incasso delle commissioni LP, KEEPER_COLLECT_FEES
(attiva di default), KEEPER_COLLECT_FEES_HOURS (24) e KEEPER_COLLECT_FEES_MIN_WEI (0);
l'allerta dopo esclusioni ripetute, KEEPER_BRIDGE_ALERT_AFTER_SKIPS (3); e la cartella
del file di stato, KEEPER_STATE_DIR. Dal 2026-10-06, KEEPER_CONVERT_ONCE_PER_WINDOW
(attiva di default): l'ETH di un vault si converte una volta per finestra e, dal sesto
ciclo di audit, il suo USDC parte una volta dopo ogni conversione e al massimo una volta
per finestra negli altri casi; un'esecuzione locale o di testnet la disattiva e converte,
compra e passa dal bridge a ogni giro. Dal sesto ciclo di audit anche
KEEPER_TICKET_ALERT_HOURS (6): sul rail canonico, le ore dopo le quali un ticket di cui
non si sa che sia stato eseguito è oggetto di un'allerta. KEEPER_STOCK_ROUTER, il router
che la factory indica per i nuovi vault, sceglie solo quale sessione di mercato abilita un
ciclo; ogni vault viene quotato sul proprio router.
Dal settimo ciclo di audit gli identificatori di chain, KEEPER_CHAIN_ID e
KEEPER_REMOTE_CHAIN_ID, vengono verificati rispetto agli RPC prima del primo ciclo: 1 con
4663 sulla mainnet, 11155111 con 46630 sulla testnet. Dall'ottavo ciclo di audit entrambi
devono essere impostati: KEEPER_CHAIN_ID sempre, KEEPER_REMOTE_CHAIN_ID con il bridge,
e il keeper si rifiuta di partire senza di essi (fino ad allora, se omessi,
KEEPER_CHAIN_ID si leggeva 11155111 e KEEPER_REMOTE_CHAIN_ID 4663). Dal decimo
ciclo di audit KEEPER_LOG_MAX_SELECTORS (1.000 di default, almeno 5) limita gli
indirizzi e i valori di evento che nomina una richiesta di log, contati come li
contano i nodi di Robinhood Chain; dietro l'endpoint pubblico di Sepolia, che
rifiuta dieci indirizzi o più, è impostata a 10. Due variabili
dicono quanti blocchi sotto l'ultimo si fermano le sue ricerche nei log:
KEEPER_LOG_LAG_BLOCKS (2, su Ethereum) e KEEPER_REMOTE_LOG_LAG_BLOCKS (12, su Robinhood
Chain); dall'ottavo ciclo di audit la prima indica anche quanto deve essere profondo il
blocco di un batch del bridge prima che il keeper lo segua, e la seconda quanti blocchi
sotto l'ultimo si legge un ticket. Le protezioni dell'oracolo non richiedono alcuna
variabile: il keeper le legge sull'oracolo di ogni vault speculare. Una variabile numerica
intera lasciata vuota prende il suo default, e una fuori dal suo intervallo ferma il
keeper all'avvio.
La chiave del keeper è una hot key, finanziata solo per il gas, e distinta da quella del deployer.
I suoi limiti
- Sul rail USDG, una consegna che il token cash ha rifiutato prima che il keeper partisse, o mentre era fermo, appare solo come un avvertimento quando fa lo sweep; il rail canonico trova una quota del genere nello stato stesso dell'hub remoto
- Fino al nono ciclo di audit il keeper non eseguiva da sé l'ultimo passo di una consegna
fallita (
lzCompose); da allora riesegue un passo bloccato, entro un limite, e un secondo fallimento viene lasciato a una persona, con il comando - La sorveglianza delle emergenze non conserva la propria posizione tra un riavvio e l'altro: gli eventi emessi mentre il keeper era fermo non vengono esaminati
- Un keeper interrotto nei pochi millisecondi tra un invio e la sua scrittura nel file di stato può inviare quella transazione ancora una volta al riavvio; i controlli dei contratti stessi fanno fallire la maggior parte di queste ripetizioni, al costo del loro gas
- La ricerca dell'arrivo di un trasferimento non legge mai due volte un blocco: una riorganizzazione che sposta un arrivo in un blocco già letto lascia quel trasferimento all'allerta di transito. Dal settimo ciclo di audit ogni ricerca nei log si ferma qualche blocco sotto l'ultimo, il che ritarda un accredito, e la sua allerta di transito, di quei pochi blocchi
- Dall'ottavo ciclo di audit un batch del bridge viene seguito una volta che il suo blocco è profondo qualche blocco, un ciclo più tardi di prima, e il batch successivo lo attende; una riorganizzazione più profonda di così non è coperta, come per le ricerche nei log. Un ticket eseguito negli ultimi pochi blocchi vi si legge ancora vivo finché l'ultimo blocco non supera la sua esecuzione di altrettanti blocchi: il ciclo successivo su una chain movimentata, più cicli su una tranquilla. Dal nono ciclo di audit uno che la riesecuzione stessa del keeper ha cancellato non viene né rieseguito di nuovo né segnalato nel frattempo, anche dopo un riavvio; uno rieseguito da qualcun altro in quei blocchi viene ritentato, fallisce in simulazione, e può ancora essere segnalato come vivo oltre le ore dell'allerta
- Il minuto durante il quale il keeper legge al blocco della sua ultima transazione non copre un nodo indietro di più di un minuto rispetto ai suoi pari
- Sul rail locale, l'apertura della finestra si conta una sola volta, senza ciò che la seconda transazione ripete (circa il 5 % del costo): un invio può partire valendo fino a tanto meno di quanto costa
- Dal decimo ciclo di audit, un'azione data a un vault dopo la chiusura, al di fuori di qualsiasi acquisto, parte con l'invio della sessione successiva, in una finestra più tarda: il keeper rilegge un vault senza nulla da inviare solo alla sessione successiva. Un acquisto di cui il keeper ha letto la ricevuta nell'ultimo giro della sessione potrebbe anche essere letto vuoto al primo giro dopo la chiusura da un nodo in ritardo rispetto a quella lettura, il che richiede un intervallo tra i giri più breve del ritardo di quel nodo (circa cinque secondi su Sepolia), mai i cinque minuti di default
Preflight
Prima di ogni esecuzione reale, un comando di preflight verifica, senza scrivere una sola transazione, che le reti rispondano, che gli indirizzi contengano codice, che i peer LayerZero dell'OFT USDG corrispondano, che USDG non sia messo in pausa dal suo emittente, Paxos, e che i pesi dei basket siano quelli previsti.
Dall'ottavo ciclo di audit verifica anche le protezioni dell'oracolo. Il suo manifest deve
dire come è impostato il controllo del sequencer, in un senso o nell'altro: nessun feed di
uptime e nessun periodo di grazia (il controllo disattivato, come oggi), o un feed con un
periodo di grazia. Legge quel feed quando c'è, e la pausa dell'oracolo del token di ogni
azione: un token in un'operazione societaria è un avvertimento, che non fa fallire la
verifica; un token senza il segnale fallisce. Dopo il deploy verifica che l'oracolo
deployato corrisponda al manifest, che lo stato del suo sequencer lasci passare i prezzi e
che il controllo di pausa di ogni azione sia attivo. Il comando inspect del keeper
stampa le stesse protezioni: il controllo del sequencer e il suo stato, e il controllo di
pausa di ogni azione e se il suo token è ora in pausa.
Dal nono ciclo di audit gira anche sul deploy dell'esecuzione su testnet con LayerZero (Sepolia e la testnet di Robinhood Chain), a partire dai due file di quell'esecuzione, con RPC nominati per quella coppia, così un RPC della mainnet non viene mai interrogato sulla testnet; qualsiasi altra coppia di chain, un misto compreso, viene rifiutata. Sulla testnet non legge alcun router Uniswap v3, che quella chain non ha, e verifica il feed ETH/USD rispetto all'heartbeat dato all'oracolo dell'esecuzione. Su entrambe le coppie verifica che il router remoto indichi il router v3 che dice il manifest. Eseguito sul deploy di testnet: 68 verifiche, tutte superate.
Si rifiuta di girare senza RPC piuttosto che riportare un falso successo.