Saltar al contenido

Artículos

Auditoría de smart contracts: checklist de preparación

30 comprobaciones antes de una auditoría de smart contracts: alcance y docs, tests, fuzzing e invariantes con Foundry, triaje de Slither, accesos y despliegue.

Equipo de ingeniería de sigmacode.io8 min de lectura

En esta página (9)
  1. 1. Alcance y documentación
  2. 2. Higiene del código y análisis estático
  3. 3. Tests
  4. 4. Fuzzing y tests de invariantes
  5. 5. Control de acceso y actualizabilidad
  6. 6. Integraciones externas
  7. 7. Despliegue y operación
  8. 8. Logística de la auditoría
  9. Cómo podemos ayudarte

Una auditoría de smart contracts es cara y tiene un plazo estrictamente acotado. Los auditores disponen de un número fijo de días, y cada hora que dedican a averiguar qué se supone que hace el código, a perseguir un commit que no deja de moverse o a escribir los tests que tú no escribiste es una hora que no dedican a la lógica que de verdad puede hacer perder fondos. El informe de hallazgos se llena entonces de NatSpec ausente, pragmas flotantes y caminos de revert sin probar, en lugar de los problemas por los que pagas.

Un código bien preparado recibe una auditoría más profunda por el mismo presupuesto. Esta checklist reúne lo que dejamos listo antes de entregar código a un auditor externo: 30 comprobaciones concretas sobre documentación, higiene del código, tests, control de acceso, integraciones, despliegue y logística. No sustituye a la auditoría; se asegura de que el tiempo de auditoría vaya a donde importa.

1. Alcance y documentación#

Los auditores solo pueden verificar el comportamiento frente a una intención que entienden, así que la intención tiene que estar por escrito.

  • Congela un hash de commit. Entrega a los auditores un único commit etiquetado y no cambies el código en revisión durante el encargo. Las correcciones van en una rama aparte y se revisan en la ronda de revisión de correcciones.
  • Publica la lista de archivos del alcance con su nSLOC. Enumera cada contrato incluido en el alcance con su ruta y sus líneas de código fuente normalizadas, e indica de forma explícita qué queda fuera (librerías, mocks, scripts, archivos ya auditados). Los auditores presupuestan y planifican a partir del nSLOC, así que un recuento preciso evita sorpresas a ambas partes.
  • Escribe una visión general de la arquitectura. Una o dos páginas que describan los contratos, cómo se llaman entre sí, cuáles custodian fondos y los principales flujos de usuario. Un diagrama sencillo de llamadas y flujos de tokens ahorra a los auditores horas de ingeniería inversa.
  • Especifica en prosa el comportamiento y los invariantes. Describe qué debe hacer cada función externa y las propiedades que deben cumplirse siempre, por ejemplo «la suma de los saldos de los usuarios es igual a totalAssets» o «solo el timelock puede cambiar las comisiones». Estas afirmaciones son la base tanto de la revisión de los auditores como de tus propios tests de invariantes.
  • Documenta los problemas conocidos y los riesgos aceptados. Enumera las limitaciones que ya conoces y las decisiones de diseño que asumes, como los compromisos en materia de centralización o los tipos de token no admitidos. Así quedan fuera del informe de hallazgos y los auditores ven dónde ya has pensado las cosas a fondo.

2. Higiene del código y análisis estático#

El ruido en el código cuesta tiempo de auditoría y produce hallazgos de poco valor, así que elimínalo antes de que lo vean los auditores.

  • Fija el compilador y llega a cero warnings. Usa un pragma solidity 0.8.x; exacto en lugar de un ^0.8.0 flotante, y haz que la versión, los optimizer runs, via_ir y evm_version de foundry.toml coincidan con lo que vas a desplegar. Cada warning del compilador debe corregirse o explicarse de forma consciente.
  • Fija y enumera las dependencias. Bloquea OpenZeppelin Contracts y cualquier otra librería en una versión exacta, por ejemplo un submódulo etiquetado v5.x o una versión exacta en package.json. Enumera todas las dependencias en la documentación de la auditoría y señala los archivos que hayas copiado o modificado.
  • Elimina el código muerto, los TODO y la salida de depuración. Borra de los contratos del alcance las funciones sin usar, el código comentado, los imports de console.log y los helpers de test olvidados. Los TODO abiertos indican lógica sin terminar y se reportarán como tal.
  • Completa el NatSpec de las funciones external y public. Toda función external o public debería tener @notice, @param, @return y, cuando proceda, una nota sobre el control de acceso y los reverts. Usa custom errors y emite eventos en cada cambio de estado que importe off-chain.
  • Ejecuta Slither y haz el triaje de cada hallazgo. Ejecuta slither sobre el commit congelado y clasifica cada hallazgo como corregido, falso positivo o aceptado, con una justificación de una línea. Una segunda herramienta como Aderyn suele detectar patrones distintos, y compartir la salida ya clasificada indica a los auditores qué hallazgos automáticos están ya tratados.

3. Tests#

Los tests muestran a los auditores cómo se supone que debe usarse el código y les permiten escribir pruebas de concepto con rapidez.

  • Cubre todas las funciones externas y publica el informe. Cada función external o public necesita al menos un test del camino feliz. Genera un informe de cobertura de líneas y ramas con forge coverage y explica las ramas sin cubrir en lugar de esconderlas.
  • Prueba los caminos de revert y los casos límite. Comprueba que las llamadas no autorizadas, los parámetros inválidos, los importes cero y los valores frontera revierten con el custom error esperado mediante vm.expectRevert. En los caminos de revert sin probar es donde se esconden los bugs de validación y de control de acceso.
  • Ejecuta fork tests contra integraciones reales. Si el protocolo habla con contratos externos como DEX, mercados de préstamos u oráculos, prueba contra un fork de mainnet o de una L2 con números de bloque fijados. Los mocks solo demuestran que tu código funciona con tus suposiciones sobre la otra parte.

4. Fuzzing y tests de invariantes#

El fuzzing encuentra las combinaciones de entradas en las que nadie pensó, y los tests de invariantes comprueban que el sistema se mantiene coherente a lo largo de secuencias de llamadas.

  • Haz fuzzing de toda función que reciba una entrada numérica. Escribe fuzz tests de Foundry para importes, marcas de tiempo, cálculos de participaciones y cálculos de comisiones, y acota las entradas con bound() en lugar de abusar de vm.assume. La dirección del redondeo y el desbordamiento en valores extremos son los objetivos típicos.
  • Escribe tests de invariantes con estado y con handlers. Usa los tests de invariantes de Foundry con contratos handler que llamen al sistema en secuencias realistas desde varios actores. Comprueba la solvencia, la conservación del valor y las propiedades de control de acceso después de cada secuencia de llamadas.
  • Mantén sincronizados los invariantes escritos y la suite de tests. Cada invariante de tu especificación debería corresponderse con un test de invariantes, y cada test de invariantes debería poder rastrearse hasta la especificación. Ejecuta la suite en CI con un número de runs y una profundidad significativos, no solo en local con los valores por defecto.

5. Control de acceso y actualizabilidad#

Las funciones privilegiadas y las actualizaciones pueden mover o bloquear todos los activos del sistema, así que el modelo de confianza tiene que ser explícito.

  • Enumera todos los roles y funciones privilegiados. Documenta cada rol, qué funciones puede llamar, quién lo ostenta y cuál es el peor escenario si su clave se ve comprometida. Es la sección de supuestos de confianza que los auditores pedirán en primer lugar.
  • Pon el poder de administración detrás de una multisig y un timelock. Los parámetros críticos y las actualizaciones deberían estar controlados por una multisig como Safe, con un TimelockController o un retardo equivalente para los cambios que afecten a los fondos de los usuarios. Documenta el umbral de firmantes y el retardo.
  • Comprueba el layout de storage de los contratos actualizables. Para los proxies, usa los contratos actualizables de OpenZeppelin v5 con namespaced storage ERC-7201 y valida las actualizaciones con las herramientas de upgrades de OpenZeppelin. Incluye el diff del layout de storage entre versiones si hay una actualización dentro del alcance.
  • Protege los initializers y las funciones de actualización. Llama a _disableInitializers() en el constructor de la implementación, usa correctamente los modificadores initializer y reinitializer y restringe _authorizeUpgrade en los proxies UUPS (ERC-1822). Un contrato de implementación sin proteger es un hallazgo que los auditores nunca deberían tener que reportar.

6. Integraciones externas#

Cada llamada externa es una suposición sobre el código de otro, y cada suposición debería estar escrita y probada.

  • Gestiona los datos caducados y los fallos del oráculo. Compara updatedAt con el heartbeat del feed, rechaza los precios cero o negativos y, en las L2, consulta el feed de disponibilidad del secuenciador. Decide qué hace el protocolo cuando el oráculo no está disponible y documéntalo.
  • Ten en cuenta las peculiaridades de los tokens. Indica qué tipos de token se admiten y gestiona o excluye de forma explícita los tokens fee-on-transfer, los rebasing, los que no tienen 18 decimales y los ERC-20 no estándar. Usa SafeERC20 en las transferencias y mide las diferencias de saldo allí donde importe el importe recibido.
  • Haz un mapa de las superficies de reentrada. Enumera todas las llamadas externas y transferencias de tokens, sigue checks-effects-interactions y usa ReentrancyGuard (o ReentrancyGuardTransient con EIP-1153) donde se comparta estado. Ten en cuenta la reentrada entre funciones y la de solo lectura, así como los callbacks de los tokens ERC-721, ERC-1155 y ERC-777.
  • Ten en cuenta el MEV y el front-running. Añade límites de slippage y plazos a los swaps y a los depósitos, protege los vaults ERC-4626 frente al ataque de inflación contra el primer depositante y revisa toda lógica que dependa del orden de las transacciones. Documenta qué riesgos de ordenación aceptas.

7. Despliegue y operación#

El código auditado solo es tan seguro como la forma en que se despliega y se opera.

  • Haz que los scripts de despliegue sean reproducibles y documenta los parámetros. Despliega con scripts de Foundry desde el commit congelado, mantén todos los parámetros de constructores e initializers en una configuración bajo control de versiones e incluye los scripts en el alcance de la auditoría o, como mínimo, en la entrega. Un contrato correcto desplegado con parámetros equivocados sigue estando roto.
  • Ensaya en una testnet con el código fuente verificado. Ejecuta exactamente el mismo script de despliegue en una testnet y en un fork de mainnet, verifica el código fuente en el explorador de bloques y comprueba que los roles han quedado en las direcciones previstas. Da a los auditores las direcciones de la testnet para que puedan interactuar con un despliegue real.
  • Prepara la pausa, el runbook y la monitorización. Decide quién puede pausar qué, escribe un runbook de incidentes breve y configura alertas para los cambios de rol, las actualizaciones, las retiradas grandes y los estados de pausa. Los auditores revisarán los caminos de emergencia, así que tienen que existir antes de la auditoría.

8. Logística de la auditoría#

Una buena logística mantiene la auditoría en marcha y garantiza que la ronda de correcciones se aproveche de verdad.

  • Designa un contacto técnico y un canal. Asigna a un desarrollador que conozca el código y pueda responder a las preguntas en cuestión de horas, y acuerda un canal compartido para lo que dure la auditoría. Cada pregunta sin responder frena la revisión.
  • Reserva tiempo para las correcciones y para una ronda de revisión de correcciones. Planifica capacidad de desarrollo justo después de que llegue el informe y acuerda de antemano que los auditores revisen las correcciones. Las correcciones sin revisar pueden introducir bugs nuevos después de que la auditoría haya dado su visto bueno.
  • Comunica las auditorías y revisiones anteriores. Comparte los informes de auditorías previas, el estado de sus correcciones y cualquier revisión interna. Así los auditores pueden centrarse en lo que ha cambiado y comprobar que los hallazgos anteriores no han reaparecido.

Cómo podemos ayudarte#

Ofrecemos un Audit-Readiness Sprint de alcance cerrado, de una a dos semanas: un modelo de amenazas, un análisis de carencias de la suite de tests, fuzz tests y tests de invariantes con Foundry, triaje de Slither, una lista de correcciones priorizada y un paquete de entrega para la auditoría con el alcance, la documentación y los problemas conocidos. No somos una firma de auditoría y el sprint no sustituye a una auditoría externa; prepara tu código para que los auditores puedan dedicar su tiempo a la lógica. Tienes los detalles en nuestra página de Precios y en nuestros servicios de Blockchain y Web3.

Si quieres una estimación gratuita para tu código, cuéntanos tu proyecto. Firmamos con gusto un NDA mutuo antes de que compartas nada de código.

¿Tienes un proyecto en mente?

Cuéntanos qué estás construyendo. Normalmente en pocos días laborables recibes una valoración honesta, un alcance claro y una propuesta a precio fijo o por hitos.

¿Prefieres escribirnos primero? Escríbenos

Hablarás directamente con Ing. Ismet Mesic, Tech lead.