Preskočite na vsebino

Članki

Smart contract audit: kontrolni seznam za pripravo

30 preverjanj pred auditom pametnih pogodb: obseg in dokumentacija, testi, Foundry fuzz testi in invariante, triaža Slitherja, nadzor dostopa, namestitev.

Inženirska ekipa sigmacode.io8 min branja

Na tej strani (9)
  1. 1. Obseg in dokumentacija
  2. 2. Higiena kode in statična analiza
  3. 3. Testi
  4. 4. Fuzz testi in invariantni testi
  5. 5. Nadzor dostopa in nadgradljivost
  6. 6. Zunanje integracije
  7. 7. Namestitev in obratovanje
  8. 8. Logistika revizije
  9. Kako lahko pomagamo

Revizija pametnih pogodb (smart contract audit) je draga in časovno strogo omejena. Revizorji dobijo fiksno število dni in vsaka ura, ki jo porabijo za ugotavljanje, kaj naj bi koda sploh počela, za lovljenje commita, ki se nenehno premika, ali za pisanje testov, ki jih niste napisali vi, je ura, ki ni šla v logiko, zaradi katere se sredstva dejansko lahko izgubijo. Poročilo z ugotovitvami se nato napolni z manjkajočim NatSpecom, plavajočimi pragmami in netestiranimi potmi z revertom namesto s težavami, za katerih iskanje plačujete.

Dobro pripravljena koda za isti proračun dobi temeljitejšo revizijo. Ta kontrolni seznam zbira vse, kar uredimo, preden kodo predamo zunanjemu revizorju: 30 konkretnih preverjanj s področij dokumentacije, higiene kode, testiranja, nadzora dostopa, integracij, namestitve in logistike. Revizije ne nadomešča; poskrbi pa, da gre čas revizije tja, kjer šteje.

1. Obseg in dokumentacija#

Revizorji lahko vedenje preverijo le glede na namen, ki ga razumejo, zato mora biti namen zapisan.

  • Zamrznite hash commita. Revizorjem dajte en označen commit in kode v pregledu med sodelovanjem ne spreminjajte. Popravki gredo na ločeno vejo in se pregledajo v krogu pregleda popravkov.
  • Objavite seznam datotek v obsegu z nSLOC. Naštejte vsako pogodbo v obsegu z njeno potjo in normaliziranim številom vrstic izvorne kode ter izrecno navedite, kaj je zunaj obsega (knjižnice, mocki, skripte, že revidirane datoteke). Revizorji ponudbo in načrt pripravijo na podlagi nSLOC, zato točno štetje prepreči presenečenja na obeh straneh.
  • Napišite pregled arhitekture. Ena ali dve strani, ki opišeta pogodbe, kako se med seboj kličejo, katere pogodbe hranijo sredstva in glavne uporabniške tokove. Preprost diagram klicev in tokov tokenov revizorjem prihrani ure obratnega inženirstva.
  • Vedenje in invariante opišite z besedami. Opišite, kaj mora narediti vsaka zunanja funkcija, in lastnosti, ki morajo vedno veljati, na primer „vsota stanj uporabnikov je enaka totalAssets“ ali „provizije lahko spremeni samo timelock“. Te trditve postanejo podlaga tako za pregled revizorjev kot za vaše invariantne teste.
  • Dokumentirajte znane težave in sprejeta tveganja. Naštejte omejitve, ki jih že poznate, in načrtovalske odločitve, ki jih sprejemate, na primer kompromise glede centralizacije ali nepodprte vrste tokenov. Tako ne končajo v poročilu z ugotovitvami, revizorjem pa pokažete, kje ste stvari že premislili.

2. Higiena kode in statična analiza#

Šum v kodi stane čas revizije in prinaša ugotovitve z majhno vrednostjo, zato ga odstranite, preden ga vidijo revizorji.

  • Fiksirajte prevajalnik in dosezite nič opozoril. Uporabite natančen pragma solidity 0.8.x; namesto plavajočega ^0.8.0 ter poskrbite, da se različica, optimizer runs, via_ir in evm_version v foundry.toml ujemajo s tem, kar boste namestili. Vsako opozorilo prevajalnika je treba odpraviti ali zavestno pojasniti.
  • Fiksirajte in naštejte odvisnosti. OpenZeppelin Contracts in vsako drugo knjižnico zaklenite na natančno izdajo, na primer na označen podmodul v5.x ali natančno različico v package.json. V dokumentaciji za revizijo naštejte vse odvisnosti in označite vse datoteke, ki ste jih kopirali ali spremenili.
  • Odstranite mrtvo kodo, TODO-je in razhroščevalni izpis. Iz pogodb v obsegu izbrišite neuporabljene funkcije, zakomentirano kodo, uvoze console.log in ostanke testnih pripomočkov. Odprti TODO-ji kažejo na nedokončano logiko in bodo kot takšni tudi prijavljeni.
  • Dopolnite NatSpec na zunanjih in javnih funkcijah. Vsaka zunanja in javna funkcija naj ima @notice, @param, @return ter, kjer je smiselno, opombo o nadzoru dostopa in revertih. Uporabljajte napake po meri (custom errors) in oddajte dogodek ob vsaki spremembi stanja, ki je pomembna off-chain.
  • Poženite Slither in triažirajte vsako ugotovitev. slither poženite na zamrznjenem commitu in vsako ugotovitev razvrstite kot odpravljeno, lažno pozitivno ali sprejeto, z enovrstično utemeljitvijo. Drugo orodje, na primer Aderyn, pogosto ujame drugačne vzorce, triažirani izpis pa revizorjem pove, katere samodejne ugotovitve so že obravnavane.

3. Testi#

Testi revizorjem pokažejo, kako naj bi se koda uporabljala, in jim omogočijo hitro pisanje dokazov koncepta.

  • Pokrijte vsako zunanjo funkcijo in objavite poročilo. Vsaka zunanja in javna funkcija potrebuje vsaj en test običajne, uspešne poti. S forge coverage ustvarite poročilo o pokritosti vrstic in vej ter nepokrite veje pojasnite, namesto da jih skrivate.
  • Testirajte poti z revertom in robne primere. Z vm.expectRevert preverite, da se nepooblaščeni klici, neveljavni parametri, ničelni zneski in mejne vrednosti razveljavijo s pričakovano napako po meri. V netestiranih poteh z revertom se skrivajo napake v validaciji in nadzoru dostopa.
  • Poganjajte fork teste proti resničnim integracijam. Če protokol komunicira z zunanjimi pogodbami, kot so DEX-i, posojilni trgi ali oracli, testirajte na forku glavnega omrežja ali L2 s fiksiranimi številkami blokov. Mocki dokazujejo le, da vaša koda deluje z vašimi predpostavkami o drugi strani.

4. Fuzz testi in invariantni testi#

Fuzzing najde kombinacije vnosov, na katere ni nihče pomislil, invariantni testi pa preverijo, ali sistem ostane konsistenten skozi zaporedja klicev.

  • S fuzzingom preizkusite vsako funkcijo s številskim vnosom. Napišite Foundry fuzz teste za zneske, časovne žige, izračune deležev in matematiko provizij, vnose pa omejite z bound() namesto s pretiranim vm.assume. Tipični tarči sta smer zaokroževanja in prekoračitev pri skrajnih vrednostih.
  • Napišite invariantne teste s stanjem in handlerji. Uporabite Foundryjevo invariantno testiranje s pogodbami handler, ki sistem kličejo v realističnih zaporedjih z več akterji. Po vsakem zaporedju klicev preverite solventnost, ohranjanje vrednosti in lastnosti nadzora dostopa.
  • Zapisane invariante in zbirka testov naj bodo usklajene. Vsaka invarianta iz specifikacije naj ima svoj invariantni test in vsak invariantni test naj bo sledljiv do specifikacije. Zbirko v CI poganjajte s smiselnim številom zagonov in globino, ne samo lokalno s privzetimi vrednostmi.

5. Nadzor dostopa in nadgradljivost#

Privilegirane funkcije in nadgradnje lahko premaknejo ali zaklenejo vsako sredstvo v sistemu, zato mora biti model zaupanja izrecen.

  • Naštejte vse privilegirane vloge in funkcije. Dokumentirajte vsako vlogo, katere funkcije lahko kliče, kdo jo ima in kaj je najslabši scenarij, če je njen ključ kompromitiran. To je razdelek o predpostavkah zaupanja, ki ga bodo revizorji zahtevali najprej.
  • Administratorsko moč postavite za multisig in timelock. Kritične parametre in nadgradnje naj nadzoruje multisig, kot je Safe, s TimelockController ali enakovredno zakasnitvijo za spremembe, ki vplivajo na sredstva uporabnikov. Dokumentirajte prag podpisnikov in zakasnitev.
  • Preverite razporeditev shrambe pri nadgradljivih pogodbah. Za proxyje uporabite nadgradljive pogodbe OpenZeppelin v5 z imenskim prostorom shrambe po ERC-7201 in nadgradnje validirajte z OpenZeppelinovimi orodji za nadgradnje. Če je v obsegu nadgradnja, priložite razliko v razporeditvi shrambe med različicama.
  • Zaščitite inicializatorje in funkcije za nadgradnjo. V konstruktorju implementacije pokličite _disableInitializers(), pravilno uporabljajte modifikatorja initializer in reinitializer ter pri proxyjih UUPS (ERC-1822) omejite _authorizeUpgrade. Nezaščitena implementacijska pogodba je ugotovitev, ki je revizorjem nikoli ne bi smelo biti treba prijaviti.

6. Zunanje integracije#

Vsak zunanji klic je predpostavka o kodi nekoga drugega in vsako predpostavko je treba zapisati in testirati.

  • Obravnavajte zastarelost in izpad oracla. updatedAt preverite glede na heartbeat vira, zavrnite ničelne ali negativne cene in na L2 preverite vir razpoložljivosti sekvencerja (sequencer uptime feed). Odločite se, kaj protokol stori, ko oracle ni na voljo, in to dokumentirajte.
  • Upoštevajte posebnosti tokenov. Navedite, katere vrste tokenov so podprte, in obravnavajte ali izrecno izključite tokene s provizijo ob prenosu (fee-on-transfer), rebasing tokene, tokene, ki nimajo 18 decimalnih mest, in nestandardne tokene ERC-20. Za prenose uporabite SafeERC20 in merite razlike v stanju tam, kjer je pomemben prejeti znesek.
  • Popišite površine za reentrancy. Naštejte vsak zunanji klic in prenos tokenov, držite se vzorca checks-effects-interactions in uporabite ReentrancyGuard (ali ReentrancyGuardTransient z EIP-1153), kjer je stanje deljeno. Upoštevajte reentrancy med funkcijami in read-only reentrancy ter povratne klice tokenov ERC-721, ERC-1155 in ERC-777.
  • Razmislite o MEV in front-runningu. Zamenjavam in pologom dodajte omejitve zdrsa (slippage) in roke, trezorje ERC-4626 zaščitite pred inflacijskim napadom na prvega vplačnika ter preglejte vso logiko, ki je odvisna od vrstnega reda transakcij. Dokumentirajte, katera tveganja vrstnega reda sprejemate.

7. Namestitev in obratovanje#

Revidirana koda je varna le toliko, kolikor je varen način, na katerega je nameščena in upravljana.

  • Namestitvene skripte naj bodo ponovljive, parametri pa dokumentirani. Nameščajte s skriptami Foundry iz zamrznjenega commita, vsak parameter konstruktorja in inicializatorja hranite v konfiguraciji pod nadzorom različic, skripte pa vključite v obseg revizije ali vsaj v predajo. Pravilna pogodba, nameščena z napačnimi parametri, je še vedno pokvarjena.
  • Vadite na testnem omrežju z verificiranimi viri. Povsem isto namestitveno skripto poženite na testnem omrežju in na forku glavnega omrežja, izvorno kodo verificirajte v raziskovalcu blokov in preverite, ali so vloge pristale pri predvidenih naslovih. Revizorjem dajte naslove s testnega omrežja, da lahko delajo z resnično namestitvijo.
  • Pripravite zaustavitev, runbook in spremljanje. Odločite se, kdo lahko kaj zaustavi, napišite kratek runbook za incidente in nastavite opozorila za spremembe vlog, nadgradnje, velike dvige in zaustavljena stanja. Revizorji bodo pregledali poti za ukrepanje v sili, zato morajo obstajati pred revizijo.

8. Logistika revizije#

Dobra logistika skrbi, da revizija teče, in zagotovi, da je krog popravkov dejansko izkoriščen.

  • Določite tehnično kontaktno osebo in kanal. Določite enega razvijalca, ki pozna kodo in lahko na vprašanja odgovori v nekaj urah, ter se dogovorite za skupen kanal za čas trajanja revizije. Vsako neodgovorjeno vprašanje pregled ustavi.
  • Rezervirajte čas za popravke in krog pregleda popravkov. Načrtujte razvijalske zmogljivosti takoj po prejemu poročila in se vnaprej dogovorite, da revizorji popravke pregledajo. Nepregledani popravki lahko vnesejo nove napake po tem, ko je bila revizija že zaključena.
  • Razkrijte prejšnje revizije in preglede. Delite prejšnja revizijska poročila, stanje njihovih popravkov in morebitne interne preglede. Revizorji se tako lahko osredotočijo na to, kar se je spremenilo, in preverijo, da se prejšnje ugotovitve niso ponovile.

Kako lahko pomagamo#

Ponujamo Audit-Readiness Sprint s fiksnim obsegom, ki traja en do dva tedna: model groženj, analiza vrzeli v zbirki testov, fuzz in invariantni testi Foundry, triaža Slitherja, prednostno razvrščen seznam popravkov in paket za predajo revizorjem z obsegom, dokumentacijo in znanimi težavami. Nismo revizijsko podjetje in sprint ne nadomešča zunanje revizije; vašo kodo pripravi tako, da lahko revizorji svoj čas namenijo logiki. Podrobnosti so na naši strani s cenami in pri naših storitvah Blockchain in Web3.

Če želite brezplačno oceno za svojo kodo, nam povejte več o projektu. Pred deljenjem kakršne koli kode z veseljem podpišemo vzajemni NDA.

Imate v mislih projekt?

Povejte nam, kaj razvijate. Senior inženir odgovori v 24 urah ob delovnih dneh, v nekaj delovnih dneh pa prejmete iskreno oceno, jasen obseg in ponudbo s fiksno ceno ali po mejnikih.

Bi raje najprej pisali? Pišite nam

Vaš sogovornik: Ing. Ismet Mesic, Tehnični vodja.