hacktricks/src/blockchain/smart-contract-security/mutation-testing-with-slither.md

5.7 KiB
Raw Blame History

Mutation Testing za Solidity sa Slither (slither-mutate)

{{#include ../../banners/hacktricks-training.md}}

Mutation testing "tests your tests" tako što sistematski uvodi male promene (mutante) u vaš Solidity kod i ponovo pokreće vaš skup testova. Ako test padne, mutant se smatra ubijenim. Ako testovi i dalje prolaze, mutant preživi, otkrivajući slepu tačku u vašem skupu testova koju linijsko/gransko pokriće ne može detektovati.

Ključna ideja: pokriće pokazuje da je kod izvršen; mutation testing pokazuje da li je ponašanje zaista provereno.

Zašto pokriće može zavarati

Razmotrite ovu jednostavnu proveru praga:

function verifyMinimumDeposit(uint256 deposit) public returns (bool) {
if (deposit >= 1 ether) {
return true;
} else {
return false;
}
}

Jedinični testovi koji proveravaju samo vrednost ispod i vrednost iznad praga mogu dostići 100% line/branch coverage, a da ne asertuju granicu jednakosti (==). Refaktor u deposit >= 2 ether bi i dalje prošao takve testove, tiho kvareći logiku protokola.

Mutation testing otkriva ovaj prazninu mutiranjem uslova i proverom da li vaši testovi zakažu.

Common Solidity mutation operators

Slithers mutation engine primenjuje mnogo malih izmena koje menjaju semantiku, kao što su:

  • Zamena operatora: +-, */, itd.
  • Zamena dodele: +==, -==
  • Zamena konstanti: non-zero → 0, truefalse
  • Negacija/izmena uslova unutar if/petlji
  • Zakomentarisati cele linije (CR: Comment Replacement)
  • Zameniti liniju sa revert()
  • Zamene tipova podataka: npr. int128int64

Cilj: Ugasiti 100% generisanih mutanata, ili opravdati preživele jasnim obrazloženjem.

Running mutation testing with slither-mutate

Zahtevi: Slither v0.10.2+.

  • List options and mutators:
slither-mutate --help
slither-mutate --list-mutators
  • Foundry example (uhvati rezultate i sačuvaj kompletan log):
slither-mutate ./src/contracts --test-cmd="forge test" &> >(tee mutation.results)
  • Ako ne koristite Foundry, zamenite --test-cmd načinom na koji pokrećete testove (npr. npx hardhat test, npm test).

Artefakti i izveštaji se podrazumevano čuvaju u ./mutation_campaign. Neotkriveni (preživeli) mutanti se kopiraju tamo radi inspekcije.

Understanding the output

Redovi izveštaja izgledaju ovako:

INFO:Slither-Mutate:Mutating contract ContractName
INFO:Slither-Mutate:[CR] Line 123: 'original line' ==> '//original line' --> UNCAUGHT
  • Oznaka u uglastim zagradama je alias mutatora (npr. CR = Comment Replacement).
  • UNCAUGHT znači da su testovi prošli pod mutiranim ponašanjem → nedostaje asercija.

Reducing runtime: prioritize impactful mutants

Mutation kampanje mogu trajati satima ili danima. Saveti za smanjenje troškova:

  • Scope: Počnite sa kritičnim contracts/directories samo, pa proširite.
  • Prioritize mutators: Ako mutant visokog prioriteta na liniji preživi (npr. cela linija zakomentarisana), možete preskočiti varijante nižeg prioriteta za tu liniju.
  • Paralelizujte testove ako vaš runner to dozvoljava; cache-ujte dependencies/builds.
  • Fail-fast: zaustavite rano kada promena jasno demonstrira prazninu u asercijama.

Triage workflow for surviving mutants

  1. Inspect the mutated line and behavior.
  • Reproducirajte lokalno primenom mutirane linije i pokretanjem fokusiranog testa.
  1. Strengthen tests to assert state, not only return values.
  • Dodajte provere granica jednakosti (npr. test threshold ==).
  • Asertujte post-uslove: balances, total supply, efekte autorizacije i emitovane događaje.
  1. Replace overly permissive mocks with realistic behavior.
  • Osigurajte da mocks forsiraju transfers, failure paths i event emissions koji se dešavaju on-chain.
  1. Add invariants for fuzz tests.
  • Npr. očuvanje vrednosti, nenegativni balances, invarianti autorizacije, monotonic supply gde je primenjivo.
  1. Re-run slither-mutate until survivors are killed or explicitly justified.

Case study: revealing missing state assertions (Arkis protocol)

A mutation campaign during an audit of the Arkis DeFi protocol surfaced survivors like:

INFO:Slither-Mutate:[CR] Line 33: 'cmdsToExecute.last().value = _cmd.value' ==> '//cmdsToExecute.last().value = _cmd.value' --> UNCAUGHT

Komentarisanje dodele nije prekinulo testove, što ukazuje na nedostatak asercija stanja nakon izvršenja. Osnovni uzrok: kod je verovao korisnički kontrolisanom _cmd.value umesto da proveri stvarne transfere tokena. Napadač bi mogao desinhronizovati očekivane i stvarne transfere da isprazni sredstva. Rezultat: visok stepen rizika po solventnost protokola.

Smernica: tretirajte preostale mutante koji utiču na transfere vrednosti, računovodstvo ili kontrolu pristupa kao visokorizične dok se ne uklone.

Praktična kontrolna lista

  • Pokrenite ciljanu kampanju:
  • slither-mutate ./src/contracts --test-cmd="forge test"
  • Razvrstajte preostale mutante i napišite testove/invarijante koji bi pali pod izmenjenim ponašanjem.
  • Potvrdite bilanse, ukupnu ponudu, autorizacije i događaje.
  • Dodajte granične testove (==, overflows/underflows, zero-address, zero-amount, empty arrays).
  • Zamenite nerealne mock-ove; simulirajte režime otkaza.
  • Iterirajte dok svi mutanti nisu uklonjeni ili opravdani komentarima i obrazloženjem.

References

{{#include ../../banners/hacktricks-training.md}}