$$\rightleftharpoonup{xx}$$
$$\longleftharp{xx}$$,
$$\longrightharp{xx}$$,
Input di studio
In questo studio, due smart contract Solidity sono stati utilizzati come input di verifica. Il primo era un contratto in stile Simple DAO utilizzato come caso di studio di rientranza. Il secondo era un contratto di bilancio semplificato/transizione di stato progettato per testare i vincoli a livello contrattuale contro la doppia spesa. Il codice sorgente originale di Solidity serviva come input ai processi di astrazione e verifica definiti in questo protocollo. Il processo generale di trasformazione utilizzato per tali contratti è illustrato nella Figura 1, che mostra la trasformazione graduale del codice sorgente Solidity nei modelli FSM, Event-B e SMV durante la verifica.
Modellazione del confine
La modellazione formale si concentra sulla logica controllo-flusso degli smart contract, inclusi il comportamento di ingresso e uscita delle funzioni, l'esecuzione interna e le transizioni di stato a livello contrattuale. La visibilità delle funzioni (pubblica, esterna, interna e privata) è stata rappresentata insieme al corrispondente comportamento della pila di chiamate rilevante per l'analisi della rientranza. I tipi di transizioni astratte (chiamata, invio, trasferimento) venivano trattati come operazioni per il trasferimento di etheri.
Gli invarianti a livello contrattuale sono stati definiti per garantire l'unicità delle transazioni e prevenire la doppia spesa all'interno del confine di astrazione. Per rappresentare i requisiti di selezione di validazione e integrità del blocco, il livello di astrazione logica del protocollo è stato definito in termini delle transizioni di stato a livello di protocollo sia di Proof of Work che di Proof of Stake.
Il confine di astrazione non includeva elementi a livello di rete, inclusi la programmazione dei messaggi da consegnare e i ritardi causati da un numero qualsiasi di salti, la risoluzione del fork, avversari di rete che impiegano strategie bizantine, la semantica della Macchina Virtuale Ethereum e la semantica del gas controllata dai nodi di rete, la propagazione delle eccezioni, le esecuzioni asincrone, il comportamento di fallback complesso e la finalità a livello di rete. Pertanto, i risultati della determinazione della doppia spesa si applicano solo agli invarianti a livello contrattuale e non costituiscono un accordo a livello di rete sulla finalità.
Strumenti e configurazione
La piattaforma Rodin veniva utilizzata per modellare, perfezionare, generare obblighi di prova e svolgere il modello Event-B (versione 3.7.0), che permetteva ai provatori PP, ML, SMT e Atelier-B di scaricare le prove automaticamente e in modo interattivo. nuXmv versione 2.0.0 veniva eseguita sui modelli SMV generati in modalità completa di esplorazione CTL per effettuare il controllo dei modelli CTL.
Tutte le esecuzioni di verifica venivano eseguite in un ambiente computazionale controllato utilizzando Ubuntu 22.04 LTS, OpenJDK 11 e Python 3.10.12. Web3 (7.6.0), NetworkX (3.4.2), Matplotlib (3.8.0), Graphviz (0.20.3), NumPy (1.26.4) e Pandas (2.2.2) sono stati utilizzati per implementare elementi di simulazione e visualizzazione. Questo progetto garantiva che i risultati formali di verifica e simulazione potessero essere riprodotti quando eseguiti nelle stesse condizioni di esecuzione.
Flusso di lavoro di trasformazione
Questo processo di verifica era composto da quattro fasi. I contratti di solidità furono inizialmente tradotti in una rappresentazione Finite State Machine (FSM-SC). Il modello FSM-SC fu successivamente codificato in Event-B, con invarianti e gradi di affinamento chiaramente specificati. L'astrazione FSM-SC è stata tradotta in un modello SMV in nuXmv. I risultati della verifica sono stati riportati come statistiche sull'addebitamento dell'obbligo di prova e sul controllo del modello CTL, e sono stati forniti controesempi come casi diversi.
Costruzione FSM
Ogni contratto è stato astrattato come una Macchina a Stati Finiti definita come:

Dove:
S = insieme degli stati
S₀ = stato iniziale
T = relazione di transizione
V = mappatura della visibilità
G = predicati di guardia
A = azioni/aggiornamenti di stato.
Algoritmo 1: Costruzione FSM da Solidity
Input: codice sorgente Solidity
Output: FSM-SC
1) Parsi l'albero sintattico astratto del contratto di Solidity.
2) Creare lo stato iniziale S₀ dalla definizione del costruttore.
3) Per ogni funzione di Solidity f, creare un S_f di stato di controllo distinto e registrare la visibilità V(f) ∈ {pubblico, esterno, interno, privato}.
4) Per ogni istruzione all'interno della funzione f, si deriva una transizione t estraendo predicati di guardia da condizioni di necessità/asserzione e azioni da aggiornamenti delle variabili di stato.
5) Aggiungere la transizione t a T.
6) Creare tipi di transizione espliciti per operazioni di trasferimento Ether (chiamata, invio, trasferimento), chiamate interne ed esterne, delegatecall, autodistruzione, uso di tx.origin, branch condizionali e costrutti di loop.
7) Restituire FSM-SC = (S, S₀, T, V, G, A).
E ogni funzione di solidità è associata a uno stato di controllo FSM diverso. Per la comprensione delle vulnerabilità, i flussi di esecuzione rilevanti per la vulnerabilità, ad esempio tali flussi con chiamate esterne seguite da aggiornamenti di bilanciamento, sono stati esplicitamente astratti in transizioni ordinate. La Figura 2 descrive un esempio di diagramma FSM-SC per il contratto in stile SimpleDAO, mostrando come gli stati di ingresso, le transizioni di chiamata esterna e le sequenze di aggiornamento di stato siano state astrstrate durante questa fase di costruzione.
Codifica di FSM in Evento-B
La transizione FSM era rappresentata come costrutti Event-B. Tutte le transizioni corrispondono a eventi dell'Evento-B, inclusi particolari guardie e azioni.
Algoritmo 2: FSM a codifica Event-B
Input: FSM-SC
Output: Macchina Event-B e contesto
1) Definire STATE_SET e FUNZIONE nel contesto contrattuale.
2) Dichiarare variabili che rappresentano lo stato di controllo FSM e lo stato a livello contrattuale.
3) Rappresentare ogni stato di controllo FSM s ∈ S usando current_state ∈ STATE_SET.
4) Per ogni transizione (s → s′, g, a), creare un evento Event-B E_t con:
5) DOVE current_state = s ∧ g
6) ALLORA current_state := s′ ∥ applicare(a)
7) Codificare i vincoli di visibilità usando guardie derivate da V(f).
8) Definire gli invarianti inv1–inv9 per catturare le proprietà di sicurezza e coerenza.
9) Definire l'INIZIALIZZAZIONE assegnando S₀ e valori predefiniti.
L'insieme degli stati, lo stato corrente, la visibilità della funzione, lo stack di chiamate, il timestamp della transazione, lo stato del trasferimento Ether, il flag di chiamata delegata, il flag di autodistruzione e la condizione di verifica sono alcune delle variabili catturate nel modello Event-B. Gli invarianti (inv1-inv9) e le azioni delle inizializzazioni (act1-act6) corrispondono a quelle della specifica formale. La Figura 3 fornisce anche una rappresentazione grafica di come gli eventi chiave rilevanti per le vulnerabilità, in particolare le transizioni legate alla reentranza, vengano mantenuti nella codifica Event-B. Questa figura spiega come i modelli strutturali del modello FSM-SC mostrati nella Figura 2 siano mappati in eventi Event-B verificabili.
Strategia di raffinamento
Furono implementati due livelli di perfezionamento. Il flusso di controllo contrattuale ad alto livello e gli invarianti core erano rappresentati a livello astratto. Il livello raffinato ha aggiunto restrizioni specifiche per contratto, tra cui restrizioni di call-stack, restrizioni di visibilità e condizioni di prevenzione del rientro.
Algoritmo 3: Raffinatezza e liberazione proof-obligation
Input: Macchina astratta e macchina raffinata
Output: Obblighi di prova espulsi e statistiche
1) Generare obblighi di dimostrazione per la macchina astratta in Rodin.
2) Eseguire i test automatici abilitati e registrare i risultati delle scarre.
3) Generare obblighi di rifinimento per la macchina raffinata.
4) Applicare i provers automatici agli obblighi di raffinamento.
5) Svolgere gli obblighi rimanenti in modo interattivo quando necessario.
6) Statistiche di prova di esportazione e rapporti di stato.
La segnalazione delle prove includeva il numero di invarianti, i livelli di raffinamento, gli obblighi di prova generati, il tasso di scarico automatico, il tasso di scarico interattivo e il tasso di scarico finale.
Specifica delle proprietà CTL e controllo dei modelli
L'astrazione FSM-SC è stata tradotta in un modello SMV per la verifica temporale in tempo di ramificazione in nuXmv.
Algoritmo 4: Controllo FSM a SMV e CTL
Output: risultato della verifica PASS/FAIL e tracce controesempi (se presenti)
1. Registrare gli stati di controllo FSM come uno stato di variabile SMV enumerato.
2. Convertire le transizioni FSM in assegnazioni protette (stato successivo).
3. Mantenere segnalazioni per condizioni rilevanti alla vulnerabilità, come chiamate esterne e aggiornamenti di bilanciamento.
4. Codificare le proprietà CTL in nuXmv ed eseguire il controllo dei modelli.
5. Se una proprietà fallisce, generare tracce controesempi che rappresentano sequenze di transizione FSM.
Il controllo CTL includeva requisiti per ordini di rientranza, la finalizzazione degli aggiornamenti di stato dopo le operazioni di trasferimento, la limitazione dell'ingresso ricorsivo non vincolato nelle sezioni critiche ed evitare blocchi.
Proprietà di sicurezza verificate
Il contratto in stile Simple DAO che impedisce la rientranza è stato confermato garantendo un ordine sicuro tra chiamate esterne e aggiornamenti di stato tramite invarianti e vincoli CTL. Quando consentito, queste guardie controllavano i vincoli di controllo degli accessi, assicurandosi che le transizioni non autorizzate fossero limitate da invarianti. Il modello del registro ridotto ha verificato gli invarianti dell'unicità delle transazioni e della coerenza del registro al livello di prevenzione all'interno del contratto.
Risultati riportati
La sezione Risultati fornisce report sulle metriche strutturali FSM, sulle metriche del modello Event-B e sulle statistiche per la verifica proof of obligation e CTL. I risultati della dimostrazione formale, e quelli del modello di verifica CTL, sono forniti separatamente per distinguere le prove di garanzie di correttezza da parte degli invarianti e le prove di una verifica temporale tramite la verifica temporale.