Analizadores de solidez

El análisis estático del código es un proceso de depuración del código examinándolo sin ejecutarlo.

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.

Solidity Analyzers utiliza estas herramientas:

Nota

Slither can only be used on Remix Desktop.

Cómo utilizarlo

Un contrato debe compilarse antes de poder ejecutar el análisis.

En la parte superior del panel, marque las herramientas que desee utilizar.

Solidity Analyzers panel with tool selection checkboxes

Errores y advertencias

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

Si marca Ocultar advertencias, las advertencias se ocultarán y verá exclusivamente los errores.

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

Avisos de bibliotecas externas

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

El linter Solhint puede ejecutarse sin conectar Remix a su sistema de archivos.

Remix analysis

Remix Analysis tiene 4 categorías: Seguridad, Gas y Economía, ERC y Varios.

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

Categoría: Seguridad

  • Origen de la transacción: se utiliza “tx.origin”

tx.origin sólo es útil en casos muy excepcionales. Si lo utiliza para la autenticación, normalmente querrá sustituirlo por msg.sender, porque de lo contrario cualquier contrato al que llame podrá actuar en su nombre.

Ejemplo:

require(tx.origin == owner);
  • Comprobar efectos: Posibles fallos de reentrada

La violación potencial del patrón Comprobaciones-Efectos-Interacción puede conducir a una vulnerabilidad de reentrada.

Ejemplo:

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

// updating state afterwards
balances[msg.sender] -= amount;
  • Montaje en línea: Montaje en línea utilizado

Se aconseja utilizar el montaje en línea sólo en casos excepcionales.

Ejemplo:

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

now no significa hora actual. now es un alias de block.timestamp. block.timestamp puede estar influenciado por los mineros hasta cierto punto, tenga cuidado.

Ejemplo:

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

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

El uso de call, callcode o delegatecall de bajo nivel debe evitarse siempre que sea posible. send no lanza una excepción cuando no tiene éxito, asegúrese de tratar el caso de fallo como corresponda. Utilice transfer siempre que el fallo de la transferencia de éter deba hacer retroceder toda la transacción.

Ejemplo:

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

blockhash se utiliza para acceder a los últimos 256 hashes de bloque. Un minero calcula el hash del bloque «sumando» la información del bloque actual minado. Al sumar la información de forma inteligente, un minero puede intentar influir en el resultado de una transacción en el bloque actual.

Ejemplo:

bytes32 b = blockhash(100);
  • Autodestrucción: Cuidado con los contratos de llamadas

selfdestruct puede bloquear contratos de llamada inesperadamente. Tenga especial cuidado si está previsto que este contrato sea utilizado por otros contratos (por ejemplo, contratos de biblioteca, interacciones). La autodestrucción del contrato llamante puede dejar a los contratos llamantes en un estado inoperativo.

Ejemplo:

selfdestruct(address(0x123abc..));

Categoría: Gas y economía

  • Costes de gas: Demasiado alto requerimiento de gas de las funciones

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.

Ejemplo:

for (uint8 proposal = 0; proposal < proposals.length; proposal++) {
    if (proposals[proposal].voteCount > winningVoteCount) {
        winningVoteCount = proposals[proposal].voteCount;
        winningProposal = proposal;
    }
}
  • This en llamadas locales: Invocación de funciones locales a través de “this”

Nunca utilice this para llamar a funciones en el mismo contrato, sólo consume más gas que las llamadas locales normales.

Ejemplo:

contract test {

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

    function b(address a) public returns (bool) {}
}
  • Borrar en array dinámico: Utilice require/assert adecuadamente

La operación delete cuando se aplica a un array de tamaño dinámico en Solidity genera código para borrar cada uno de los elementos que contiene. Si el array es grande, esta operación puede sobrepasar el límite de gas del bloque y lanzar una excepción OOG. También los objetos anidados de tamaño dinámico pueden producir los mismos resultados.

Ejemplo:

contract arr {
    uint[] users;
    function resetState() public{
        delete users;
    }
}
  • Bucle for sobre matriz dinámica: Las iteraciones dependen del tamaño del array dinámico

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.

Ejemplo:

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;
    }
}
  • Transferencia de éter en un bucle: Transferencia de éter en un bucle 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.

Ejemplo:

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

Categoría: ERC

  • ERC20: “decimales” debería ser “uint8”

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

Ejemplo:

contract EIP20 {

    uint public decimals = 12;
}

Categoría: Varios

  • Funciones constantes/vistas/puras: Funciones potencialmente constantes/vistas/puras

Advierte de los métodos que potencialmente deberían ser constantes/vistos/puros pero no lo son.

Ejemplo:

function b(address a) public returns (bool) {
        return true;
}
  • Nombres de variables similares: Los nombres de las variables son demasiado similares

Advierte sobre el uso de nombres de variables similares.

Ejemplo:

// Variables have very similar names voter and voters.
function giveRightToVote(address voter) public {
    require(voters[voter].weight == 0);
    voters[voter].weight = 1;
}
  • Sin retorno: Función con “returns” no retorna

Advierte de los métodos que definen un tipo de retorno pero nunca devuelven explícitamente un valor.

Ejemplo:

function noreturn(string memory _dna) public returns (bool) {
       dna = _dna;
   }
  • Guarde las condiciones: Utilice “require” y “assert” adecuadamente

Utilice assert(x) si nunca jamás quiere que x sea falso, en ninguna circunstancia (aparte de un error en su código). Utilice require(x) si x puede ser falso, debido, por ejemplo, a una entrada no válida o a un componente externo que falle.

Ejemplo:

assert(a.balance == 0);
  • Resultado no utilizado: El resultado de una operación no utilizada

Una operación binaria produce un valor que no se utiliza en la siguiente. Esto suele deberse a la confusión entre asignación (=) y comparación (==).

Ejemplo:

c == 5;
or
a + b;
  • Longitud de la cadena: Longitud de bytes != Longitud de cadena

Los bytes y la longitud de la cadena no son lo mismo, ya que se supone que las cadenas están codificadas en UTF-8 (según la definición de la ABI), por lo que un carácter no está necesariamente codificado en un byte de datos.

Ejemplo:

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

    return x.length;
}
  • Borrar de un array dinámico: “borrar” en un array deja un hueco

Utilizar delete en un array deja un hueco. La longitud del array sigue siendo la misma. Si desea eliminar la posición vacía, deberá desplazar los elementos manualmente y actualizar la propiedad de longitud.

Ejemplo:

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

    function removeAtIndex() public returns (uint[] memory) {
        delete array[1];
        return array;
    }
}
  • Datos truncados: La división en valores int/uint trunca el resultado

La división de valores enteros vuelve a dar como resultado un valor entero. Esto significa, por ejemplo, que 10 / 100 = 0 en lugar de 0,1, ya que el resultado vuelve a ser un número entero. Esto no es válido para la división de (sólo) valores literales ya que esos dan constantes racionales.

Ejemplo:

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

Analizador de remezclas

remix-analyzer es la biblioteca que funciona por debajo de la herramienta Remix Analysis.

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.