Elenco delle vulnerabilità comuni
Le vulnerabilità degli smart contract, una volta distribuiti, possono portare a conseguenze catastrofiche e spesso irreversibili. Lo sfruttamento di queste debolezze rappresenta una sfida importante per la sicurezza della blockchain, causando il furto di miliardi di dollari in asset e minando la fiducia degli utenti. Raggiungere una sicurezza robusta per gli smart contract è quindi fondamentale. Richiede più della semplice distribuzione del codice sulla blockchain. Richiede pratiche rigorose di codifica sicura, test approfonditi e, spesso, audit indipendenti degli smart contract per identificare i problemi prima che possano essere sfruttati. Comprendere le insidie comuni – dagli attacchi di rientranza come il famoso hack di The DAO agli errori logici sottili e alle vulnerabilità di Ethereum come gli overflow di interi – è il primo passo critico verso la mitigazione del rischio. Questa pagina fornisce una panoramica essenziale delle vulnerabilità frequenti degli smart contract per aumentare la consapevolezza e promuovere uno sviluppo più sicuro nell'ecosistema decentralizzato.
- Reentrancy
- Unexpected Ether
- DoS
- Overflow
- tx.origin Authentication
- Access Control
- Weak Randomness
- Hash Collision
- Precision Loss
- msg.value in loop
- ecrecover
- Replay
- Inheritance Order
- MEV
La rientranza è una delle prime e più devastanti vulnerabilità degli smart contract, responsabile di exploit storici significativi come l'hack di The DAO, che ha portato a un hard fork di Ethereum. Rimane una minaccia critica con conseguenze potenzialmente terribili, capace di prosciugare i contratti di tutti i fondi.
La vulnerabilità deriva da un comune anti-pattern di codifica, spesso ereditato dalle abitudini del Web2: eseguire interazioni esterne (come trasferire Ether o chiamare un altro contratto) prima di aggiornare lo stato interno del contratto (ad esempio, i saldi degli utenti). Quando un contratto invia Ether o chiama un contratto esterno, trasferisce temporaneamente il controllo dell'esecuzione. Un attaccante che controlla il contratto ricevente può sfruttare ciò richiamando immediatamente il contratto originale, spesso tramite la funzione receive o fallback attivata dal trasferimento di Ether, o attraverso gli hook degli standard dei token.
Poiché lo stato del contratto originale (ad esempio, il saldo dell'attaccante) non è stato ancora aggiornato, la chiamata rientrante supera i controlli iniziali, consentendo all'attaccante di ripetere l'azione (come prelevare fondi) più volte all'interno della stessa transazione. Questo ciclo ricorsivo continua finché i fondi non vengono prosciugati o i limiti di gas vengono raggiunti.
Sebbene l'attacco classico coinvolga una singola funzione, esistono varianti più complesse, come la rientranza inter-funzionale (che sfrutta lo stato condiviso tra funzioni) e la rientranza di sola lettura (read-only reentrancy, che manipola le letture dello stato tramite funzioni di visualizzazione - view functions).
La difesa principale è il pattern Checks-Effects-Interactions (CEI): eseguire tutti i controlli necessari, quindi aggiornare le variabili di stato (effetti) e solo dopo questi aggiornamenti, interagire con contratti esterni.
La vulnerabilità "Manipolazione del saldo tramite ricezione inattesa di Ether" sorge perché gli smart contract Solidity possono ricevere Ether tramite meccanismi che bypassano le loro funzioni `receive` e `fallback` definite. I metodi principali per questa iniezione "inattesa" di Ether sono l'opcode `selfdestruct`, che trasferisce forzatamente il saldo di un contratto in fase di distruzione a un indirizzo designato, e le transazioni coinbase (ricompense per il blocco).
Il problema fondamentale è che questi trasferimenti forzati aumentano direttamente il saldo Ether del contratto (`address(this).balance`) senza attivare la logica programmata del contratto per la gestione dei fondi in entrata. Questo infrange un assunto o invariante comune, spesso implicito: che il saldo effettivo del contratto rifletta accuratamente la somma dei fondi elaborati tramite le sue funzioni `payable` previste o contabilizzati internamente.
I contratti che utilizzano erroneamente `address(this).balance` per controlli logici critici diventano vulnerabili. Ad esempio, i contratti potrebbero verificare che `address(this).balance == importoAtteso`. Un attaccante può sfruttare ciò utilizzando `selfdestruct` per inviare una piccola quantità di Ether, manipolando così il saldo. Questo può portare a:
Denial of Service (DoS): I controlli di uguaglianza stretta possono essere permanentemente compromessi, rendendo le funzioni inutilizzabili.
Manipolazione della logica: Le soglie possono essere raggiunte prematuramente, innescando modifiche di stato o pagamenti non intenzionali.
Violazioni di `assert`: In rari casi, il saldo inatteso potrebbe portare a incoerenze di stato interne che causano il fallimento di un `assert()`, consumando tutto il gas della transazione.
La mitigazione fondamentale consiste nel non fare mai affidamento su `address(this).balance` per la logica del contratto. Invece, i contratti devono mantenere una contabilità interna accurata utilizzando variabili di stato dedicate, aggiornandole solo all'interno di funzioni legittime per la gestione dei fondi. Tutti i controlli critici e le transizioni di stato devono basarsi su queste variabili interne affidabili.
Le vulnerabilità di Negazione del Servizio (Denial of Service, DoS) negli smart contract Solidity mirano a interrompere o bloccare completamente la funzionalità prevista del contratto, impedendo agli utenti legittimi di accedere ai suoi servizi. Il "servizio" negato è la capacità del contratto di eseguire le sue funzioni come programmato e atteso dai suoi utenti.
Gli attaccanti ottengono ciò sfruttando falle nella logica del codice del contratto, manipolando le limitazioni delle risorse (principalmente il gas) o sfruttando le dipendenze esterne.
Gli schemi comuni includono:
Esaurimento del Gas (Gas Exhaustion): Creare transazioni o manipolare lo stato per far sì che i costi di esecuzione della funzione superino il limite di gas del blocco o il limite di gas della transazione. Ciò comporta spesso l'attivazione di operazioni computazionalmente costose o, molto comunemente, l'iterazione su array non limitati la cui dimensione può crescere indefinitamente. Man mano che i dati crescono, il costo del gas del ciclo supera i limiti, rendendo la funzione inutilizzabile.
Revert (Annullamenti) Imprevisti: Causare l'annullamento (revert) imprevisto di funzioni critiche. Ciò può essere attivato forzando chiamate esterne fallite (ad esempio, inviando fondi a un contratto che li rifiuta), manipolando lo stato per far fallire le condizioni `require` o altre eccezioni non gestite. Se un contratto dipende da una chiamata esterna che fallisce (forse indotta maliziosamente), l'intera operazione può essere bloccata.
La natura immutabile degli smart contract rende il DoS particolarmente grave. Un attacco riuscito può portare a fondi bloccati permanentemente o a funzionalità irrecuperabile, poiché il codice del contratto non può essere facilmente corretto dopo il deployment.
La mitigazione si basa su pratiche di codifica sicure: l'uso di operazioni limitate invece di cicli illimitati, preferire i pattern "pull-payment" (pagamento su richiesta) rispetto all'invio di fondi agli utenti ("push-payment"), il che isola i fallimenti dei trasferimenti, la gestione robusta dei fallimenti delle chiamate esterne (ad esempio, l'uso di `try/catch` dove appropriato) e un'attenta gestione del gas.
Le vulnerabilità di overflow e underflow di interi rappresentano una minaccia persistente nel dominio della sicurezza degli smart contract, derivante dalle limitazioni fondamentali dell'aritmetica degli interi a dimensione fissa. Un overflow si verifica quando il risultato di un'operazione aritmetica supera il valore massimo per il suo tipo e un underflow si verifica quando il risultato scende al di sotto del valore minimo.
Storicamente, nelle versioni di Solidity precedenti alla 0.8.0, questi errori aritmetici causavano un "wrap around" (ritorno a zero/capo) silenzioso del valore. Ad esempio, aggiungere 1 al valore massimo di uint8 (255) risultava in 0, e sottrarre 1 da 0 risultava in 255. Questo comportamento prevedibile ma non controllato era una fonte principale di exploit, consentendo agli aggressori di manipolare i saldi dei token (ad esempio, creando enormi quantità o prosciugando fondi causando il wrap around dei saldi), bypassare controlli di sicurezza critici, corrompere lo stato del contratto o causare denial-of-service (negazione del servizio).
La versione 0.8.0 di Solidity ha introdotto un miglioramento cruciale della sicurezza: le operazioni aritmetiche standard (+, -, *, /) ora eseguono controlli di overflow e underflow per impostazione predefinita. Se un'operazione dovesse comportare un wrap around, la transazione viene invece annullata (reverts), prevenendo la corruzione silenziosa dello stato.
Tuttavia, il rischio non è completamente eliminato. Gli sviluppatori possono bypassare esplicitamente questi controlli predefiniti utilizzando blocchi `unchecked`, spesso per l'ottimizzazione del gas. Il codice all'interno di un blocco `unchecked` ritorna al pericoloso comportamento di wrap around silenzioso delle versioni precedenti alla 0.8.0 e richiede una validazione estremamente attenta. Allo stesso modo, il codice assembly EVM a basso livello non beneficia dei controlli di Solidity, richiedendo una gestione manuale della sicurezza per le operazioni aritmetiche. Inoltre, le operazioni di scorrimento (shift) (<<, >>) rimangono non controllate anche nella modalità predefinita >=0.8.0 e troncheranno i risultati. Un type casting (conversione di tipo) improprio (ad esempio, convertire un grande uint256 in un tipo più piccolo come uint8) può anche portare al troncamento del valore, causando potenzialmente un comportamento inaspettato nei calcoli successivi.
Pertanto, sebbene significativamente mitigato per impostazione predefinita nel Solidity moderno, l'overflow/underflow di interi rimane una vulnerabilità rilevante, specialmente nei contratti legacy, nel codice che utilizza blocchi `unchecked` o assembly, e attraverso specifiche operazioni non controllate o conversioni di tipo negligenti.
L'uso di `tx.origin` per la logica di autorizzazione negli smart contract Solidity rappresenta una vulnerabilità di sicurezza critica. Deriva da un'errata comprensione fondamentale del suo comportamento rispetto a `msg.sender`. Mentre `tx.origin` identifica costantemente l'EOA (Externally Owned Account / Account di Proprietà Esterna) che ha avviato la transazione, `msg.sender` identifica il chiamante immediato. Utilizzare `tx.origin` per i controlli delle autorizzazioni non riesce a convalidare l'entità che interagisce direttamente con il contratto.
Questa falla apre le porte ad attacchi di tipo phishing, in cui un contratto intermediario malevolo, invocato da un utente privilegiato, può chiamare con successo funzioni protette sul contratto di destinazione. Il controllo `tx.origin` convalida erroneamente l'utente originale, consentendo al contratto malevolo di eseguire azioni con l'autorità dell'utente. Le conseguenze vanno dal furto diretto di Ether e token alla manipolazione non autorizzata dello stato del contratto, causando potenzialmente perdite finanziarie significative e danni irreparabili all'integrità e alla reputazione del protocollo.
Il Controllo degli Accessi Insufficiente è una vulnerabilità critica negli smart contract Solidity in cui le restrizioni su chi può eseguire quali funzioni sono mancanti o implementate in modo improprio. Poiché Solidity non dispone di modelli di autorizzazione integrati, gli sviluppatori devono aggiungere manualmente controlli, spesso utilizzando modificatori come onlyOwner o implementando il Controllo degli Accessi Basato sui Ruoli (RBAC). Non farlo correttamente crea falle di sicurezza.
Questa vulnerabilità si manifesta tipicamente come funzioni non protette. Funzioni destinate a operazioni amministrative o sensibili - come il trasferimento della proprietà del contratto (changeOwner), il prelievo di fondi (withdraw), la messa in pausa del contratto, il conio di token o persino la distruzione del contratto (selfdestruct) - potrebbero essere lasciate richiamabili da qualsiasi account esterno. Ciò accade spesso a causa di modificatori di controllo degli accessi mancanti o di una visibilità della funzione errata (ad esempio, le funzioni sono public per impostazione predefinita se non specificato diversamente). Anche le funzioni di inizializzazione esposte, che dovrebbero essere eseguite una sola volta, possono costituire un vettore di attacco se rimangono richiamabili dopo il deployment, consentendo potenzialmente agli aggressori di reimpostare la proprietà o parametri critici.
Le conseguenze sono gravi, spaziando dall'ottenimento di privilegi amministrativi da parte di utenti non autorizzati al furto completo dei fondi gestiti dal contratto o alla distruzione irreversibile del contratto stesso. Exploit reali, come gli incidenti del wallet Parity e l'hack di LAND Token, dimostrano il potenziale devastante di un controllo degli accessi insufficiente.
La mitigazione comporta l'applicazione rigorosa dei controlli di accesso a tutte le funzioni sensibili, l'adesione al Principio del Privilegio Minimo, l'uso di pattern consolidati come Ownable o RBAC (spesso tramite librerie come quelle di OpenZeppelin) e l'esecuzione di test e audit approfonditi.
Generare casualità sicura sulla Ethereum Virtual Machine (EVM) è complesso a causa della sua natura deterministica, essenziale per il consenso della rete. Questo crea un'"illusione di entropia" in cui valori apparentemente casuali derivati puramente da dati on-chain sono in realtà prevedibili.
Gli sviluppatori spesso utilizzano impropriamente variabili di blocco facilmente disponibili come block.timestamp, blockhash e il prevrandao post-Merge (accessibile tramite block.difficulty) come fonti di pseudo-casualità. Queste variabili non sono sicure perché possono essere previste o influenzate.
I miner (in Proof-of-Work) o i validatori (in Proof-of-Stake) possono manipolare questi valori in una certa misura per ottenere un vantaggio ingiusto, spesso come parte di strategie MEV (Maximal Extractable Value). Fondamentalmente, anche gli utenti regolari o i contratti attaccanti possono spesso prevedere i risultati se la logica di casualità si basa solo su input noti prima o durante l'esecuzione della transazione, consentendo exploit come il front-running. Questa prevedibilità compromette l'equità di applicazioni come lotterie, giochi e mint di NFT.
La collisione di hash tramite `abi.encodePacked` con tipi dinamici multipli non deriva da debolezze nella funzione di hash sottostante Keccak-256, ma dal modo in cui i dati vengono codificati prima di essere sottoposti ad hashing, specificamente quando si utilizza la funzione `abi.encodePacked` di Solidity. A differenza dello standard `abi.encode`, che aggiunge padding agli argomenti fino a 32 byte e include prefissi di lunghezza per i tipi dinamici, `abi.encodePacked` crea una codifica compatta e non standard concatenando gli argomenti utilizzando il numero minimo di byte richiesti, omettendo il padding per i tipi statici piccoli e, cosa fondamentale, omettendo le informazioni sulla lunghezza per i tipi dinamici come `string`, `bytes` o array dinamici.
Il problema principale si verifica quando `abi.encodePacked` viene utilizzato con due o più argomenti di tipo dinamico adiacenti. Poiché la lunghezza di ciascun argomento dinamico non viene codificata, il confine tra di essi diventa ambiguo nella stringa di byte risultante. Questa ambiguità rende possibile, spesso in modo banale, creare diversi set di input logici che producono esattamente la stessa sequenza di byte compattata. Ad esempio, `abi.encodePacked("a", "bc")` produce lo stesso output di byte di `abi.encodePacked("ab", "c")`.
Quando questo output di byte identico viene successivamente sottoposto ad hashing (ad es., `keccak256(abi.encodePacked(...))`), si ottiene lo stesso valore di hash, una collisione di hash indotta dalla codifica.
Questa vulnerabilità di collisione della codifica può essere sfruttata in diversi modi se l'hash risultante viene utilizzato in un contesto sensibile alla sicurezza:
Bypass della verifica della firma: Un utente malintenzionato può prendere una firma valida creata per un set di parametri e riutilizzarla con un set di parametri diverso e dannoso che produce un hash in collisione. La verifica della firma del contratto (`ecrecover`) avrà successo, garantendo un'esecuzione non autorizzata.
Corruzione dello stato tramite collisioni di chiavi di mapping: Se l'hash soggetto a collisioni viene utilizzato come chiave in un mapping (`mapping(bytes32 => ...)`), un utente malintenzionato può creare input per generare una chiave che collide con la chiave di un utente legittimo, potenzialmente sovrascrivendo i suoi dati, bypassando i controlli di accesso o causando un denial of service.
Problemi di autenticazione dei messaggi: La vulnerabilità compromette i controlli che si basano sull'hash per garantire l'integrità dei dati, poiché messaggi logici diversi possono apparire identici dopo l'hashing.
Le conseguenze di uno sfruttamento riuscito possono essere gravi, tra cui l'accesso non autorizzato a funzioni o dati, il furto diretto di fondi (Fund Theft), la corruzione critica dello stato e il Denial of Service (DoS).
Le vulnerabilità legate alla perdita di precisione derivano dall'uso dell'aritmetica intera e dalla mancanza di supporto nativo per i numeri in virgola mobile. Questo design dà priorità all'esecuzione deterministica ma richiede agli sviluppatori di gestire manualmente i valori frazionari, creando opportunità per errori.
Il problema principale è il troncamento della divisione intera: Solidity scarta i resti e arrotonda i risultati della divisione verso lo zero. Questo comportamento prevedibile può essere sfruttato, spesso attraverso schemi come:
Divisione prima della moltiplicazione: Calcolare (a / b) * c invece di (a * c) / b tronca il risultato intermedio a / b, amplificando la perdita di precisione.
Arrotondamento per difetto a zero: Se il numeratore A è minore del denominatore B (ed entrambi sono positivi), A / B risulta sempre 0. Questo è rischioso per calcoli che coinvolgono piccole commissioni, ricompense o conversioni di token.
Gli aggressori sfruttano queste proprietà matematiche per manipolare la logica del contratto a scopo di lucro finanziario. Le strategie comuni includono:
Manipolazione dello stato/prezzo: Innescare errori di arrotondamento per distorcere valori critici del protocollo come tassi di cambio, riserve di pool, prezzi delle quote dei vault o rapporti di collateralizzazione, che possono poi essere sfruttati in transazioni successive.
Attacco ai casi limite: Utilizzare transazioni con input molto piccoli o input progettati per interagire con grandi valori interni per massimizzare l'impatto del troncamento, spesso causando risultati di calcolo pari a zero.
Attacchi riusciti basati sulla perdita di precisione possono portare a significative conseguenze negative:
Costi ridotti: Gli aggressori pagano commissioni/costi inferiori o nulli.
Guadagni gonfiati: Gli aggressori ricevono illegittimamente più token, quote o ricompense.
Opportunità di arbitraggio: Creare differenze di prezzo artificiali all'interno del protocollo che gli aggressori possono sfruttare.
Aggiramento dei meccanismi di rischio: Bypassare liquidazioni o altri controlli di sicurezza a causa di calcoli imprecisi.
Esaurimento graduale dei fondi: Sottrarre valore attraverso transazioni ripetute che sfruttano minuscoli errori di arrotondamento ("attacchi da 1 wei").
La mitigazione comporta un'attenta gestione dell'aritmetica, come eseguire le moltiplicazioni prima delle divisioni, utilizzare la scalatura numerica (simulando la matematica a virgola fissa), impiegare librerie matematiche specializzate e implementare una logica di arrotondamento appropriata. I controlli standard sull'overflow (come SafeMath o Solidity >=0.8) non prevengono la perdita di precisione dovuta alla divisione.
Questa vulnerabilità si verifica quando uno smart contract utilizza impropriamente msg.value all'interno di un ciclo. Il problema principale deriva dal fatto che msg.value rimane costante per l'intero contesto di esecuzione di una transazione. Se un ciclo itera più volte, eseguendo controlli o azioni basati su questo msg.value iniziale in ogni iterazione senza tracciare correttamente il valore cumulativo elaborato o speso attraverso quelle iterazioni, si crea un'opportunità di exploit.
Un utente malintenzionato (attacker) può sfruttare questa situazione inviando una quantità specifica di Ether per attivare la funzione vulnerabile. All'interno del ciclo, controlli come require(msg.value >= amount_per_item) potrebbero essere superati ripetutamente, oppure gli aggiornamenti di stato potrebbero utilizzare erroneamente l'intero valore iniziale di msg.value più volte. Ciò accade perché la logica del contratto non tiene conto del valore effettivamente 'speso' o allocato nelle iterazioni precedenti dello stesso ciclo.
Questo difetto consente a un utente malintenzionato di attivare azioni (come trasferimenti di Ether o accrediti di saldo interno) il cui valore totale supera significativamente l'Ether effettivamente inviato con la transazione.
ecrecover è un precompilato EVM essenziale che consente agli smart contract di recuperare l'indirizzo del firmatario da un hash del messaggio e una firma ECDSA (v, r, s). Questo abilita funzionalità cruciali come la verifica di messaggi firmati off-chain per meta-transazioni o funzioni `permit`. Tuttavia, il suo uso diretto presenta rischi significativi per la sicurezza, spesso sottovalutati, se non gestito con attenzione.
Vulnerabilità del Ritorno dell'Indirizzo Zero: Un problema critico deriva dalla gestione unica degli errori di `ecrecover`. Quando gli viene presentata una firma non valida o matematicamente impossibile, non annulla (revert) la transazione. Invece, fallisce silenziosamente e restituisce l'indirizzo zero (`address(0)`). I contratti che chiamano `ecrecover` ma mancano di un controllo sull'indirizzo zero sono altamente vulnerabili. Un utente malintenzionato può inviare intenzionalmente dati di firma non validi, facendo sì che `ecrecover` restituisca `address(0)`. Se questo output non viene esplicitamente controllato e rifiutato, il contratto potrebbe procedere in modo errato, trattando `address(0)` come il firmatario legittimo. Ciò può portare a conseguenze gravi, come modifiche di stato non autorizzate, emissioni di eventi errate o concessione di permessi, specialmente se l'indirizzo zero ha privilegi speciali o uno stato significativo all'interno della logica specifica del contratto. Il codice robusto deve sempre convalidare `recoveredAddress != address(0)` immediatamente dopo la chiamata a `ecrecover`.
Vulnerabilità della Malleabilità della Firma: Il secondo rischio principale deriva da una proprietà intrinseca dell'algoritmo ECDSA stesso: la malleabilità della firma. Per un dato messaggio e chiave privata, possono esistere più rappresentazioni di firma distinte ma crittograficamente valide (specificamente, una firma che utilizza il componente `s` può spesso essere trasformata in una firma valida utilizzando `n-s`, dove `n` è l'ordine della curva). Questo diventa una vulnerabilità se i contratti assumono erroneamente che una firma per un messaggio sia unica. Gli attaccanti possono sfruttare questo bypassando i controlli di unicità. Ad esempio, se un contratto utilizza l'hash della firma stessa come nonce per prevenire i replay (un pattern errato), un utente malintenzionato può prendere una firma valida, calcolare la sua controparte malleabile e inviarla per eseguire nuovamente l'azione, poiché gli hash delle firme differiranno. Può anche causare comportamenti imprevisti o replay in sistemi che si aspettano una forma di firma specifica, se sistemi esterni o parti della logica del contratto non sono stati progettati per gestire entrambe le forme di firma valide. Una mitigazione efficace implica l'applicazione della canonizzazione della firma – un controllo implementato in modo robusto in librerie standard come ECDSA di OpenZeppelin, che dovrebbe essere preferito all'uso diretto di `ecrecover`.
Attacco Replay Cross-Chain (CCRA): Una transazione validamente eseguita su una chain EVM viene catturata e reinviata con successo su una chain EVM differente. Questo sfrutta le somiglianze nei formati delle transazioni e nelle firme tra le diverse chain, specialmente quando alle transazioni mancano identificatori univoci della catena (Chain ID). L'hard fork di Ethereum/Ethereum Classic è un classico esempio in cui questo rischio si è concretizzato. EIP-155 è stato introdotto per mitigare questo problema incorporando il Chain ID nelle firme di transazione standard, rendendole specifiche per la catena. Tuttavia, gli smart contract che utilizzano una verifica della firma personalizzata devono anch'essi controllare esplicitamente il Chain ID. L'exploit da 20 milioni di dollari su Optimism ai danni di Wintermute è derivato da una tale mancata verifica del Chain ID in un contratto distribuito cross-chain.
Replay a Livello di Smart Contract (Stessa Catena): Un messaggio firmato o una transazione viene rieseguito (replayed) contro lo stesso smart contract, o potenzialmente contro un altro contratto sulla stessa chain. Questo tipicamente sfrutta vulnerabilità all'interno della logica stessa del contratto, in particolare negli schemi di verifica della firma personalizzati usati per funzionalità come le meta-transazioni o le funzioni `permit` di ERC-20. Il difetto più comune è l'assenza o l'implementazione impropria di un nonce a livello applicativo. Un nonce («numero usato una sola volta») è un contatore univoco associato al firmatario che deve essere incluso nel digest del messaggio firmato e tracciato on-chain dal contratto per garantire che ogni specifica firma autorizzi una sola azione.
Il supporto di Solidity per l'ereditarietà multipla consente a un contratto di ereditare funzionalità da più contratti padre contemporaneamente. Sebbene potente per il riutilizzo del codice, ciò introduce una potenziale ambiguità, nota come "Problema del Diamante": se due o più contratti base definiscono una funzione con lo stesso nome e parametri.
Per risolvere questo problema, Solidity impiega l'algoritmo di linearizzazione C3 per stabilire un Ordine di Risoluzione dei Metodi (Method Resolution Order, MRO) singolo e deterministico per ogni contratto. Questo MRO detta la sequenza precisa in cui vengono controllati i contratti base durante la risoluzione delle chiamate di funzione.
La vulnerabilità deriva direttamente da come viene determinato questo MRO. Il fattore cruciale è l'ordine in cui lo sviluppatore elenca i contratti base nell'istruzione `is`. Solidity richiede di elencare i contratti dal "più simile alla base" al "più derivato". Questo ordine specificato influenza direttamente l'MRO finale generato dall'algoritmo C3.
La vulnerabilità si verifica quando lo sviluppatore fornisce un ordine di ereditarietà che non corrisponde alla gerarchia logica o alla priorità prevista. Se un contratto più generale viene elencato dopo uno più specifico, o se l'ordine causa in altro modo la prioritizzazione da parte dell'MRO di un'implementazione di funzione non intenzionale, il contratto può comportarsi in modo imprevisto. Ad esempio, una chiamata potrebbe risolversi in una funzione base priva di controlli di sicurezza critici o di logica aggiornata implementata in un override del contratto derivato previsto ma ordinato in modo errato.
Le conseguenze dell'esecuzione della funzione errata a causa di un ordine di ereditarietà errato includono l'aggiramento dei controlli di accesso, l'esecuzione di logica aziendale obsoleta o errata, la corruzione dello stato e potenziali perdite finanziarie. Essenzialmente, il flusso di esecuzione effettivo del contratto devia dal progetto dello sviluppatore, minando la sicurezza e la funzionalità.
Il Valore Massimo Estraibile (Maximal Extractable Value - MEV) rappresenta il profitto che i produttori di blocchi e i cercatori specializzati (searchers) possono catturare manipolando l'inclusione e l'ordine delle transazioni all'interno di un blocco, oltre alle ricompense standard per blocco e alle commissioni sul gas. Originariamente definito "Valore Estraibile dai Miner" (Miner Extractable Value), il nome si è evoluto in "Massimo" per riflettere la sua avidità di ricompensa.
Il MEV si verifica perché le transazioni in sospeso si trovano spesso in un'area di attesa pubblica chiamata mempool, visibile a chiunque. I produttori di blocchi hanno l'autorità di decidere l'ordine finale delle transazioni in un blocco. Bot automatizzati, gestiti da "cercatori" (searchers), monitorano costantemente la mempool, simulano potenziali esiti e sfruttano opportunità redditizie ordinando strategicamente le transazioni, spesso utilizzando aste per il gas (Priority Gas Auctions - PGA) per assicurarsi un posizionamento preferenziale.
Le strategie MEV comuni, spesso viste come attacchi, includono:
Front-Running: Posizionare la transazione di un attaccante prima della transazione di una vittima (ad esempio, una grande operazione su un DEX) per trarre profitto dall'impatto anticipato sul prezzo.
Attacchi Sandwich (Sandwich Attacks): Una combinazione di front-running e back-running (seguire) l'operazione DEX di una vittima per catturare la differenza di prezzo (slippage o slittamento) causata dalla manipolazione.
Liquidità Just-in-Time (JIT Liquidity): Aggiungere e rimuovere temporaneamente liquidità attorno a un grande scambio (swap) su DEX a liquidità concentrata per catturare le commissioni.
Manipolazione degli Oracoli (Oracle Manipulation): Sfruttare aggiornamenti o imprecisioni degli oracoli di prezzo a scopo di lucro, spesso influenzando i protocolli di prestito.
Altri tipi includono lo sniping di NFT (NFT sniping) e gli attacchi di polvere (dust attacks).
Le conseguenze per gli utenti includono un aumento dei costi di transazione dovuto alle guerre del gas (gas wars), prezzi di esecuzione peggiori (slippage) sulle operazioni, perdita impermanente (impermanent loss) esacerbata per i fornitori di liquidità e liquidazioni attivate ingiustamente.
Le strategie di mitigazione mirano a ridurre l'impatto negativo del MEV. L'invio di transazioni tramite mempool private o relay (come Flashbots Protect o MEV Blocker) le nasconde alla vista pubblica. I design a livello applicativo come gli schemi Commit-Reveal (impegno-rivelazione) nascondono i dettagli della transazione fino a quando l'ordine non è fissato, mentre l'uso di prezzi medi ponderati nel tempo (Time-Weighted Average Prices - TWAP) per gli oracoli può ridurre i rischi di manipolazione.