Vai al contenuto principale

ERC-20 con barriere di sicurezza

erc-20
Principiante
Ori Pomerantz
15 agosto 2022
8 minuti letti minute read

Introduzione

Una delle cose fantastiche su Ethereum è che non esiste un'autorità centrale che possa modificare o annullare le tue transazioni. Uno dei suoi grandi problemi è che non c'è un'autorità centrale con il potere di annullare gli errori o le transazioni illecite degli utenti. In questo articolo imparerai alcuni dei comuni errori che gli utenti commettono con i token ERC-20, nonché come creare dei contratti ERC-20 che aiutano gli utenti a evitarli, o danno poteri a un'autorità centrale (ad esempio, congelare gli account).

Nota che, mentre utilizzeremo il contratto del token ERC-20 di OpenZeppelin(opens in a new tab), questo articolo non lo spiega nel dettaglio. Puoi trovare queste informazioni qui.

Se desideri visualizzare il codice sorgente completo:

  1. Apri l'IDE di Remix(opens in a new tab).
  2. Clicca l'icona di clonazione di GitHub (clone github icon).
  3. Clona la repository di GitHub https://github.com/qbzzt/20220815-erc20-safety-rails.
  4. Apri contratti > erc20-safety-rails.sol.

Creare un contratto ERC-20

Prima di poter aggiungere la funzionalità della barriera di sicurezza, ci occorre un contratto ERC-20. In questo articolo utilizzeremo la procedura guidata dei contratti di OpenZeppelin(opens in a new tab). Aprila in un altro browser e segui queste istruzioni:

  1. Seleziona ERC20.

  2. Inserisci queste impostazioni:

    ParametroValore
    NomeSafetyRailsToken
    SymbolSAFE
    Premint1000
    CaratteristicheNessuno
    Access ControlOwnable
    UpgradabilityNessuno
  3. Scorri in alto e clicca su Apri su Remix (per Remix) o su Scarica per utilizzare un ambiente differente. Supporrò che tu stia utilizzando Remix, altrimenti, effettua le modifiche appropriate.

  4. Ora, abbiamo un contratto ERC-20 pienamente funzionante. Puoi espandere .deps > npm per visualizzare il codice importato.

  5. Compila, distribuisci e gioca con il contratto, per scoprire che funziona come un contratto ERC-20. Se devi apprendere come funziona Remix, utilizza questo tutorial(opens in a new tab).

Errori comuni

Gli errori

Gli utenti, talvolta, inviano dei token all'indirizzo errato. Anche se non possiamo leggere la loro mente per sapere cosa intendevano fare, ci sono due tipi di errore che capitano spesso e sono facili da rilevare:

  1. Inviare i token all'indirizzo del contratto. Ad esempio, il token OP di Optimism(opens in a new tab) è riuscito ad accumulare oltre 120.000(opens in a new tab) token OP in meno di due mesi. Questo rappresenta una significativa quantità di ricchezza che, presumibilmente, è semplicemente stata persa dalle persone.

  2. Inviare i token a un indirizzo vuoto, non corrispondente a un conto posseduto esternamente o a un contratto intelligente. Sebbene non siano disponibili le statistiche su quanto spesso si verifichi, un incidente potrebbe costare fino a 20.000.000 token(opens in a new tab).

Prevenire i trasferimenti

Il contratto ERC-20 di OpenZeppelin include un hook, _beforeTokenTransfer(opens in a new tab), chiamato prima del trasferimento di un token. Per impostazione predefinita, questo hook non fa nulla, ma possiamo allegarci la nostra funzionalità, come controlli che ripristinano la transazione se si verifica un problema.

Per utilizzare l'hook, aggiungi questa funzione dopo il costruttore:

1 function _beforeTokenTransfer(address from, address to, uint256 amount)
2 internal virtual
3 override(ERC20)
4 {
5 super._beforeTokenTransfer(from, to, amount);
6 }
Copia

Alcune parti di questa funzione potrebbero essere nuove, se non hai familiarità con Solidity:

1 internal virtual
Copia

La parola chiave virtual signiica che appena abbiamo ereditato la funzionalità da ERC20 e sovrascritto questa funzione, gli altri contratti possono ereditare da noi e sovrascriverla.

1 override(ERC20)
Copia

Dobbiamo specificare esplicitamente che stiamo sovrascrivendo(opens in a new tab) la definizione del token ERC20 di _beforeTokenTransfer. In generale, le definizioni esplicite sono molto migliori, dal punto di vista della sicurezza, rispetto a quelle implicite: non puoi dimenticare di aver fatto qualcosa se ce l'hai davanti. Questo è anche il motivo per cui dobbiamo specificare il _beforeTokenTransfer di quale superclasse stiamo sovrascrivendo.

1 super._beforeTokenTransfer(from, to, amount);
Copia

Questa riga chiama la funzione _beforeTokenTransfer del contratto o dei contratti da cui abbiamo ereditato o che la contengono. In qusto caso, è solo ERC20, Ownable non contiene questo hook. Sebbene correntemente ERC20._beforeTokenTransfer non faccia nulla, lo chiamiamo nel caso in cui la funzionalità sia aggiunta in futuro (e, quindi, decidiamo di ridistribuire il contratto, poiché i contratti non cambiano dopo la distribuzione).

Codifica dei requisiti

Vogliamo aggiungere questi requisiti alla funzione:

Analizziamo il codice, riga per riga:

1 require(to != address(this), "Can't send tokens to the contract address");
Copia

Questo è il primo requisito, controlla che to this(address) non siano la stessa cosa.

1 bool isToContract;
2 assembly {
3 isToContract := gt(extcodesize(to), 0)
4 }
Copia

È così che controlliamo se un indirizzo è un contratto. Non possiamo ricevere direttamente il risultato da Yul, quindi, invece, definiamo una variabile che detenga il risultato (isToContract in questo caso). Yul funziona così: ogni codice operativo è considerato come una funzione. Quindi, prima chiamiamo EXTCODESIZE(opens in a new tab) per ottenere le dimensioni del contratto, quindi utilizziamo GT(opens in a new tab) per verificare che non sia zero (stiamo avendo a che fare con interi non firmati, quindi, ovviamente, non possono essere negativi). Poi, dobbiamo scrivere il risultato in isToContract.

1 require(to.balance != 0 || isToContract, "Can't send tokens to an empty address");
Copia

E, infine, abbiamo il controllo effettivo per gli indirizzi vuoti.

Accesso amministrativo

Talvolta è utile avere un amministratore che possa annullare gli errori. Per ridurre il potenziale di abusi, questo può essere una multifirma(opens in a new tab), quindi più persone devono approvare un'azione. In questo articolo abbiamo due funzionalità amministrative:

  1. Congelamento e scongelamento degli account. Utile, ad esempio, quando un account potrebbe essere compromesso.

  2. Pulizia della risorsa.

    Talvolta, i truffatori inviano token fraudolenti al contratto del token reale, per ottenere legittimità. Ad esempio, vedi qui(opens in a new tab). Il contratto ERC-20 legittimo è 0x4200....0042(opens in a new tab). La truffa che pretende di essere tale contratto è 0x234....bbe(opens in a new tab).

    Inoltre, è possibile che le persone inviino token ERC-20 legittimi al nostro contratto per errore, un altro motivo per cui vogliamo avere un modo per farli uscire.

OpenZeppelin fornisce due meccanismi per consentire l'accesso amministrativo:

Per semplicità, in questo articolo utilizzeremo Ownable.

Congelare e scongelare i contratti

Congelare e scongelare i contratti richiede diverse modifiche:

Pulizia della risorsa

Per rilasciare i token ERC-20 detenuti da questo contratto, dobbiamo chiamare una funzione sul contratto del token cui aappartengono, transfer(opens in a new tab) o approve(opens in a new tab). Non ha senso, in questo caso, sprecare gas sulle indennità, potremmo anche trasferirle direttamente.

1 function cleanupERC20(
2 address erc20,
3 address dest
4 )
5 public
6 onlyOwner
7 {
8 IERC20 token = IERC20(erc20);
Copia

Questa è la sintassi per creare un oggetto per un contratto quando riceviamo l'indirizzo. Possiamo farlo perché abbiamo la definizione per i token ERC20 come parte del codice sorgente (vedi riga 4) e tale file include la definizione per IERC20(opens in a new tab), l'interfaccia per un contratto ERC-20 di OpenZeppelin.

1 uint balance = token.balanceOf(address(this));
2 token.transfer(dest, balance);
3 }
Copia

Questa è una funzione di pulizia, quindi, presumibilmente, non vogliamo lasciare alcun token. Invece di ottenere manualmente il saldo dall'utente, potremmo automatizzare anche questo processo.

Conclusioni

Questa non è una soluzione perfetta, non esiste una soluzione perfetta al problema "un utente ha commesso un errore". Tuttavia, utilizzando questi tipi di controlli, alcuni errori possono almeno essere prevenuti. L'abilità di congelare gli account, sebbene pericolosa, è utilizzabile per limitare i danni di certi attacchi, negando all'hacker i fondi rubati.

Ultima modifica: @omahs(opens in a new tab), 17 febbraio 2024

Questo tutorial è stato utile?