Naar de inhoud

Insights

Smart contract audit checklist: klaar voor de audit

30 checks vóór je smart contract audit: scope en docs, tests, fuzz- en invarianttests met Foundry, Slither-triage, toegangscontrole, integraties en deployment.

Engineeringteam van sigmacode.io8 min leestijd

Op deze pagina (9)
  1. 1. Scope en documentatie
  2. 2. Codehygiëne en statische analyse
  3. 3. Tests
  4. 4. Fuzz- en invarianttests
  5. 5. Toegangscontrole en upgradeability
  6. 6. Externe integraties
  7. 7. Deployment en beheer
  8. 8. Logistiek van de audit
  9. Hoe wij kunnen helpen

Een smart-contractaudit is duur en strikt afgebakend in tijd. De auditors krijgen een vast aantal dagen, en elk uur dat ze besteden aan uitzoeken wat de code hoort te doen, achter een bewegende commit aan rennen of de tests schrijven die jij niet hebt geschreven, is een uur dat niet naar de logica gaat waarmee daadwerkelijk geld verloren kan gaan. Het rapport met bevindingen loopt dan vol met ontbrekende NatSpec, floating pragma's en ongeteste revert-paden in plaats van de problemen waarvoor je betaalt om ze te vinden.

Een goed voorbereide codebase krijgt voor hetzelfde budget een diepere audit. Deze checklist bundelt wat wij op orde brengen voordat we code aan een externe auditor overdragen: 30 concrete controles op het gebied van documentatie, codehygiëne, testen, toegangscontrole, integraties, deployment en logistiek. De checklist vervangt de audit niet; hij zorgt ervoor dat de audittijd terechtkomt waar het ertoe doet.

1. Scope en documentatie#

Auditors kunnen gedrag alleen verifiëren tegen een bedoeling die ze begrijpen, dus die bedoeling moet op papier staan.

  • Bevries een commit-hash. Geef de auditors één getagde commit en wijzig de code die wordt gereviewd niet tijdens de opdracht. Fixes gaan naar een aparte branch en worden beoordeeld in de fix-reviewronde.
  • Publiceer een lijst met bestanden in scope, met nSLOC. Vermeld elk contract in scope met zijn pad en het aantal genormaliseerde regels broncode, en zeg expliciet wat buiten scope valt (libraries, mocks, scripts, eerder geaudite bestanden). Auditors offreren en plannen op basis van nSLOC, dus een nauwkeurige telling voorkomt verrassingen aan beide kanten.
  • Schrijf een architectuuroverzicht. Eén of twee pagina's die de contracts beschrijven, hoe ze elkaar aanroepen, welke contracts geld vasthouden en wat de belangrijkste gebruikersflows zijn. Een eenvoudig diagram van calls en tokenstromen bespaart de auditors uren reverse engineering.
  • Specificeer gedrag en invarianten in gewone tekst. Beschrijf wat elke externe functie moet doen en welke eigenschappen altijd moeten gelden, bijvoorbeeld ‘de som van de gebruikerssaldi is gelijk aan totalAssets’ of ‘alleen de timelock kan fees wijzigen’. Deze uitspraken vormen de basis voor zowel de review van de auditors als je eigen invarianttests.
  • Documenteer bekende problemen en geaccepteerde risico's. Zet de beperkingen die je al kent en de ontwerpbeslissingen die je accepteert op een rij, zoals afwegingen rond centralisatie of niet-ondersteunde tokentypes. Zo blijven ze buiten het rapport met bevindingen en zien de auditors waar je al over hebt nagedacht.

2. Codehygiëne en statische analyse#

Ruis in de codebase kost audittijd en levert bevindingen van weinig waarde op, dus haal die weg voordat de auditors hem zien.

  • Pin de compiler en kom uit op nul warnings. Gebruik een exacte pragma solidity 0.8.x; in plaats van een floating ^0.8.0, en zorg dat de versie, de optimizer runs, via_ir en evm_version in foundry.toml overeenkomen met wat je gaat deployen. Elke compilerwarning hoort te zijn opgelost of bewust te zijn toegelicht.
  • Pin je dependencies en zet ze op een lijst. Zet OpenZeppelin Contracts en elke andere library vast op een exacte release, bijvoorbeeld een getagde v5.x-submodule of een exacte versie in package.json. Vermeld alle dependencies in de auditdocumentatie en markeer bestanden die je hebt gekopieerd of aangepast.
  • Verwijder dode code, TODO's en debug-output. Haal ongebruikte functies, uitgecommentarieerde code, console.log-imports en achtergebleven testhelpers uit de contracts in scope. Openstaande TODO's wijzen op onaffe logica en worden ook zo gerapporteerd.
  • Maak NatSpec compleet op externe en publieke functies. Elke externe en publieke functie hoort @notice, @param, @return te hebben en, waar relevant, een opmerking over toegangscontrole en reverts. Gebruik custom errors en emit events voor elke statewijziging die off-chain van belang is.
  • Draai Slither en triageer elke bevinding. Draai slither op de bevroren commit en classificeer elke bevinding als opgelost, false positive of geaccepteerd, met een onderbouwing van één regel. Een tweede tool zoals Aderyn vangt vaak andere patronen, en door de getriageerde output te delen weten auditors welke geautomatiseerde bevindingen al zijn afgehandeld.

3. Tests#

Tests laten de auditors zien hoe de code bedoeld is om te worden gebruikt en stellen hen in staat snel proofs of concept te schrijven.

  • Dek elke externe functie af en publiceer het rapport. Elke externe en publieke functie heeft minstens één test van het happy path nodig. Genereer met forge coverage een rapport over line en branch coverage en licht niet-gedekte branches toe in plaats van ze te verstoppen.
  • Test revert-paden en randgevallen. Assert met vm.expectRevert dat ongeautoriseerde calls, ongeldige parameters, nulbedragen en grenswaarden reverten met de verwachte custom error. In ongeteste revert-paden verstoppen zich bugs in validatie en toegangscontrole.
  • Draai forktests tegen echte integraties. Als het protocol met externe contracts praat, zoals DEX'en, lending markets of oracles, test dan tegen een mainnet- of L2-fork met gepinde blocknummers. Mocks bewijzen alleen dat je code werkt met jouw aannames over de andere kant.

4. Fuzz- en invarianttests#

Fuzzing vindt de combinaties van inputs waar niemand aan had gedacht, en invarianttests controleren of het systeem consistent blijft over reeksen van calls.

  • Fuzz elke functie die numerieke input aanneemt. Schrijf Foundry-fuzztests voor bedragen, timestamps, shareberekeningen en feeberekeningen, en begrens inputs met bound() in plaats van met overmatig veel vm.assume. De afrondingsrichting en overflow bij extreme waarden zijn de typische doelwitten.
  • Schrijf stateful invarianttests met handlers. Gebruik invariant testing van Foundry met handlercontracts die het systeem in realistische reeksen vanuit meerdere actoren aanroepen. Controleer na elke reeks calls de solvabiliteit, het behoud van waarde en de eigenschappen van de toegangscontrole.
  • Houd de beschreven invarianten en de testsuite synchroon. Elke invariant uit je specificatie hoort bij een invariant-test, en elke invariant-test hoort te herleiden te zijn naar de specificatie. Draai de suite in CI met een zinvol aantal runs en een zinvolle depth, niet alleen lokaal met de standaardwaarden.

5. Toegangscontrole en upgradeability#

Geprivilegieerde functies en upgrades kunnen elke asset in het systeem verplaatsen of vastzetten, dus het vertrouwensmodel moet expliciet zijn.

  • Zet elke geprivilegieerde rol en functie op een rij. Documenteer elke rol, welke functies die kan aanroepen, wie hem in handen heeft en wat het ergste geval is als de key wordt gecompromitteerd. Dit is het onderdeel over vertrouwensaannames waar auditors als eerste om vragen.
  • Zet adminmacht achter een multisig en een timelock. Kritieke parameters en upgrades horen te worden beheerd door een multisig zoals Safe, met een TimelockController of een vergelijkbare vertraging voor wijzigingen die het geld van gebruikers raken. Documenteer de drempel voor signers en de vertraging.
  • Controleer de storage-layout van upgradeable contracts. Gebruik voor proxy's de upgradeable contracts van OpenZeppelin v5 met ERC-7201 namespaced storage en valideer upgrades met de upgrades-tooling van OpenZeppelin. Voeg de diff van de storage-layout tussen versies toe als er een upgrade in scope is.
  • Bescherm initializers en upgradefuncties. Roep _disableInitializers() aan in de constructor van de implementatie, gebruik de modifiers initializer en reinitializer correct en scherm _authorizeUpgrade af bij UUPS-proxy's (ERC-1822). Een onbeschermd implementatiecontract is een bevinding die auditors nooit zouden moeten hoeven rapporteren.

6. Externe integraties#

Elke externe call is een aanname over de code van iemand anders, en elke aanname hoort te zijn opgeschreven en getest.

  • Vang verouderde en uitgevallen oracles af. Controleer updatedAt tegen de heartbeat van de feed, weiger prijzen van nul of lager en controleer op L2's de sequencer uptime feed. Bepaal wat het protocol doet als het oracle niet beschikbaar is en documenteer dat.
  • Houd rekening met eigenaardigheden van tokens. Geef aan welke tokentypes worden ondersteund en handel fee-on-transfer-, rebasing-, niet-18-decimalen- en niet-standaard ERC-20-tokens af of sluit ze expliciet uit. Gebruik SafeERC20 voor transfers en meet saldoverschillen waar het ontvangen bedrag ertoe doet.
  • Breng de reentrancy-oppervlakken in kaart. Zet elke externe call en tokentransfer op een rij, volg checks-effects-interactions en gebruik ReentrancyGuard (of ReentrancyGuardTransient met EIP-1153) waar state wordt gedeeld. Denk aan cross-function en read-only reentrancy en aan callbacks van ERC-721-, ERC-1155- en ERC-777-tokens.
  • Denk na over MEV en front-running. Voeg slippagelimieten en deadlines toe aan swaps en stortingen, bescherm ERC-4626-vaults tegen de inflation attack op de eerste storter en review alle logica die afhangt van de volgorde van transacties. Documenteer welke volgorderisico's je accepteert.

7. Deployment en beheer#

De geaudite code is maar zo veilig als de manier waarop hij wordt gedeployd en beheerd.

  • Maak deployscripts reproduceerbaar en documenteer de parameters. Deploy met Foundry-scripts vanaf de bevroren commit, bewaar elke constructor- en initializer-parameter in configuratie onder versiebeheer en neem de scripts op in de auditscope of op zijn minst in de overdracht. Een correct contract dat met verkeerde parameters is gedeployd, is nog steeds kapot.
  • Oefen op een testnet met geverifieerde broncode. Draai exact hetzelfde deploymentscript op een testnet en op een mainnet-fork, verifieer de broncode op de block explorer en controleer of de rollen bij de bedoelde adressen zijn terechtgekomen. Geef de auditors de testnetadressen, zodat ze met een echte deployment kunnen werken.
  • Bereid pauze, runbook en monitoring voor. Bepaal wie wat mag pauzeren, schrijf een kort incident-runbook en stel alerts in voor rolwijzigingen, upgrades, grote opnames en gepauzeerde toestanden. Auditors beoordelen de noodpaden, dus die moeten er vóór de audit zijn.

8. Logistiek van de audit#

Goede logistiek houdt de vaart in de audit en zorgt ervoor dat de fixronde ook echt wordt benut.

  • Wijs een technisch aanspreekpunt en een kanaal aan. Wijs één developer aan die de codebase kent en vragen binnen enkele uren kan beantwoorden, en spreek voor de duur van de audit een gedeeld kanaal af. Elke onbeantwoorde vraag legt de review stil.
  • Reserveer tijd voor fixes en een fix-reviewronde. Plan developercapaciteit in voor direct nadat het rapport binnenkomt en spreek vooraf af dat de auditors de fixes reviewen. Fixes zonder review kunnen nieuwe bugs introduceren nadat de audit al is afgetekend.
  • Deel eerdere audits en reviews. Deel eerdere auditrapporten, de status van de fixes daarin en eventuele interne reviews. Auditors kunnen zich dan richten op wat er is veranderd en controleren of eerdere bevindingen niet zijn teruggekomen.

Hoe wij kunnen helpen#

Wij bieden een Audit-Readiness Sprint met vaste scope van één tot twee weken: een threat model, een gap-analyse van de testsuite, fuzz- en invarianttests met Foundry, Slither-triage, een geprioriteerde lijst met fixes en een overdrachtspakket voor de audit met scope, documentatie en bekende problemen. Wij zijn geen auditbedrijf en de sprint vervangt geen externe audit; hij bereidt je code zo voor dat de auditors hun tijd aan de logica kunnen besteden. Details vind je op onze prijzenpagina en bij onze diensten voor Blockchain & Web3.

Wil je een gratis raming voor je codebase, vertel ons dan over je project. We tekenen graag een wederzijdse NDA voordat je code deelt.

Heb je een project in gedachten?

Vertel ons wat je bouwt. Een senior engineer reageert op werkdagen binnen 24 uur, en meestal heb je binnen enkele werkdagen een eerlijke inschatting, een heldere scope en een voorstel met een vaste prijs of mijlpalen.

Liever eerst schrijven? Stuur ons een bericht

Je spreekt direct met Ing. Ismet Mesic, Tech lead.