Analizzatori di Solidity

L’analisi statica del codice è un processo di debug del codice che si effettua esaminandolo senza eseguirlo.

The Solidity Analyzers plugin combines three analysis tools to perform static analysis on Solidity smart contracts. Each tool checks for security vulnerabilities and bad development practices, among other issues. The plugin can be activated from the Remix Plugin Manager.

Activating Solidity Analyzers from the Plugin Manager

Solidity Analyzers can also be loaded by clicking on the Solidity icon in the featured plugins section of Remix’s home tab. This button loads the following plugins: Solidity Compiler, Solidity Unit Testing, and Static Analyzers.

«Analizzatori di Solidity» utilizza questi strumenti:

Nota

Slither can only be used on Remix Desktop.

Come si usa

Un contratto deve essere compilato prima di poter eseguire l’analisi.

Nella parte superiore del pannello, selezionate gli strumenti che desiderate utilizzare.

Solidity Analyzers panel with tool selection checkboxes

Errori & Avvertimenti

By default, Solidity Analyzers will show both errors and warnings. The combined number of errors and warnings is shown in the badge on that tool’s tab.

Error and warning badges on the analysis tool tabs

Se si seleziona Nascondi avvertimenti, gli avvertimenti saranno nascosti e si vedranno solo gli errori.

NOTE: Remix Analysis does not flag errors — it only shows warnings, so if you check Hide warnings, nothing will be shown in the Remix Analysis tab.

Hide warnings checkbox in Solidity Analyzers

Avvertimenti da librerie esterne

By default, warnings from external libraries are not shown. If you check the box Show warnings for external libraries, the tools will also analyze the external libraries for warnings.

Slither

To run Slither with this plugin, you need to open your workspace on Remix Desktop.

Solhint

Il linter Solhint può essere eseguito senza collegare Remix al proprio filesystem.

Remix analysis

Analisi di Remix ha 4 categorie: Sicurezza, Carburante & Economia, ERC e Varie.

Here is the list of modules under each category along with example code which should be avoided or used very carefully during development:

Categoria: Sicurezza

  • Origine della transazione: si usa “tx.origin”

tx.origin è utile solo in casi eccezionali. Se lo si usa per l’autenticazione, di solito lo si vuole sostituire con «msg.sender», perché altrimenti qualsiasi contratto invocato può agire per conto dell’utente.

Esempio:

require(tx.origin == owner);
  • Controlla gli effetti: potenziali bug di rientranza

La potenziale violazione dello schema Controlli-Effetti-Interazione può portare alla vulnerabilità della rientranza.

Esempio:

// sending ether first
msg.sender.transfer(amount);

// updating state afterwards
balances[msg.sender] -= amount;
  • Assemblaggio in linea: utlizzo dell’Assemblaggio in linea

L’uso dell’assemblaggio in linea è consigliato solo in rari casi.

Esempio:

assembly {
            // retrieve the size of the code, this needs assembly
            let size := extcodesize(_addr)
}
  • Block timestamp: Semantics may be unclear

now non significa ora corrente. now è un alias per block.timestamp. block.timestamp può essere influenzato in una certa misura dai validatori, fate attenzione.

Esempio:

// using now for date comparison
if(startDate > now)
    isStarted = true;

// using block.timestamp
uint c = block.timestamp;
  • Low level calls: Semantics may be unclear

L’uso di call, callcode o delegatecall di basso livello dovrebbe essere evitato quando possibile. send non lancia un’eccezione quando non ha successo, assicuratevi di gestire il caso di fallimento di conseguenza. Utilizzate transfer ogni volta che il fallimento del trasferimento di Ether deve far riavvolgere l’intera transazione.

Esempio:

x.call('something');
x.send(1 wei);
  • Blockhash usage: Semantics may be unclear

blockhash è usato per accedere agli ultimi 256 hash dei blocchi. Un validatore calcola l’hash del blocco «sommando» le informazioni del blocco correntemente processato. Sommando le informazioni in modo intelligente, un validatore può cercare di influenzare l’esito di una transazione nel blocco attuale.

Esempio:

bytes32 b = blockhash(100);
  • Autodistruzione: Fai attenzione ai contratti «caller»

selfdestruct può bloccare inaspettatamente le invocazioni ai contratti. Fare particolare attenzione se si prevede che questo contratto sia usato da altri contratti (ad esempio, contratti di libreria, interazioni). L’autodistruzione del contratto invocante può lasciare gli invocatori in uno stato inutilizzabile.

Esempio:

selfdestruct(address(0x123abc..));

Categoria: Carburante & Economia

  • Costi del carburante: Requisiti di carburante delle funzioni troppo elevati

If the gas requirement of a function is higher than the block gas limit, it cannot be executed. Please avoid loops in your functions or actions that modify large areas of storage.

Esempio:

for (uint8 proposal = 0; proposal < proposals.length; proposal++) {
    if (proposals[proposal].voteCount > winningVoteCount) {
        winningVoteCount = proposals[proposal].voteCount;
        winningProposal = proposal;
    }
}
  • «This» nelle invocazioni locali: Invocazione di funzioni locali tramite “this”

Non usare mai this per invocare funzioni nello stesso contratto, perché consuma solo più carburante delle normali invocazioni locali.

Esempio:

contract test {

    function callb() public {
        address x;
        this.b(x);
    }

    function b(address a) public returns (bool) {}
}
  • Cancellazione su una matrice dinamica: Usare require/assert in modo appropriato

L’operazione delete, se applicata a una matrice con dimensioni dinamiche in Solidity, genera codice per cancellare ciascuno degli elementi contenuti. Se la matrice è grande, questa operazione può superare il limite di carburante del blocco e generare un’eccezione OOG. Anche gli oggetti contenuti di dimensioni dinamiche possono produrre gli stessi risultati.

Esempio:

contract arr {
    uint[] users;
    function resetState() public{
        delete users;
    }
}
  • Ciclo for su una matrice dinamica: Le iterazioni dipendono dalla dimensione della matrice dinamica

Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully: Due to the block gas limit, transactions can only consume a certain amount of gas. The number of iterations in a loop can grow beyond the block gas limit which can stall the complete contract at a certain point. Additionally, using unbounded loops can incur a lot of avoidable gas costs. Carefully test how many items at maximum you can pass to such functions to make it successful.

Esempio:

contract forLoopArr {
    uint[] array;

    function shiftArrItem(uint index) public returns(uint[] memory) {
        for (uint i = index; i < array.length; i++) {
            array[i] = array[i+1];
        }
        return array;
    }
}
  • Trasferimento di Ether in un ciclo: Trasferire Ether in un ciclo for/while/do-while

Ether payout should not be done in a loop. Due to the block gas limit, transactions can only consume a certain amount of gas. The number of iterations in a loop can grow beyond the block gas limit which can cause the complete contract to be stalled at a certain point. If required, make sure that the number of iterations is low and you trust each address involved.

Esempio:

contract etherTransferInLoop {
    address payable owner;

    function transferInForLoop(uint index) public  {
        for (uint i = index; i < 100; i++) {
            owner.transfer(i);
        }
    }

    function transferInWhileLoop(uint index) public  {
        uint i = index;
        while (i < 100) {
            owner.transfer(i);
            i++;
        }
    }
}

Categoria: ERC

  • ERC20: “decimals” dovrebbe essere “uint8”

ERC20 contracts” decimals function should have uint8 as the return type.

Esempio:

contract EIP20 {

    uint public decimals = 12;
}

Categoria: Varie

  • Funzioni Constant/View/Pure: Funzioni potenzialmente constant/view/pure

Mette in guardia riguardo a metodi che potenzialmente dovrebbero essere constant/view/pure, ma non lo sono.

Esempio:

function b(address a) public returns (bool) {
        return true;
}
  • Nomi di variabili simili: I nomi delle variabili sono troppo simili

Mette in guardia sull’uso di nomi di variabili simili.

Esempio:

// Variables have very similar names voter and voters.
function giveRightToVote(address voter) public {
    require(voters[voter].weight == 0);
    voters[voter].weight = 1;
}
  • Nessun ritorno: Una funzione con “returns” non restituisce nulla

Mette in guardia sui metodi che definiscono un tipo di ritorno, ma che non restituiscono mai esplicitamente un valore.

Esempio:

function noreturn(string memory _dna) public returns (bool) {
       dna = _dna;
   }
  • Condizioni di guardia: Usare i termini “require” e “assert” in modo appropriato

Usa assert(x) se non vuoi mai e poi mai che x sia falso, in nessuna circostanza (a parte un bug nel tuou codice). Usa require(x) se x può essere falso, ad esempio a causa di un input non valido o di un componente esterno che non funziona.

Esempio:

assert(a.balance == 0);
  • Risultato non utilizzato: Il risultato di un’operazione non utilizzato

Un’operazione binaria produce un valore che non viene utilizzato di seguito. Ciò è spesso causato dalla confusione tra assegnazione (=) e confronto (==).

Esempio:

c == 5;
or
a + b;
  • String Length: Bytes length != String length

I byte e la lunghezza delle stringhe non sono la stessa cosa, poiché si presume che le stringhe siano codificate in UTF-8 (secondo la definizione ABI), quindi un carattere non è necessariamente codificato in un byte di dati.

Esempio:

function length(string memory a) public pure returns(uint) {
    bytes memory x = bytes(a);

    return x.length;
}
  • Eliminazione da una matrice dinamica: “delete” su una matrice lascia uno spazio vuoto

L’uso di delete su una matrice lascia uno spazio vuoto. La lunghezza della matrice rimane invariata. Se si vuole rimuovere la posizione vuota, è necessario spostare manualmente gli elementi e aggiornare la proprietà «lunghezza».

Esempio:

contract arr {
    uint[] array = [1,2,3];

    function removeAtIndex() public returns (uint[] memory) {
        delete array[1];
        return array;
    }
}
  • Dati Troncati: La Divisione su valori int/uint tronca il risultato

La divisione di valori interi produce nuovamente un valore intero. Ciò significa che, ad esempio, 10 / 100 = 0 invece di 0,1, poiché il risultato è di nuovo un intero. Questo non vale per la divisione di valori (solo) letterali, poiché questi producono costanti razionali.

Esempio:

function contribute() payable public {
    uint fee = msg.value * uint256(feePercentage / 100);
    fee = msg.value * (p2 / 100);
}

Analizzatore di Remix

remix-analyzer è la libreria che sta alla base dello strumento di Analisi di Remix.

remix-analyzer is an NPM package. It can be used as a library in a solution supporting Node.js. Find more information about this type of usage in the remix-analyzer repository.