Solo a scopo educativo; non costituisce consulenza finanziaria. Gli investimenti possono comportare perdite.
Risposta diretta
Una collisione dello storage del proxy si verifica quando il codice di implementazione legge o scrive uno slot del proxy con un significato diverso da quello previsto dal layout che ha creato lo stato esistente. Un aggiornamento può così far interpretare un saldo come indirizzo, azzerare il proprietario, corrompere lo slot di base di una mapping o sovrascrivere i dati di controllo dell’aggiornamento.
Con delegatecall, il bytecode dell’implementazione viene eseguito nel contesto del proxy: storage, saldo e address(this) appartengono al proxy. I nomi delle variabili non sono memorizzati on-chain; l’EVM segue solo lo slot e l’offset in byte calcolati dal nuovo codice.
Un aggiornamento deve quindi preservare il layout distribuito, non soltanto compilare correttamente o esporre le stesse funzioni. Prima di autorizzarlo, confrontare i layout generati dal compilatore con la versione esatta distribuita.
Come funziona
Solidity normalmente dispone le variabili di stato dallo slot 0, in ordine di dichiarazione dopo la linearizzazione C3 dell’ereditarietà. Valori inferiori a 32 byte possono condividere uno slot; struct e array seguono altre regole, mentre mapping e array dinamici derivano la posizione dei dati da uno slot di base. Se questo si sposta, cambiano anche le posizioni dei dati derivati.
Vanno verificati quattro distinti confini di collisione:
- Proxy e implementazione: i campi propri del proxy, come implementazione e amministratore, non devono occupare slot usati dallo stato applicativo. ERC-1967 assegna slot standard, evitati dal compilatore, ai dati di implementazione, beacon e amministratore.
- Vecchia e nuova implementazione: le variabili esistenti devono mantenere slot, offset e tipi compatibili. Aggiungere una variabile in coda può essere sicuro; inserirla, riordinarla, eliminarla o cambiarne il tipo può reinterpretare parole esistenti.
- Ereditarietà: aggiungere stato a un contratto base o cambiare l’ordine di ereditarietà può spostare lo storage dei discendenti anche se il loro sorgente non cambia.
- Storage riservato o con namespace: uno storage gap usato correttamente riserva spazio a un contratto base e i namespace in stile ERC-7201 isolano i layout. Nessuna delle due tecniche consente modifiche arbitrarie in un layout esistente.
Esempio
Si supponga che la versione 1 abbia questo layout:
uint256 totalAssets; // slot 0
address owner; // slot 1La versione 2 inserisce erroneamente una variabile all’inizio:
bool paused; // slot 0, offset 0
uint256 totalAssets; // slot 1
address owner; // slot 2Dopo l’aggiornamento, paused legge il byte basso del vecchio totalAssets, il nuovo totalAssets legge come intero la parola del vecchio owner e owner legge ciò che era già nello slot 2, spesso zero. Le parole originali restano nello storage, ma il nuovo codice assegna loro altri significati. Una transazione può riuscire applicando autorizzazioni o contabilità a interpretazioni corrotte.
Rischi e controlli dell’aggiornamento
- Generare il layout di entrambe le implementazioni e confrontare slot, offset, tipo ed ereditarietà con il contratto di riferimento realmente distribuito.
- In un layout lineare convenzionale, aggiungere nuove variabili solo in coda. Non riordinare campi, cambiarne i tipi, eliminarli e riutilizzarli né modificare contratti base senza dimostrare la compatibilità.
- Quando si usa uno storage gap, ridurlo del numero esatto di slot riservati occupati. Con i namespace, mantenere identificatori univoci e verificare le modifiche in ogni namespace esistente.
- Non considerare ERC-1967 una protezione completa. Separa i metadati del proxy dagli slot applicativi assegnati dal compilatore, ma non rende compatibili due layout di implementazione.
- Provare l’aggiornamento e ogni reinizializzatore su un fork o snapshot dello stato. Controllare prima e dopo proprietari, ruoli, saldi, allowance, voci delle mapping, pausa, slot di implementazione e controlli di rollback o emergenza.
Se un aggiornamento può aver già provocato una collisione, sospendere altri aggiornamenti e chiamate che cambiano stato quando la governance lo consente. Conservare il numero del blocco precedente e il bytecode, confrontare lo storage grezzo degli slot interessati e affidare a tecnici qualificati una migrazione con revisione indipendente. Ripetere aggiornamenti senza una mappa dello storage verificata può distruggere altro stato recuperabile.
Errori comuni
- “I nomi delle variabili non sono cambiati, quindi il layout è sicuro.” I nomi non determinano le posizioni. Contano tipi, ordine, packing, ereditarietà e regole dei namespace.
- “Eliminare una variabile libera lo slot.” Lo storage del proxy persiste. Riutilizzarlo assegna un nuovo significato alla vecchia parola, salvo che una migrazione verificata la cancelli o trasformi.
- “Una transazione di prova riuscita dimostra la compatibilità.” Può toccare pochi slot. La validazione e i test delle differenze di stato devono coprire campi privilegiati, valori packed, mapping, array e storage ereditato.
Argomenti correlati
- Rischio dello storage con delegatecall
- Acquisizione dell’inizializzatore
- Contratto proxy
- Monitoraggio degli aggiornamenti proxy
- Contratto aggiornabile
Fonti
- Introduzione agli smart contract - Solidity Documentation (consultato: 2026-08-21)
- Layout delle variabili di stato nello storage - Solidity Documentation (consultato: 2026-08-21)
- ERC-1967: slot di storage del proxy - Ethereum Improvement Proposals (consultato: 2026-08-21)
- Scrivere contratti aggiornabili - OpenZeppelin Documentation (consultato: 2026-08-21)