Analizadores de solidez

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

El complemento Solidity Analyzers agrupa tres herramientas de análisis para realizar un análisis estático de los contratos inteligentes Solidity. Cada herramienta busca vulnerabilidades de seguridad y malas prácticas de desarrollo, entre otras cuestiones. Puede activarse desde el Administrador de plugins de Remix.

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 sólo puede utilizarse cuando Remix está conectado al sistema de archivos del ordenador local con Remixd.

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.

Errores y advertencias

Por defecto, Solidity Analyzers mostrará tanto los errores como las advertencias. El número combinado de errores y advertencias se muestra en la insignia de esa pestaña de herramientas.

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

NOTA: El Análisis Remix no marca errores - sólo muestra advertencias, por lo que si marca Ocultar advertencias, no se mostrará nada en la pestaña Análisis Remix.

Avisos de bibliotecas externas

Por defecto, no se muestran las advertencias de las bibliotecas externas. Si marca la casilla Mostrar advertencias de bibliotecas externas, las herramientas también analizarán las bibliotecas externas en busca de advertencias.

Slither

Para ejecutar Slither con este plugin, necesita conectar Remix IDE a su sistema de archivos con Remixd. Una vez que Remixd esté funcionando, Slither se cargará automáticamente.

Solhint

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

Análisis de remezclas

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

He aquí la lista de módulos de cada categoría junto con el código de ejemplo que debe evitarse o utilizarse con mucho cuidado durante el desarrollo:

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);
  • Uso del bloqueo: Semántica quizá poco clara

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

Si la necesidad de gas de una función es superior al límite de gas del bloque, no podrá ejecutarse. Por favor, evite bucles en sus funciones o acciones que modifiquen grandes áreas de almacenamiento

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

Los bucles que no tienen un número fijo de iteraciones, por ejemplo, los bucles que dependen de valores de almacenamiento, deben utilizarse con cuidado: Debido al límite de gas en bloque, las transacciones sólo pueden consumir una determinada cantidad de gas. El número de iteraciones de un bucle puede crecer más allá del límite de gas de bloque, lo que puede paralizar el contrato completo en un momento dado. Además, utilizar bucles no limitados puede acarrear muchos gastos de gas evitables. Pruebe cuidadosamente cuántos elementos como máximo puede pasar a dichas funciones para que tenga éxito.

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

El pago de éter no debe realizarse en bucle. Debido al límite de gas en bloque, las transacciones sólo pueden consumir una determinada cantidad de gas. El número de iteraciones en un bucle puede crecer más allá del límite de gas de bloque, lo que puede provocar que el contrato completo se paralice en un momento determinado. Si es necesario, asegúrese de que el número de iteraciones es bajo y de que confía en cada una de las direcciones implicadas.

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”

La función decimals de ERC20 Contracts debería tener uint8 como tipo de retorno.

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 es un paquete NPM. Puede utilizarse como biblioteca en una solución que soporte node.js. Encontrará más información sobre este tipo de uso en el repositorio remix-analyzer