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:
Análisis de remezclas: una herramienta básica de análisis
Solhint linter: un linter de Solidity para validaciones de código y guías de estilo
Slither Static Analysis: una completa herramienta de análisis estático
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