
Introduzione
Gli smart contract sono programmati per essere permanenti e non modificabili dopo che sono stati implementati su una rete blockchain. Questa immutabilità è una fonte di sicurezza e fiducia, ma presenta anche delle sfide quando gli sviluppatori devono correggere bug o aggiungere nuove funzionalità.
Quando servono modifiche, gli sviluppatori devono pubblicare un contratto completamente nuovo con un indirizzo diverso. Anche se questa permanenza è ottima per la sicurezza, può essere un limite quando qualcosa va storto o serve migliorare qualcosa.
La storia ci ha insegnato che le falle negli smart contract possono portare a perdite finanziarie enormi. Un esempio è un grosso incidente in cui sono stati persi milioni di dollari a causa di un difetto che è stato sfruttato.
Questi casi mostrano quanto sia importante poter risolvere i problemi di sicurezza e i bug dopo l'implementazione. Rendere i contratti aggiornabili permette di avere un modo per sistemare i problemi senza perdere lo stato attuale e l'esperienza dell'utente, che è un aspetto fondamentale dell'aggiornabilità degli smart contract.
Questa capacità può essere importante per evitare perdite significative e mantenere l'integrità del sistema.
Ci sono un sacco di modi per implementare contratti intelligenti aggiornabili, come i modelli proxy dei contratti intelligenti e le tecniche di separazione dei dati. Questo articolo parla proprio di come implementare l'aggiornabilità usando i modelli proxy, che sono un modo molto usato per risolvere questo problema nello sviluppo della blockchain.
Capire il modello proxy
Il modello proxy è un modello di progettazione strutturale in cui un contratto implementa un'interfaccia per un altro contratto. Questa architettura ha due parti principali: il contratto proxy e il contratto di implementazione.
Invece di comunicare direttamente con il contratto di implementazione, l'utente comunicherà con il contratto proxy, che poi passerà le richieste come deve.
In questa configurazione, sia il contratto proxy che il contratto di implementazione non vengono modificati dopo essere stati implementati. Tuttavia, l'aggiornabilità viene ottenuta consentendo al proxy di fare riferimento a diversi contratti di implementazione nel corso del tempo.
Questo vuol dire che gli utenti possono continuare a usare lo stesso indirizzo e la stessa interfaccia mentre la funzionalità di base viene aggiornata. Dal punto di vista dell'utente, l'applicazione continua a funzionare alla grande anche se la logica di backend cambia.
Il contratto proxy si occupa delle interazioni degli utenti e di salvare tutti i dati. Tiene l'indirizzo del contratto di implementazione in un posto speciale.
Quando gli utenti chiamano le funzioni sul contratto proxy, viene chiamata una funzione di fallback. La funzione seguente utilizza un meccanismo delegatecall ethereum per richiamare il codice dal contratto di implementazione, ma qualsiasi modifica di stato viene salvata nella memoria del contratto proxy.
Principali implementazioni dei modelli proxy
Modello proxy trasparente
Il modello Transparent Proxy mette la funzionalità di aggiornamento nel contratto proxy stesso. Il proxy ha un metodo upgradeTo che serve per aggiornare l'indirizzo che punta al contratto di implementazione.
Questo può creare un problema: se sia il contratto proxy che quello di implementazione hanno un metodo upgradeTo, non è chiaro quale dei due dovrebbe essere chiamato quando un utente usa questa funzione.
Per risolvere questa ambiguità, è stata trovata una soluzione in cui la scelta di chi delegare dipende da chi sta chiamando il contratto.
Se chi chiama è l'amministratore del proxy, le chiamate vengono gestite dal contratto proxy stesso. Altrimenti, le chiamate vengono delegate al contratto di implementazione. Questo approccio assicura che le funzioni vengano eseguite in modo chiaro e che non ci sia confusione tra le operazioni amministrative e quelle degli utenti normali.
Standard proxy aggiornabile universale
Lo standard Universal Upgradeable Proxy ha un approccio diverso, mettendo la funzionalità di aggiornamento nel contratto di implementazione, non nel proxy. Il proxy manda tutte le chiamate al contratto di implementazione, che ha il metodo upgradeTo per puntare alle versioni più recenti.
Questo modello dà agli sviluppatori più controllo sul percorso di aggiornamento. Se uno sviluppatore decide di non includere più il metodo upgradeTo in una versione futura, il contratto ora è definitivamente immutabile e non può essere ulteriormente aggiornato.
Questo può essere utile quando un contratto è stato ben testato e il team di sviluppo vuole bloccarlo nella sua versione finale.
Dato che la logica di aggiornamento è nel contratto di implementazione, non c'è bisogno che la funzione di fallback del proxy controlli se chi chiama è un amministratore prima di delegare le chiamate.
Questo rende il modello più efficiente rispetto al modello Transparent Proxy. Anche il potenziale conflitto di denominazione viene eliminato, poiché il metodo upgradeTo esiste solo nel contratto di implementazione.
Modello Beacon Proxy
Il modello Beacon Proxy introduce un'architettura a tre componenti: il contratto proxy, un contratto beacon e il contratto di implementazione. Invece di salvare l'indirizzo dell'implementazione, il proxy salva l'indirizzo del contratto beacon.
Il contratto beacon contiene l'indirizzo del contratto di implementazione.
Quando un utente chiama il proxy, prima prende l'indirizzo del contratto beacon, poi chiama il beacon per avere l'indirizzo del contratto di implementazione. Alla fine, lascia la chiamata al contratto di implementazione.
Questo ulteriore livello di indirezione ha uno scopo ben preciso.
Questo modello è particolarmente utile quando diversi contratti proxy devono usare la stessa implementazione. Nei modelli più semplici, per aggiornare l'implementazione, bisognerebbe aggiornare l'indirizzo in ogni contratto proxy singolarmente. Con il modello beacon, basta aggiornare il contratto beacon e tutti i proxy associati ad esso puntano automaticamente alla nuova implementazione.
Principali implementazioni dei modelli proxy
Modello Diamond Proxy
Il Diamond Proxy Pattern risolve il problema delle dimensioni dei contratti intelligenti, che di solito non possono superare i 24 kilobyte. Questo modello divide le funzionalità in contratti più piccoli chiamati "facets".
Il contratto proxy tiene traccia della mappatura tra i selettori di funzione e l'indirizzo dei facet che contengono tali funzioni.
Quando una funzione viene chiamata sul proxy, usa il selettore di funzioni per cercare il facet che contiene la funzione e passa la chiamata di funzione al facet giusto.
Gli aggiornamenti si fanno cambiando gli indirizzi dei facet nel proxy. Questo modo di fare permette di avere un sacco più di funzionalità distribuendo tutto su più contratti, con ogni facet che rimane sotto il limite di dimensione.
Confronto tra approcci basati su modelli proxy
Ogni modello di proxy ha caratteristiche diverse che lo rendono adatto a diversi casi d'uso. Tutti e quattro i modelli richiedono un contratto proxy e utilizzano la delega per inoltrare le chiamate ai contratti di implementazione.
Il modello Transparent Proxy individua le funzioni di aggiornamento nel contratto proxy e UUPS individua le funzioni di aggiornamento nel contratto di implementazione. Il modello Beacon inserisce la funzionalità di aggiornamento nel proprio contratto beacon e il modello Diamond in genere inserisce la funzionalità di aggiornamento nei contratti di implementazione, ma non è specificato in modo rigoroso.
Per quanto riguarda l'immutabilità, i modelli UUPS e Diamond possono rendere i contratti permanentemente immutabili rimuovendo la funzione di aggiornamento dalle versioni future. I modelli Transparent e Beacon non offrono questa flessibilità.
Quando si ha a che fare con più proxy, il modello Beacon offre un netto vantaggio. Con i modelli Transparent, UUPS e Diamond, ogni proxy deve essere modificato singolarmente quando si implementa una nuova implementazione.
Con il modello Beacon, devi solo aggiornare il contratto beacon e tutti i proxy useranno automaticamente la nuova implementazione.
L'efficienza del gas varia a seconda del modello. Il modello trasparente richiede di controllare se chi chiama è un amministratore prima di ogni delega, quindi è più costoso.
UUPS è più efficiente perché non ha bisogno di questo controllo. Tutti i modelli, tranne Diamond, hanno costi di gas extra per le operazioni di ricerca aggiuntive.
Tutti i modelli, tranne Diamond, hanno un limite di 24 kilobyte per contratto. Il modello Diamond permette che ogni aspetto sia grande fino a 24 kilobyte, il che rende possibile avere funzionalità totali molto più grandi su più aspetti.
Padroneggia la sicurezza degli smart contract
Impara tecniche avanzate per creare contratti sicuri e aggiornabili con i nostri corsi tenuti da esperti.
Cose importanti da considerare e rischi
Problemi di collisione di archiviazione
Le collisioni di archiviazione sono un rischio serio quando si implementano modelli proxy. Le variabili dei contratti sono memorizzate in slot di archiviazione specifici e se le variabili del contratto proxy e quelle del contratto di implementazione sono memorizzate negli stessi slot di archiviazione, si interferiranno a vicenda.
Inoltre, se l'ordine delle variabili nel contratto di implementazione cambia da una versione all'altra, gli slot di archiviazione potrebbero essere riassegnati, causando il danneggiamento dei dati.
Contratti di implementazione non inizializzati
I contratti di implementazione devono essere inizializzati esattamente una volta tramite una funzione di inizializzazione, che è un concetto simile a un costruttore nei contratti tradizionali.
Gli sviluppatori a volte si dimenticano di inizializzare l'implementazione o non mettono protezioni per evitare che la funzione di inizializzazione venga chiamata più di una volta.
Quando succede questo, gli hacker possono usare la funzione di inizializzazione e magari prendere il controllo del contratto o cambiarne lo stato.
Incidenti di sicurezza nel mondo reale
Un programma di bug bounty ha trovato una vulnerabilità importante nei contratti proxy vault dove i contratti di implementazione non erano stati inizializzati bene. Questo difetto avrebbe potuto essere usato da qualcuno per distruggere il contratto di implementazione, rendendo inutili i contratti proxy collegati. In un altro caso, in un certo periodo di luglio, gli smart contract sono stati hackerati a causa di una vulnerabilità nel codice di inizializzazione, dove la funzione di inizializzazione poteva essere chiamata più volte.
Approcci alternativi all'aggiornabilità
Modello di separazione dei dati
Un'alternativa ai modelli proxy è l'approccio di separazione dei dati. Questo approccio si basa sull'uso di contratti separati per l'archiviazione e la logica. Il contratto logico comunica con il contratto di archiviazione per leggere o aggiornare i dati.
Anche se puoi sostituire il contratto logico con nuove versioni, il contratto di archiviazione è fisso e non si può cambiare. Questo offre un modello diverso di aggiornabilità che può essere utile in alcune situazioni.
Livelli di verifica
Nessuno dei modelli proxy comunemente usati ha dei meccanismi per controllare che un nuovo contratto di implementazione sia valido prima di fare l'aggiornamento.
È fondamentale che le nuove versioni includano tutta la logica aziendale necessaria, le funzioni di fallback e altri componenti essenziali.
Questo si può fare aggiungendo un livello di verifica che va insieme al livello proxy, al livello della logica di business e al livello di archiviazione. Questo livello extra fa sì che gli aggiornamenti rispettino alcuni criteri prima di poter essere eseguiti.
Migliori pratiche per i contratti aggiornabili
Gli sviluppatori che stanno progettando contratti aggiornabili dovrebbero seguire alcune linee guida chiave per garantire sicurezza e affidabilità:
- •Invece, usa implementazioni ben collaudate da librerie ben testate che sono state attentamente esaminate e controllate
- •Assicurati sempre di inizializzare correttamente i contratti di implementazione e di verificare che le funzioni di inizializzazione possano essere chiamate una sola volta
- •Non inizializzare mai le variabili di stato alla loro dichiarazione o in un costruttore, usa la funzione di inizializzazione per tutte le impostazioni di stato
- •Non cambiare l'ordine o i tipi delle variabili di stato quando crei nuove versioni dei contratti di implementazione
- •Se servono nuove variabili, aggiungile dopo quelle già esistenti
- •Per le implementazioni del modello Diamond, usa metodi di archiviazione speciali ottimizzati per quel modello
- •Se puoi, usa il metodo UUPS invece del Transparent Proxy Pattern, perché richiede meno gas per le operazioni di routine
- •Assicurati che l'account amministratore proxy sia altamente sicuro perché controlla il processo di aggiornamento ed è un punto critico per la sicurezza
- •Infine, fai controllare tutti i contratti da professionisti esperti in sicurezza degli smart contract prima di metterli in funzione.
Considerazioni finali
I modelli proxy offrono un modo efficace per aggiornare gli smart contract, mantenendo lo stesso indirizzo e stato. Il contratto proxy usa delegateCall per passare l'esecuzione ai contratti di implementazione, così che la logica di base possa essere cambiata senza modificare l'interfaccia utente.
Comunque, se i contratti aggiornabili non sono messi in atto nel modo giusto, possono creare grossi problemi di sicurezza.
Gli sviluppatori devono valutare attentamente i compromessi tra i diversi tipi di modelli proxy, seguire le migliori pratiche consolidate e assicurarsi che vengano condotti adeguati controlli di sicurezza per creare contratti intelligenti affidabili, sicuri e aggiornabili.
Confronto tra modelli proxy
Quando si confrontano i diversi modelli di proxy da usare, ci sono un po' di cose da considerare:
Confronto tra modelli proxy
| Caratteristica | Trasparente | UUPS | Beacon | Diamante |
|---|---|---|---|---|
| Aggiorna posizione | Contratto di procura | Contratto di implementazione | Contratto Beacon | Contratti di implementazione |
| Immutabilità permanente | No | Sì | No | Sì |
| Proxy multipli | Aggiornamenti individuali | Aggiornamenti individuali | Aggiornamento singolo beacon | Aggiornamenti individuali |
| Efficienza del gas | Costo più alto (controllo amministrativo) | Costo inferiore | Medio (ricerca extra) | Medio (ricerca per faccetta) |
| Limite di dimensione del contratto | 24 KB | 24 KB | 24 KB | 24 KB per Facet |
| Complessità dell'implementazione | Medium | Medium | Medium | Medium |
Cose da considerare nella scelta dei modelli
Cose da tenere a mente quando scegli il modello:
- •Tutti i modelli hanno bisogno di un contratto proxy e usano la delega per passare le chiamate
- •Il modello Transparent Proxy viene usato per salvare le funzioni di aggiornamento nel contratto proxy stesso
- •UUPS inserisce le funzioni di aggiornamento nel contratto di implementazione
- •Il modello Beacon usa un contratto beacon separato per gli aggiornamenti
- •Il modello Diamond di solito mette le funzioni di aggiornamento nei contratti di implementazione, ma le specifiche non lo richiedono
- •Solo i modelli UUPS e Diamond possono rendere i contratti sempre immutabili togliendo la funzione di aggiornamento dalle versioni future
- •Il modello Beacon è perfetto se hai più proxy da aggiornare contemporaneamente, perché basta aggiornare il beacon e non ogni singolo proxy
- •La dimensione massima del contratto è di 24 kilobyte per i modelli Transparent, UUPS e Beacon
- •Il modello Diamond supporta ogni aspetto fino a un massimo di 24 kilobyte, quindi è possibile supportare funzionalità totali molto più ampie
- •Tutti i modelli hanno una complessità di implementazione media ed esistono librerie ben consolidate per i modelli Transparent, UUPS e Beacon


