Мутаційне тестування для Solidity зі Slither (slither-mutate)

Reading time: 5 minutes

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks

Мутаційне тестування "перевіряє ваші тести", систематично вносячи невеликі зміни (мутанти) у ваш Solidity-код і повторно запускаючи набір тестів. Якщо тест не проходить — мутант вбито. Якщо тести все ще проходять — мутант виживає, виявляючи сліпу пляму у вашому наборі тестів, яку line/branch coverage не може виявити.

Ключова ідея: Coverage показує, що код був виконаний; мутаційне тестування показує, чи поведінка фактично перевірена.

Чому coverage може вводити в оману

Розглянемо цю просту перевірку порогу:

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

Модульні тести, які перевіряють лише значення нижче та вище порогу, можуть досягти 100% покриття рядків/гілок, водночас не перевіряючи граничну рівність (==). Рефакторинг до deposit >= 2 ether все ще пройде такі тести, тихо порушивши логіку протоколу.

Mutation testing виявляє цю прогалину — мутує умову й перевіряє, що тести не проходять.

Поширені оператори мутації для Solidity

Slither’s mutation engine applies many small, semantics-changing edits, such as:

  • Заміна операторів: +-, */, тощо.
  • Заміна присвоєння: +==, -==
  • Заміна констант: ненульове → 0, truefalse
  • Заперечення/заміна умов всередині if/циклів
  • Коментування цілих рядків (CR: Comment Replacement)
  • Замінити рядок на revert()
  • Заміна типів даних: наприклад, int128int64

Мета: знищити 100% згенерованих мутантів, або обґрунтувати тих, що вижили, зрозумілими аргументами.

Запуск mutation testing за допомогою slither-mutate

Вимоги: Slither v0.10.2+.

  • Перелічити опції та мутатори:
bash
slither-mutate --help
slither-mutate --list-mutators
  • Foundry приклад (захопити результати та зберегти повний log):
bash
slither-mutate ./src/contracts --test-cmd="forge test" &> >(tee mutation.results)
  • Якщо ви не використовуєте Foundry, замініть --test-cmd на те, як ви запускаєте тести (наприклад, npx hardhat test, npm test).

Артефакти та звіти зберігаються в ./mutation_campaign за замовчуванням. Невиловлені (вцілілі) мутанти копіюються туди для перевірки.

Розуміння виводу

Рядки звіту виглядають так:

text
INFO:Slither-Mutate:Mutating contract ContractName
INFO:Slither-Mutate:[CR] Line 123: 'original line' ==> '//original line' --> UNCAUGHT
  • Тег у дужках — псевдонім мутатора (наприклад, CR = Comment Replacement).
  • UNCAUGHT означає, що тести пройшли під мутованою поведінкою → відсутнє твердження.

Зменшення часу виконання: пріоритезуйте впливові мутанти

Мутаційні кампанії можуть тривати години або дні. Поради для зменшення витрат:

  • Обсяг: Почніть лише з критичних контрактів/директорій, потім розширюйте.
  • Пріоритезуйте мутатори: Якщо мутант з високим пріоритетом на рядку виживає (наприклад, увесь рядок закоментовано), ви можете пропустити варіанти з нижчим пріоритетом для цього рядка.
  • Паралелізуйте тести, якщо ваш runner дозволяє; кешуйте залежності/збірки.
  • Fail-fast: зупиняйтеся раніше, коли зміна чітко демонструє прогалину в твердженнях.

Робочий процес тріажу для вцілілих мутантів

  1. Перевірте мутований рядок та його поведінку.
  • Відтворіть локально, застосувавши мутований рядок і запустивши цілеспрямований тест.
  1. Посиліть тести так, щоб вони перевіряли стан, а не лише значення повернення.
  • Додайте перевірки меж рівності (наприклад, тест порогу ==).
  • Перевіряйте постумови: баланси, total supply, ефекти авторизації та згенеровані події.
  1. Замініть надмірно дозволяючі моки на реалістичну поведінку.
  • Переконайтеся, що моки примушують виконувати трансфери, шляхи відмови та емісію подій, які відбуваються on-chain.
  1. Додайте інваріанти для fuzz-тестів.
  • Наприклад: збереження вартості, невід'ємні баланси, інваріанти авторизації, монотонна загальна пропозиція там, де це застосовно.
  1. Повторно запустіть slither-mutate, поки вцілілі мутанти не будуть усунуті або явно виправдані.

Case study: revealing missing state assertions (Arkis protocol)

Під час аудиту протоколу Arkis DeFi мутаційна кампанія виявила такі вцілілі мутанти:

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

Коментування присвоєння не зламало тести, що підтверджує відсутність перевірок стану після виконання. Причина: код покладався на керований користувачем _cmd.value замість перевірки фактичних переказів токенів. Атакувальник міг розсинхронізувати очікувані та фактичні перекази, щоб вивести кошти. Наслідок: високий ризик для платоспроможності протоколу.

Рекомендація: вважайте виживші мутанти, які впливають на перекази вартості, облік або контроль доступу, високоризиковими, доки їх не знищено.

Практичний чекліст

  • Run a targeted campaign:
  • slither-mutate ./src/contracts --test-cmd="forge test"
  • Проаналізуйте виживші мутанти та напишіть тести/інваріанти, які проваляться при мутованій поведінці.
  • Перевірте баланси, загальну емісію, авторизації та події.
  • Додайте граничні тести (==, overflows/underflows, zero-address, zero-amount, empty arrays).
  • Замініть нереалістичні mocks; змоделюйте режими відмов.
  • Ітеруйте, поки всі мутанти не будуть знищені або виправдані коментарями та обґрунтуванням.

References

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks