Aller au contenu

Articles

Audit smart contract : la checklist de préparation

30 contrôles avant un audit smart contract : périmètre, docs, tests, fuzzing et invariants Foundry, tri Slither, contrôle d’accès, intégrations et déploiement.

Équipe d’ingénierie de sigmacode.io8 min de lecture

Sur cette page (9)
  1. 1. Périmètre et documentation
  2. 2. Hygiène du code et analyse statique
  3. 3. Tests
  4. 4. Fuzzing et tests d’invariants
  5. 5. Contrôle d’accès et upgradeabilité
  6. 6. Intégrations externes
  7. 7. Déploiement et exploitation
  8. 8. Logistique de l’audit
  9. Comment nous pouvons vous aider

Un audit de smart contract coûte cher et son temps est strictement compté. Les auditeurs disposent d’un nombre de jours fixe, et chaque heure qu’ils passent à comprendre ce que le code est censé faire, à courir après un commit qui bouge ou à écrire les tests que vous n’avez pas écrits est une heure qui n’est pas consacrée à la logique réellement susceptible de faire perdre des fonds. Le rapport de constats se remplit alors de NatSpec manquant, de pragmas flottants et de chemins de revert non testés, au lieu des problèmes que vous payez pour faire découvrir.

Une base de code bien préparée obtient un audit plus approfondi pour le même budget. Cette checklist rassemble ce que nous mettons en place avant de confier du code à un auditeur externe : 30 contrôles concrets portant sur la documentation, l’hygiène du code, les tests, le contrôle d’accès, les intégrations, le déploiement et la logistique. Elle ne remplace pas l’audit ; elle garantit que le temps d’audit va là où il compte.

1. Périmètre et documentation#

Les auditeurs ne peuvent vérifier un comportement que par rapport à une intention qu’ils comprennent : cette intention doit donc être écrite.

  • Gelez un hash de commit. Donnez aux auditeurs un seul commit tagué et ne modifiez pas le code en cours de revue pendant la mission. Les correctifs vont sur une branche distincte et sont examinés lors de la revue des correctifs.
  • Publiez la liste des fichiers du périmètre avec leur nSLOC. Listez chaque contrat du périmètre avec son chemin et son nombre de lignes de code source normalisées, et indiquez explicitement ce qui est hors périmètre (bibliothèques, mocks, scripts, fichiers déjà audités). Les auditeurs établissent leur devis et leur planning à partir du nSLOC : un décompte exact évite les surprises de part et d’autre.
  • Rédigez une vue d’ensemble de l’architecture. Une ou deux pages décrivant les contrats, la façon dont ils s’appellent entre eux, ceux qui détiennent des fonds et les principaux parcours utilisateurs. Un simple schéma des appels et des flux de tokens épargne aux auditeurs des heures de rétro-ingénierie.
  • Spécifiez le comportement et les invariants en toutes lettres. Décrivez ce que chaque fonction externe doit faire et les propriétés qui doivent toujours être vraies, par exemple « la somme des soldes des utilisateurs est égale à totalAssets » ou « seul le timelock peut modifier les frais ». Ces énoncés servent de base à la revue des auditeurs comme à vos propres tests d’invariants.
  • Documentez les problèmes connus et les risques acceptés. Listez les limites que vous connaissez déjà et les choix de conception que vous assumez, comme les compromis de centralisation ou les types de tokens non pris en charge. Ils restent ainsi hors du rapport de constats et montrent aux auditeurs où vous avez déjà mené la réflexion.

2. Hygiène du code et analyse statique#

Le bruit dans la base de code coûte du temps d’audit et produit des constats de faible valeur : éliminez-le avant que les auditeurs ne le voient.

  • Fixez la version du compilateur et visez zéro avertissement. Utilisez un pragma solidity 0.8.x; exact au lieu d’un ^0.8.0 flottant, et faites correspondre la version, les optimizer runs, via_ir et evm_version de foundry.toml à ce que vous déploierez. Chaque avertissement du compilateur doit être corrigé ou expliqué en connaissance de cause.
  • Fixez et listez les dépendances. Verrouillez OpenZeppelin Contracts et toute autre bibliothèque sur une release exacte, par exemple un sous-module tagué v5.x ou une version exacte dans package.json. Listez toutes les dépendances dans la documentation d’audit et signalez tout fichier que vous avez copié ou modifié.
  • Supprimez le code mort, les TODO et les sorties de débogage. Retirez des contrats du périmètre les fonctions inutilisées, le code commenté, les imports de console.log et les helpers de test oubliés. Des TODO ouverts signalent une logique inachevée et seront rapportés comme tels.
  • Complétez le NatSpec des fonctions externes et publiques. Chaque fonction externe ou publique doit comporter @notice, @param, @return et, le cas échéant, une note sur le contrôle d’accès et les reverts. Utilisez des custom errors et émettez un événement pour chaque changement d’état qui compte off-chain.
  • Exécutez Slither et triez chaque constat. Lancez slither sur le commit gelé et classez chaque constat comme corrigé, faux positif ou accepté, avec une justification d’une ligne. Un second outil comme Aderyn détecte souvent d’autres motifs, et partager la sortie triée indique aux auditeurs quels constats automatisés sont déjà traités.

3. Tests#

Les tests montrent aux auditeurs comment le code est censé être utilisé et leur permettent d’écrire rapidement des preuves de concept.

  • Couvrez chaque fonction externe et publiez le rapport. Chaque fonction externe ou publique a besoin d’au moins un test du cas nominal. Générez un rapport de couverture des lignes et des branches avec forge coverage et expliquez les branches non couvertes au lieu de les cacher.
  • Testez les chemins de revert et les cas limites. Vérifiez que les appels non autorisés, les paramètres invalides, les montants nuls et les valeurs limites font un revert avec la custom error attendue, via vm.expectRevert. C’est dans les chemins de revert non testés que se cachent les bugs de validation et de contrôle d’accès.
  • Exécutez des tests sur fork face aux intégrations réelles. Si le protocole dialogue avec des contrats externes tels que des DEX, des marchés de prêt ou des oracles, testez sur un fork du mainnet ou d’un L2 avec des numéros de bloc fixés. Les mocks prouvent seulement que votre code fonctionne avec vos propres hypothèses sur le contrat d’en face.

4. Fuzzing et tests d’invariants#

Le fuzzing trouve les combinaisons d’entrées auxquelles personne n’avait pensé, et les tests d’invariants vérifient que le système reste cohérent au fil de séquences d’appels.

  • Soumettez au fuzzing chaque fonction qui prend une entrée numérique. Écrivez des tests de fuzzing Foundry pour les montants, les timestamps, les calculs de parts et les calculs de frais, et contraignez les entrées avec bound() plutôt qu’avec un excès de vm.assume. Le sens des arrondis et l’overflow aux valeurs extrêmes sont les cibles typiques.
  • Écrivez des tests d’invariants avec état, à l’aide de handlers. Utilisez les tests d’invariants de Foundry avec des contrats handlers qui appellent le système selon des séquences réalistes, depuis plusieurs acteurs. Vérifiez la solvabilité, la conservation de la valeur et les propriétés de contrôle d’accès après chaque séquence d’appels.
  • Gardez les invariants écrits et la suite de tests synchronisés. Chaque invariant de votre spécification doit correspondre à un test d’invariant, et chaque test d’invariant doit renvoyer à la spécification. Exécutez la suite dans la CI avec un nombre de runs et une profondeur significatifs, pas seulement en local avec les valeurs par défaut.

5. Contrôle d’accès et upgradeabilité#

Les fonctions privilégiées et les upgrades peuvent déplacer ou bloquer tous les actifs du système : le modèle de confiance doit donc être explicite.

  • Listez chaque rôle et chaque fonction privilégiés. Documentez chaque rôle, les fonctions qu’il peut appeler, qui le détient et le pire scénario si sa clé est compromise. C’est la section sur les hypothèses de confiance, celle que les auditeurs demanderont en premier.
  • Placez le pouvoir d’administration derrière un multisig et un timelock. Les paramètres critiques et les upgrades doivent être contrôlés par un multisig tel que Safe, avec un TimelockController ou un délai équivalent pour les changements qui touchent aux fonds des utilisateurs. Documentez le seuil de signataires et le délai.
  • Vérifiez le storage layout des contrats upgradeables. Pour les proxies, utilisez les contrats upgradeables d’OpenZeppelin v5 avec le namespaced storage ERC-7201 et validez les upgrades avec l’outillage d’upgrades d’OpenZeppelin. Joignez le diff de storage layout entre versions si un upgrade fait partie du périmètre.
  • Protégez les initializers et les fonctions d’upgrade. Appelez _disableInitializers() dans le constructeur de l’implémentation, utilisez correctement les modifiers initializer et reinitializer, et restreignez _authorizeUpgrade pour les proxies UUPS (ERC-1822). Un contrat d’implémentation non protégé est un constat que les auditeurs ne devraient jamais avoir à rapporter.

6. Intégrations externes#

Chaque appel externe est une hypothèse sur le code de quelqu’un d’autre, et chaque hypothèse doit être écrite et testée.

  • Gérez la péremption et la défaillance des oracles. Comparez updatedAt au heartbeat du flux, rejetez les prix nuls ou négatifs et, sur les L2, vérifiez le flux de disponibilité du séquenceur. Décidez de ce que fait le protocole quand l’oracle est indisponible, et documentez-le.
  • Tenez compte des particularités des tokens. Indiquez quels types de tokens sont pris en charge et gérez, ou excluez explicitement, les tokens fee-on-transfer, à rebasing, à nombre de décimales différent de 18 et les ERC-20 non standard. Utilisez SafeERC20 pour les transferts et mesurez les différences de solde lorsque le montant reçu importe.
  • Cartographiez les surfaces de réentrance. Listez chaque appel externe et chaque transfert de tokens, respectez checks-effects-interactions et utilisez ReentrancyGuard (ou ReentrancyGuardTransient avec EIP-1153) là où l’état est partagé. Pensez à la réentrance entre fonctions et en lecture seule, ainsi qu’aux callbacks des tokens ERC-721, ERC-1155 et ERC-777.
  • Prenez en compte le MEV et le front-running. Ajoutez des limites de slippage et des deadlines aux swaps et aux dépôts, protégez les vaults ERC-4626 contre l’attaque par inflation du premier déposant et passez en revue toute logique qui dépend de l’ordre des transactions. Documentez les risques d’ordonnancement que vous acceptez.

7. Déploiement et exploitation#

Le code audité n’est sûr que dans la mesure où la façon de le déployer et de l’exploiter l’est aussi.

  • Rendez les scripts de déploiement reproductibles et documentez les paramètres. Déployez avec des scripts Foundry depuis le commit gelé, conservez chaque paramètre de constructeur et d’initializer dans une configuration versionnée et incluez les scripts dans le périmètre de l’audit, ou au moins dans le dossier de passation. Un contrat correct déployé avec de mauvais paramètres reste un contrat défaillant.
  • Répétez sur un testnet avec des sources vérifiées. Exécutez le script de déploiement exact sur un testnet et sur un fork du mainnet, vérifiez les sources sur l’explorateur de blocs et contrôlez que les rôles ont bien été attribués aux adresses prévues. Donnez aux auditeurs les adresses du testnet pour qu’ils puissent interagir avec un déploiement réel.
  • Préparez la pause, le runbook et le monitoring. Décidez qui peut mettre quoi en pause, rédigez un court runbook d’incident et configurez des alertes sur les changements de rôle, les upgrades, les retraits importants et les états de pause. Les auditeurs examineront les procédures d’urgence : elles doivent donc exister avant l’audit.

8. Logistique de l’audit#

Une bonne logistique maintient l’audit en mouvement et garantit que la phase de revue des correctifs est réellement mise à profit.

  • Désignez un interlocuteur technique et un canal. Nommez un développeur qui connaît la base de code et peut répondre aux questions en quelques heures, et convenez d’un canal partagé pour la durée de l’audit. Chaque question sans réponse bloque la revue.
  • Réservez du temps pour les correctifs et pour leur revue. Prévoyez de la capacité de développement dès l’arrivée du rapport et convenez à l’avance que les auditeurs examineront les correctifs. Des correctifs non relus peuvent introduire de nouveaux bugs une fois l’audit validé.
  • Communiquez les audits et revues antérieurs. Partagez les rapports d’audit précédents, l’état de leurs correctifs et toute revue interne. Les auditeurs peuvent alors se concentrer sur ce qui a changé et vérifier que les constats antérieurs n’ont pas régressé.

Comment nous pouvons vous aider#

Nous proposons un Audit-Readiness Sprint, un sprint au périmètre fixe d’une à deux semaines : un modèle de menaces, une analyse des lacunes de la suite de tests, des tests de fuzzing et d’invariants Foundry, la qualification des constats Slither, une liste de correctifs priorisée et un dossier de passation pour l’audit avec le périmètre, la documentation et les problèmes connus. Nous ne sommes pas un cabinet d’audit et le sprint ne remplace pas un audit externe ; il prépare votre code pour que les auditeurs puissent consacrer leur temps à la logique. Les détails figurent sur notre page Tarifs et dans nos services Blockchain & Web3.

Si vous souhaitez une estimation gratuite pour votre base de code, parlez-nous de votre projet. Nous signons volontiers un NDA réciproque avant que vous ne partagiez le moindre code.

Vous avez un projet en tête ?

Décrivez votre projet en quelques lignes. Un ingénieur senior vous répond sous 24 heures les jours ouvrés ; suivent un avis honnête, un périmètre clair et une proposition à prix fixe ou par jalons, en général en quelques jours ouvrés.

Vous préférez d’abord écrire ? Écrivez-nous

Vous échangez directement avec Ing. Ismet Mesic, Tech lead.