Solidity Analyzers

Static code analysis is a process of debugging code by examining it without executing it.

The Solidity Analyzers plugin gangs three analysis tools together to perform static analysis on Solidity smart contracts. Each tool checks for security vulnerabilities and bad development practices, among other issues. It can be activated from Remix 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.

Solidity Analyzers uses these tools:

NOTE: Slither can only be used when Remix is connected to the local computer’s filesystem with Remixd.

Comment l’utiliser ?

A contract must be compiled before analysis can be run.

At the top of the panel, check the tools that you want to use.

Errors & Warnings

By default, Solidity Analyzers will show both errors and warnings. The combined number of errors and warnings are shown in the badge in that tools tab.

If you check Hide warnings, warnings will be hidden and you’ll exclusively see the errors.

NOTE: Remix Analysis does not flag error - it only shows warnings so if you check Hide warnings, nothing will show in the Remix Analysis tab.

Warnings from external libraries

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

Slither

To run Slither with this plugin, you need to connect Remix IDE to your filesystem with Remixd. Once Remixd is running, Slither is automatically loaded.

Solhint

The Solhint linter can be run without connecting Remix to your filesystem.

Remix Analysis

Remix Analysis has 4 categories: Security, Gas & Economy, ERC & Miscellaneous.

Voici la liste des modules de chaque catégorie ainsi que l’exemple de code qui doit être évité ou utilisé avec précaution lors du développement :

Catégorie : Sécurité

  • Origine de la transaction : “tx.origin” est utilisé

tx.origin n’est utile que dans des cas très exceptionnels. Si vous l’utilisez pour l’authentification, vous voulez généralement le remplacer par « msg.sender », parce que sinon n’importe quel contrat que vous appelez peut agir en votre nom.

Exemple:________________.

require(tx.origin == owner);
  • Vérifiez les effets : Bogues potentiels de réentrance

La violation potentielle du modèle Checks-Effects-Interaction peut entraîner une vulnérabilité de réentrance.

Exemple:________________.

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

// updating state afterwards
balances[msg.sender] -= amount;
  • Assemblage en ligne : Assemblage en ligne utilisé

L’utilisation de l’assemblage en ligne n’est conseillée que dans de rares cas.

Exemple:________________.

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

now ne signifie pas l’heure actuelle. now est un alias de block.timestamp. block.timestamp peut être influencé par les mineurs dans une certaine mesure, soyez prudent.

Exemple:________________.

// 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’utilisation de call, callcode ou delegatecall de bas niveau doit être évitée autant que possible. send ne lève pas d’exception en cas d’échec, assurez-vous de traiter le cas d’échec en conséquence. Utilisez transfer lorsque l’échec du transfert d’éther doit annuler toute la transaction.

Exemple:________________.

x.call('something');
x.send(1 wei);
  • Utilisation de Blockhash : La sémantique n’est peut-être pas claire

blockhash est utilisé pour accéder aux 256 derniers hashs de blocs. Un mineur calcule le hachage du bloc en « résumant » les informations contenues dans le bloc en cours de minage. En résumant les informations de manière intelligente, un mineur peut essayer d’influencer le résultat d’une transaction dans le bloc en cours.

Exemple:________________.

bytes32 b = blockhash(100);
  • L’autodestruction : Méfiez-vous des contrats d’appel

selfdestruct peut bloquer les contrats appelants de manière inattendue. Soyez particulièrement prudent si ce contrat est prévu pour être utilisé par d’autres contrats (par exemple, des contrats de bibliothèque, des interactions). L’autodestruction du contrat de l’appelant peut laisser les appelants dans un état inopérant.

Exemple:________________.

selfdestruct(address(0x123abc..));

Catégorie : Gaz et économie

  • Coûts du gaz : Besoins en gaz trop élevés pour les fonctions

Si le besoin en gaz d’une fonction est supérieur à la limite de gaz du bloc, elle ne peut pas être exécutée. Veuillez éviter les boucles dans vos fonctions ou les actions qui modifient de grandes zones de stockage.

Exemple:________________.

for (uint8 proposal = 0; proposal < proposals.length; proposal++) {
    if (proposals[proposal].voteCount > winningVoteCount) {
        winningVoteCount = proposals[proposal].voteCount;
        winningProposal = proposal;
    }
}
  • Ceci sur les appels locaux : Invocation de fonctions locales via “this”.

N’utilisez jamais this pour appeler des fonctions dans le même contrat, cela ne fait que consommer plus de gaz que les appels locaux normaux.

Exemple:________________.

contract test {
    
    function callb() public {
        address x;
        this.b(x);
    }
    
    function b(address a) public returns (bool) {}
}
  • Suppression d’un tableau dynamique : Utilisez require/assert de manière appropriée

L’opération delete lorsqu’elle est appliquée à un tableau de taille dynamique dans Solidity génère du code pour supprimer chacun des éléments contenus. Si le tableau est grand, cette opération peut dépasser la limite de gaz du bloc et soulever une exception OOG. Des objets imbriqués de taille dynamique peuvent également produire les mêmes résultats.

Exemple:________________.

contract arr {
    uint[] users;
    function resetState() public{
        delete users;
    }
}
  • Boucle de forçage sur un tableau dynamique : Les itérations dépendent de la taille du tableau dynamique

Les boucles qui n’ont pas un nombre fixe d’itérations, par exemple les boucles qui dépendent des valeurs de stockage, doivent être utilisées avec précaution : En raison de la limite de gaz par bloc, les transactions ne peuvent consommer qu’une certaine quantité de gaz. Le nombre d’itérations dans une boucle peut dépasser la limite de gaz par bloc, ce qui peut bloquer le contrat complet à un certain moment. En outre, l’utilisation de boucles non bornées peut entraîner de nombreux coûts de gaz qui pourraient être évités. Testez soigneusement le nombre maximum d’éléments que vous pouvez transmettre à ces fonctions pour qu’elles soient efficaces.

Exemple:________________.

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;
    }
}
  • Transfert d’Ether dans une boucle : Transfert d’Ether dans une boucle for/while/do-while.

Le paiement des éthers ne doit pas être effectué en boucle. En raison de la limite de gaz par bloc, les transactions ne peuvent consommer qu’une certaine quantité de gaz. Le nombre d’itérations dans une boucle peut dépasser la limite de gaz par bloc, ce qui peut entraîner le blocage du contrat complet à un certain moment. Le cas échéant, assurez-vous que le nombre d’itérations est faible et que vous faites confiance à chaque adresse concernée.

Exemple:________________.

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++;
        }
    }
}

Catégorie : ERC

  • ERC20 : « decimals » doit être remplacé par « uint8 ».

Contrats ERC20 La fonction decimals devrait avoir uint8 comme type de retour.

Exemple:________________.

contract EIP20 {

    uint public decimals = 12;
}

Catégorie : Divers

  • Fonctions constantes/vues/pures : Fonctions potentiellement constantes/vues/pures

Il met en garde contre les méthodes qui devraient potentiellement être constantes/visibles/pures mais qui ne le sont pas.

Exemple:________________.

function b(address a) public returns (bool) {
        return true;
}
  • Noms de variables similaires : Les noms de variables sont trop similaires

Il met en garde contre l’utilisation de noms de variables similaires.

Exemple:________________.

// Variables have very similar names voter and voters.
function giveRightToVote(address voter) public {
    require(voters[voter].weight == 0);
    voters[voter].weight = 1;
}
  • Pas de retour : La fonction avec des « retours » ne renvoie pas d’information

Il met en garde contre les méthodes qui définissent un type de retour mais ne renvoient jamais explicitement une valeur.

Exemple:________________.

function noreturn(string memory _dna) public returns (bool) {
       dna = _dna;
   }
  • Respectez les conditions : Utilisez les termes « exiger » et « affirmer » de manière appropriée.

Utilisez assert(x) si vous ne voulez jamais que x soit faux, en aucune circonstance (à part un bug dans votre code). Utilisez require(x) si x peut être faux, à cause par exemple d’une entrée invalide ou d’un composant externe défaillant.

Exemple:________________.

assert(a.balance == 0);
  • Résultat non utilisé : Le résultat d’une opération non utilisée

Une opération binaire produit une valeur qui n’est pas utilisée dans la suite. Cela est souvent dû à une confusion entre l’affectation (=) et la comparaison (==).

Exemple:________________.

c == 5;
or
a + b;
  • Longueur de la chaîne : Longueur des octets != Longueur de la chaîne

Les octets et la longueur de la chaîne ne sont pas les mêmes puisque les chaînes sont supposées être encodées en UTF-8 (selon la définition de l’ABI) et qu’un caractère n’est donc pas nécessairement encodé dans un octet de données.

Exemple:________________.

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

    return x.length;
}
  • Suppression d’un tableau dynamique : “delete” sur un tableau laisse un vide

L’utilisation de delete sur un tableau laisse un vide. La longueur du tableau reste la même. Si vous souhaitez supprimer la position vide, vous devez décaler les éléments manuellement et mettre à jour la propriété length.

Exemple:________________.

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

    function removeAtIndex() public returns (uint[] memory) {
        delete array[1];
        return array;
    }
}
  • Données tronquées : La division sur des valeurs int/uint tronque le résultat

La division de valeurs entières donne à nouveau une valeur entière. Cela signifie que, par exemple, 10 / 100 = 0 au lieu de 0,1 puisque le résultat est à nouveau un nombre entier. Ce n’est pas le cas pour la division de valeurs littérales (uniquement), qui donnent des constantes rationnelles.

Exemple:________________.

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

Analyseur de remix

remix-analyzer is the library which works underneath the Remix Analysis tool.

remix-analyzer est un [paquet NPM] (https://www.npmjs.com/package/@remix-project/remix-analyzer). Il peut être utilisé comme bibliothèque dans une solution supportant node.js. Vous trouverez plus d’informations sur ce type d’utilisation dans le dépôt remix-analyzer