Mutation Testing for Solidity with Slither (slither-mutate)

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

Mutation testing์€ Solidity ์ฝ”๋“œ์— ์ž‘์€ ๋ณ€๊ฒฝ(mutants)์„ ์ฒด๊ณ„์ ์œผ๋กœ ๋„์ž…ํ•˜๊ณ  ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•จ์œผ๋กœ์จ โ€œํ…Œ์ŠคํŠธ๋ฅผ ํ…Œ์ŠคํŠธโ€œํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•˜๋ฉด ํ•ด๋‹น ๋ฎคํ„ดํŠธ๋Š” killed๋ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๊ฐ€ ์—ฌ์ „ํžˆ ํ†ต๊ณผํ•˜๋ฉด ๋ฎคํ„ดํŠธ๋Š” ์‚ด์•„๋‚จ์•„ line/branch coverage๋กœ๋Š” ํƒ์ง€ํ•  ์ˆ˜ ์—†๋Š” ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ์˜ ๋งน์ ์„ ๋“œ๋Ÿฌ๋ƒ…๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ์•„์ด๋””์–ด: Coverage๋Š” ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜์—ˆ์Œ์„ ๋ณด์—ฌ์ฃผ๊ณ ; mutation testing์€ ๋™์ž‘์ด ์‹ค์ œ๋กœ ๋‹จ์–ธ(asserted)๋˜์—ˆ๋Š”์ง€๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

Coverage๊ฐ€ ์˜ค๋„ํ•  ์ˆ˜ ์žˆ๋Š” ์ด์œ 

๋‹ค์Œ์˜ ๊ฐ„๋‹จํ•œ threshold ๊ฒ€์‚ฌ๋ฅผ ๊ณ ๋ คํ•ด๋ณด์ž:

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

Unit tests that only check a value below and a value above the threshold can reach 100% line/branch coverage while failing to assert the equality boundary (==). A refactor to deposit >= 2 ether would still pass such tests, silently breaking protocol logic.

Mutation testing exposes this gap by mutating the condition and verifying your tests fail.

Common Solidity mutation operators

Slitherโ€™s mutation engine applies many small, semantics-changing edits, such as:

  • Operator replacement: + โ†” -, * โ†” /, etc.
  • Assignment replacement: += โ†’ =, -= โ†’ =
  • Constant replacement: non-zero โ†’ 0, true โ†” false
  • Condition negation/replacement inside if/loops
  • Comment out whole lines (CR: Comment Replacement)
  • Replace a line with revert()
  • Data type swaps: e.g., int128 โ†’ int64

Goal: Kill 100% of generated mutants, or justify survivors with clear reasoning.

Running mutation testing with slither-mutate

Requirements: Slither v0.10.2+.

  • List options and mutators:
slither-mutate --help
slither-mutate --list-mutators
  • Foundry ์˜ˆ์ œ (๊ฒฐ๊ณผ ์บก์ฒ˜ ๋ฐ ์ „์ฒด ๋กœ๊ทธ ๋ณด๊ด€):
slither-mutate ./src/contracts --test-cmd="forge test" &> >(tee mutation.results)
  • Foundry๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, --test-cmd์„(๋ฅผ) ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๋ฐฉ๋ฒ•(์˜ˆ: npx hardhat test, npm test)์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ์„ธ์š”.

์•„ํ‹ฐํŒฉํŠธ์™€ ๋ฆฌํฌํŠธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ./mutation_campaign์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ๊ฒ€์ถœ๋˜์ง€ ์•Š์€(์ƒ์กดํ•œ) mutants๋Š” ๊ฒ€์‚ฌ๋ฅผ ์œ„ํ•ด ๊ทธ๊ณณ์— ๋ณต์‚ฌ๋ฉ๋‹ˆ๋‹ค.

์ถœ๋ ฅ ์ดํ•ดํ•˜๊ธฐ

๋ฆฌํฌํŠธ ๋ผ์ธ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

INFO:Slither-Mutate:Mutating contract ContractName
INFO:Slither-Mutate:[CR] Line 123: 'original line' ==> '//original line' --> UNCAUGHT
  • ๋Œ€๊ด„ํ˜ธ ์•ˆ์˜ ํƒœ๊ทธ๋Š” ๋ฎคํ…Œ์ดํ„ฐ ๋ณ„์นญ์ž…๋‹ˆ๋‹ค (์˜ˆ: CR = Comment Replacement).
  • UNCAUGHT๋Š” ๋ณ€ํ˜•๋œ ๋™์ž‘ ํ•˜์—์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ–ˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค โ†’ ๋ˆ„๋ฝ๋œ assertion.

์‹คํ–‰ ์‹œ๊ฐ„ ๋‹จ์ถ•: ์˜ํ–ฅ๋ ฅ ์žˆ๋Š” ๋ฎคํ„ดํŠธ ์šฐ์„ 

Mutation ์บ ํŽ˜์ธ์€ ์ˆ˜์‹œ๊ฐ„์—์„œ ์ˆ˜์ผ์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋น„์šฉ์„ ์ค„์ด๊ธฐ ์œ„ํ•œ ํŒ:

  • ๋ฒ”์œ„: ํ•ต์‹ฌ contracts/๋””๋ ‰ํ† ๋ฆฌ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•œ ๋’ค ํ™•์žฅํ•˜์„ธ์š”.
  • ๋ฎคํ…Œ์ดํ„ฐ ์šฐ์„ ์ˆœ์œ„ ์ง€์ •: ํ•œ ์ค„์—์„œ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ฎคํ„ดํŠธ๊ฐ€ ์ƒ์กดํ•˜๋Š” ๊ฒฝ์šฐ(์˜ˆ: ์ „์ฒด ์ค„ ์ฃผ์„ ์ฒ˜๋ฆฌ) ํ•ด๋‹น ์ค„์˜ ๋‚ฎ์€ ์šฐ์„ ์ˆœ ๋ณ€ํ˜•์€ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋Ÿฌ๋„ˆ์—์„œ ํ—ˆ์šฉํ•˜๋ฉด ํ…Œ์ŠคํŠธ๋ฅผ ๋ณ‘๋ ฌํ™”ํ•˜์„ธ์š”; ์˜์กด์„ฑ/๋นŒ๋“œ๋ฅผ ์บ์‹œํ•˜์„ธ์š”.
  • Fail-fast: ๋ณ€๊ฒฝ์ด ๋ช…๋ฐฑํžˆ assertion ๊ฒฉ์ฐจ๋ฅผ ๋ณด์—ฌ์ฃผ๋ฉด ์กฐ๊ธฐ์— ์ค‘๋‹จํ•˜์„ธ์š”.

์ƒ์กดํ•œ ๋ฎคํ„ดํŠธ์— ๋Œ€ํ•œ ํŠธ๋ฆฌ์•„์ง€ ์›Œํฌํ”Œ๋กœ์šฐ

  1. ๋ณ€๊ฒฝ๋œ ์ค„๊ณผ ๋™์ž‘์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.
  • ๋ณ€๊ฒฝ๋œ ์ค„์„ ์ ์šฉํ•˜๊ณ  ํŠน์ • ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ด ๋กœ์ปฌ์—์„œ ์žฌํ˜„ํ•˜์„ธ์š”.
  1. ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ•ํ™”ํ•˜์—ฌ ์ƒํƒœ(state)๋ฅผ ๋‹จ์–ธ(assert)ํ•˜์„ธ์š”, ๋ฐ˜ํ™˜๊ฐ’๋งŒ์ด ์•„๋‹ˆ๋ผ.
  • ๋™๋“ฑ์„ฑ ๊ฒฝ๊ณ„ ๊ฒ€์‚ฌ ์ถ”๊ฐ€(์˜ˆ: ์ž„๊ณ„๊ฐ’ == ํ…Œ์ŠคํŠธ).
  • ํ›„์กฐ๊ฑด์„ ๋‹จ์–ธ: ์ž”์•ก, ์ด ๊ณต๊ธ‰๋Ÿ‰(total supply), ๊ถŒํ•œ ์˜ํ–ฅ(authorization effects), ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ(emitted events).
  1. ์ง€๋‚˜์น˜๊ฒŒ ๊ด€๋Œ€ํ•œ mocks๋ฅผ ํ˜„์‹ค์ ์ธ ๋™์ž‘์œผ๋กœ ๊ต์ฒดํ•˜์„ธ์š”.
  • mocks๊ฐ€ ์ฒด์ธ ์ƒ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ „์†ก(transfers), ์‹คํŒจ ๊ฒฝ๋กœ(failure paths), ์ด๋ฒคํŠธ ๋ฐœ์ƒ(event emissions)์„ ๊ฐ•์ œํ•˜๋„๋ก ํ•˜์„ธ์š”.
  1. ํผ์ฆˆ(fuzz) ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๋ถˆ๋ณ€์„ฑ(invariants)์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.
  • ์˜ˆ: ๊ฐ€์น˜ ๋ณด์กด(conservation of value), ์Œ์ˆ˜ ๋ถˆ๊ฐ€ ์ž”์•ก(non-negative balances), ๊ถŒํ•œ ๋ถˆ๋ณ€์„ฑ, ์ ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ ๋‹จ์กฐ ์ฆ๊ฐ€ํ•˜๋Š” ๊ณต๊ธ‰๋Ÿ‰(monotonic supply).
  1. slither-mutate๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•˜์—ฌ ์ƒ์กด์ž๊ฐ€ ์ œ๊ฑฐ๋˜๊ฑฐ๋‚˜ ๋ช…ํ™•ํžˆ ์ •๋‹นํ™”๋  ๋•Œ๊นŒ์ง€ ๋ฐ˜๋ณตํ•˜์„ธ์š”.

์‚ฌ๋ก€ ์—ฐ๊ตฌ: ๋ˆ„๋ฝ๋œ ์ƒํƒœ ๋‹จ์–ธ ๋“œ๋Ÿฌ๋‚ด๊ธฐ (Arkis protocol)

Arkis DeFi protocol์˜ ๊ฐ์‚ฌ ์ค‘ ์ˆ˜ํ–‰๋œ ๋ฎคํ…Œ์ด์…˜ ์บ ํŽ˜์ธ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒ์กด์ž๊ฐ€ ๋‚˜ํƒ€๋‚ฌ์Šต๋‹ˆ๋‹ค:

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

ํ• ๋‹น๋ฌธ์„ ์ฃผ์„ ์ฒ˜๋ฆฌํ•ด๋„ ํ…Œ์ŠคํŠธ๊ฐ€ ๊นจ์ง€์ง€ ์•Š์•˜๋Š”๋ฐ, ์ด๋Š” ์‚ฌํ›„ ์ƒํƒœ ๊ฒ€์ฆ(post-state assertions)์ด ๋ˆ„๋ฝ๋˜์—ˆ์Œ์„ ์ฆ๋ช…ํ•œ๋‹ค. ๊ทผ๋ณธ ์›์ธ: ์ฝ”๋“œ๊ฐ€ ์‹ค์ œ ํ† ํฐ ์ „์†ก์„ ๊ฒ€์ฆํ•˜์ง€ ์•Š๊ณ  ์‚ฌ์šฉ์ž ์ œ์–ด _cmd.value๋ฅผ ์‹ ๋ขฐํ–ˆ๋‹ค. ๊ณต๊ฒฉ์ž๋Š” ๊ธฐ๋Œ€๋œ ์ „์†ก๊ณผ ์‹ค์ œ ์ „์†ก์„ ๋น„๋™๊ธฐํ™”์‹œ์ผœ ์ž๊ธˆ์„ ํƒˆ์ทจํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ฒฐ๊ณผ: ํ”„๋กœํ† ์ฝœ ์ง€๊ธ‰๋Šฅ๋ ฅ์— ๋Œ€ํ•œ ๋†’์€ ์‹ฌ๊ฐ๋„ ์œ„ํ—˜.

์ง€์นจ: ๊ฐ€์น˜ ์ „์†ก(value transfers), ํšŒ๊ณ„(accounting) ๋˜๋Š” ์ ‘๊ทผ ์ œ์–ด(access control)์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” survivors๋Š” ์ œ๊ฑฐ(killed)๋  ๋•Œ๊นŒ์ง€ ๊ณ ์œ„ํ—˜์œผ๋กœ ๊ฐ„์ฃผํ•˜๋ผ.

์‹ค๋ฌด ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • Run a targeted campaign:
  • slither-mutate ./src/contracts --test-cmd="forge test"
  • Triage survivors and write tests/invariants that would fail under the mutated behavior.
  • Assert balances, supply, authorizations, and events.
  • Add boundary tests (==, overflows/underflows, zero-address, zero-amount, empty arrays).
  • Replace unrealistic mocks; simulate failure modes.
  • Iterate until all mutants are killed or justified with comments and rationale.

References

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ