Перейти до основного вмісту
Change page

Тестування смарт-контрактів

Публічні блокчейни, такі як Етеріум, є незмінними, що ускладнює зміну коду смарт-контракту після розгортання. Існують шаблони оновлення контрактів для виконання «віртуальних оновлень», але їх важко реалізувати, і вони вимагають соціального консенсусу. Крім того, оновлення може виправити помилку лише після її виявлення — якщо зловмисник виявить уразливість першим, ваш смарт-контракт опиниться під загрозою експлойту.

З цих причин тестування смарт-контрактів перед розгортанням у Головній мережі є мінімальною вимогою для безпеки. Існує багато методів тестування контрактів та оцінки правильності коду; що саме ви оберете, залежить від ваших потреб. Тим не менш, набір тестів, що складається з різних інструментів та підходів, ідеально підходить для виявлення як дрібних, так і серйозних недоліків безпеки в коді контракту.

Передумови

На цій сторінці пояснюється, як тестувати смарт-контракти перед розгортанням у мережі Етеріум. Передбачається, що ви знайомі зі смарт-контрактами.

Що таке тестування смарт-контрактів?

Тестування смарт-контрактів — це процес перевірки того, що код смарт-контракту працює належним чином. Тестування корисне для перевірки того, чи відповідає конкретний смарт-контракт вимогам щодо надійності, зручності використання та безпеки.

Хоча підходи різняться, більшість методів тестування вимагають виконання смарт-контракту з невеликою вибіркою даних, які він має обробляти. Якщо контракт дає правильні результати для вибіркових даних, вважається, що він функціонує належним чином. Більшість інструментів тестування надають ресурси для написання та виконання тестових випадків (opens in a new tab), щоб перевірити, чи відповідає виконання контракту очікуваним результатам.

Чому важливо тестувати смарт-контракти?

Оскільки смарт-контракти часто керують цінними фінансовими активами, незначні помилки в програмуванні можуть призвести і часто призводять до величезних втрат для користувачів (opens in a new tab). Однак ретельне тестування може допомогти вам на ранніх стадіях виявити дефекти та проблеми в коді смарт-контракту і виправити їх перед запуском у Головній мережі.

Хоча контракт можна оновити у разі виявлення помилки, оновлення є складними і можуть призвести до помилок (opens in a new tab) при неправильному виконанні. Оновлення контракту також зводить нанівець принцип незмінності та обтяжує користувачів додатковими припущеннями довіри. Навпаки, комплексний план тестування вашого контракту знижує ризики безпеки смарт-контракту та зменшує потребу у виконанні складних оновлень логіки після розгортання.

Методи тестування смарт-контрактів

Методи тестування смарт-контрактів Етеріуму поділяються на дві широкі категорії: автоматизоване тестування та ручне тестування. Автоматизоване та ручне тестування пропонують унікальні переваги та компроміси, але ви можете поєднати їх, щоб створити надійний план аналізу ваших контрактів.

Автоматизоване тестування

Автоматизоване тестування використовує інструменти, які автоматично перевіряють код смарт-контракту на наявність помилок під час виконання. Перевага автоматизованого тестування полягає у використанні скриптів (opens in a new tab) для керування оцінкою функціональності контракту. Скриптові тести можна запланувати для багаторазового запуску з мінімальним втручанням людини, що робить автоматизоване тестування ефективнішим за ручні підходи до тестування.

Автоматизоване тестування особливо корисне, коли тести повторюються і забирають багато часу; їх важко виконати вручну; вони схильні до людських помилок; або передбачають оцінку критично важливих функцій контракту. Але інструменти автоматизованого тестування можуть мати недоліки — вони можуть пропускати певні помилки та видавати багато хибнопозитивних результатів (opens in a new tab). Тому поєднання автоматизованого тестування з ручним тестуванням смарт-контрактів є ідеальним.

Ручне тестування

Ручне тестування здійснюється за допомогою людини і передбачає виконання кожного тестового випадку у вашому наборі тестів один за одним під час аналізу правильності смарт-контракту. Це відрізняється від автоматизованого тестування, де ви можете одночасно запускати кілька ізольованих тестів для контракту та отримувати звіт, що показує всі невдалі та успішні тести.

Ручне тестування може виконуватися однією людиною за написаним планом тестування, який охоплює різні сценарії тестування. Ви також можете залучити кількох осіб або групи до взаємодії зі смарт-контрактом протягом певного періоду в рамках ручного тестування. Тестувальники порівнюватимуть фактичну поведінку контракту з очікуваною, позначаючи будь-яку різницю як помилку.

Ефективне ручне тестування вимагає значних ресурсів (навичок, часу, грошей та зусиль), і можливо — через людський фактор — пропустити певні помилки під час виконання тестів. Але ручне тестування також може бути корисним — наприклад, тестувальник (наприклад, аудитор) може використовувати інтуїцію для виявлення крайніх випадків, які інструмент автоматизованого тестування пропустив би.

Автоматизоване тестування смарт-контрактів

Модульне тестування

Модульне тестування оцінює функції контракту окремо та перевіряє, чи правильно працює кожен компонент. Хороші модульні тести мають бути простими, швидко виконуватися та давати чітке уявлення про те, що пішло не так у разі невдачі тестів.

Модульні тести корисні для перевірки того, що функції повертають очікувані значення, а сховище контракту належним чином оновлюється після виконання функції. Крім того, запуск модульних тестів після внесення змін до кодової бази контракту гарантує, що додавання нової логіки не призведе до помилок. Нижче наведено деякі рекомендації щодо проведення ефективних модульних тестів:

Рекомендації щодо модульного тестування смарт-контрактів

1. Зрозумійте бізнес-логіку та робочий процес вашого контракту

Перед написанням модульних тестів корисно знати, які функціональні можливості пропонує смарт-контракт і як користувачі отримуватимуть доступ до цих функцій та використовуватимуть їх. Це особливо корисно для запуску тестів щасливого шляху (happy path) (opens in a new tab), які визначають, чи повертають функції в контракті правильний результат для дійсних введених користувачем даних. Ми пояснимо цю концепцію на (скороченому) прикладі аукціонного контракту (opens in a new tab)

Це простий аукціонний контракт, призначений для отримання ставок під час періоду торгів. Якщо highestBid збільшується, попередній учасник з найвищою ставкою отримує свої гроші; після закінчення періоду торгів beneficiary викликає контракт, щоб отримати свої гроші.

Модульні тести для такого контракту охоплюватимуть різні функції, які користувач може викликати під час взаємодії з контрактом. Прикладом може бути модульний тест, який перевіряє, чи може користувач зробити ставку під час аукціону (тобто виклики bid() є успішними), або тест, який перевіряє, чи може користувач зробити вищу ставку, ніж поточна highestBid.

Розуміння робочого процесу контракту також допомагає у написанні модульних тестів, які перевіряють, чи відповідає виконання вимогам. Наприклад, аукціонний контракт визначає, що користувачі не можуть робити ставки, коли аукціон закінчився (тобто коли auctionEndTime менше за block.timestamp). Таким чином, розробник може запустити модульний тест, який перевіряє, чи є виклики функції bid() успішними або невдалими, коли аукціон закінчився (тобто коли auctionEndTime > block.timestamp).

2. Оцініть усі припущення, пов'язані з виконанням контракту

Важливо документувати будь-які припущення щодо виконання контракту та писати модульні тести для перевірки обґрунтованості цих припущень. Окрім захисту від несподіваного виконання, тестування тверджень змушує вас задуматися про операції, які можуть порушити модель безпеки смарт-контракту. Корисна порада — вийти за рамки «тестів щасливого користувача» і написати негативні тести, які перевіряють, чи завершується функція помилкою при неправильних вхідних даних.

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

  • Користувачі не можуть робити ставки, коли аукціон закінчився або ще не розпочався.

  • Аукціонний контракт скасовується, якщо ставка нижча за прийнятний поріг.

  • Користувачам, які не виграли ставку, повертаються їхні кошти.

Примітка: Інший спосіб тестування припущень — написати тести, які запускають модифікатори функцій (opens in a new tab) у контракті, особливо оператори require, assert та if…else.

3. Вимірюйте покриття коду

Покриття коду (opens in a new tab) — це метрика тестування, яка відстежує кількість гілок, рядків та операторів у вашому коді, виконаних під час тестів. Тести повинні мати хороше покриття коду, щоб мінімізувати ризик неперевірених вразливостей. Без достатнього покриття ви можете помилково припустити, що ваш контракт безпечний, оскільки всі тести пройдено, тоді як вразливості все ще існують у неперевірених шляхах коду. Однак фіксація високого покриття коду дає впевненість у тому, що всі оператори/функції в смарт-контракті були достатньо перевірені на правильність.

4. Використовуйте добре розроблені фреймворки для тестування

Якість інструментів, що використовуються для запуску модульних тестів для ваших смарт-контрактів, має вирішальне значення. Ідеальний фреймворк для тестування — це той, який регулярно підтримується; надає корисні функції (наприклад, можливості журналювання та звітності); і який широко використовувався та перевірявся іншими розробниками.

Фреймворки для модульного тестування смарт-контрактів Solidity доступні різними мовами (переважно JavaScript, Python та Rust). Перегляньте деякі з наведених нижче посібників, щоб дізнатися, як почати запускати модульні тести за допомогою різних фреймворків для тестування:

Інтеграційне тестування

У той час як модульне тестування налагоджує функції контракту ізольовано, інтеграційні тести оцінюють компоненти смарт-контракту в цілому. Інтеграційне тестування може виявити проблеми, що виникають через міжконтрактні виклики або взаємодію між різними функціями в одному смарт-контракті. Наприклад, інтеграційні тести можуть допомогти перевірити, чи правильно працюють такі речі, як успадкування (opens in a new tab) та впровадження залежностей.

Інтеграційне тестування корисне, якщо ваш контракт використовує модульну архітектуру або взаємодіє з іншими ончейн-контрактами під час виконання. Один із способів запуску інтеграційних тестів — це зробити на певній висоті (використовуючи такий інструмент, як Forge (opens in a new tab) або Hardhat (opens in a new tab)) та змоделювати взаємодію між вашим контрактом і розгорнутими контрактами.

Форк блокчейну поводитиметься подібно до Головної мережі і матиме акаунти з відповідними станами та балансами. Але він діє лише як ізольоване локальне середовище розробки, що означає, що вам не знадобиться справжній ETH для транзакцій, наприклад, і ваші зміни не вплинуть на реальний протокол Етеріуму.

Тестування на основі властивостей

Тестування на основі властивостей — це процес перевірки того, що смарт-контракт задовольняє певну визначену властивість. Властивості стверджують факти про поведінку контракту, які, як очікується, залишатимуться істинними в різних сценаріях — прикладом властивості смарт-контракту може бути «Арифметичні операції в контракті ніколи не призводять до переповнення або антипереповнення».

Статичний аналіз та динамічний аналіз — це два поширені методи виконання тестування на основі властивостей, і обидва можуть перевірити, чи задовольняє код програми (у цьому випадку смарт-контракту) певну заздалегідь визначену властивість. Деякі інструменти тестування на основі властивостей постачаються із заздалегідь визначеними правилами щодо очікуваних властивостей контракту та перевіряють код на відповідність цим правилам, тоді як інші дозволяють створювати власні властивості для смарт-контракту.

Статичний аналіз

Статичний аналізатор приймає на вхід вихідний код смарт-контракту і видає результати, які вказують, чи задовольняє контракт властивість, чи ні. На відміну від динамічного аналізу, статичний аналіз не передбачає виконання контракту для його перевірки на правильність. Натомість статичний аналіз міркує про всі можливі шляхи, якими може піти смарт-контракт під час виконання (тобто шляхом вивчення структури вихідного коду, щоб визначити, що це означатиме для роботи контракту під час виконання).

Лінтинг (opens in a new tab) та статичне тестування (opens in a new tab) є поширеними методами проведення статичного аналізу контрактів. Обидва вимагають аналізу низькорівневих представлень виконання контракту, таких як абстрактні синтаксичні дерева (opens in a new tab) та графи потоку керування (opens in a new tab), які виводить компілятор.

У більшості випадків статичний аналіз корисний для виявлення проблем безпеки, таких як використання небезпечних конструкцій, синтаксичні помилки або порушення стандартів кодування в коді контракту. Однак відомо, що статичні аналізатори, як правило, ненадійні у виявленні глибших вразливостей і можуть видавати надмірну кількість хибнопозитивних результатів.

Динамічний аналіз

Динамічний аналіз генерує символьні вхідні дані (наприклад, у символьному виконанні (opens in a new tab)) або конкретні вхідні дані (наприклад, у фазингу (opens in a new tab)) для функцій смарт-контракту, щоб перевірити, чи порушує будь-який слід виконання певні властивості. Ця форма тестування на основі властивостей відрізняється від модульних тестів тим, що тестові випадки охоплюють кілька сценаріїв, а програма обробляє генерацію тестових випадків.

Фазинг (opens in a new tab) є прикладом методу динамічного аналізу для перевірки довільних властивостей у смарт-контрактах. Фазер викликає функції в цільовому контракті з випадковими або неправильно сформованими варіаціями визначеного вхідного значення. Якщо смарт-контракт переходить у стан помилки (наприклад, коли твердження не виконується), проблема позначається, а вхідні дані, які спрямовують виконання до вразливого шляху, виводяться у звіті.

Фазинг корисний для оцінки механізму перевірки вхідних даних смарт-контракту, оскільки неправильна обробка несподіваних вхідних даних може призвести до ненавмисного виконання та спричинити небезпечні наслідки. Ця форма тестування на основі властивостей може бути ідеальною з багатьох причин:

  1. Написання тестових випадків для охоплення багатьох сценаріїв є складним. Тест властивостей вимагає лише визначення поведінки та діапазону даних для тестування цієї поведінки — програма автоматично генерує тестові випадки на основі визначеної властивості.

  2. Ваш набір тестів може недостатньо охоплювати всі можливі шляхи в програмі. Навіть при 100% покритті можна пропустити крайні випадки.

  3. Модульні тести доводять, що контракт виконується правильно для вибіркових даних, але чи виконується контракт правильно для вхідних даних поза вибіркою, залишається невідомим. Тести властивостей виконують цільовий контракт з кількома варіаціями заданого вхідного значення, щоб знайти сліди виконання, які викликають збої тверджень. Таким чином, тест властивостей надає більше гарантій того, що контракт виконується правильно для широкого класу вхідних даних.

Рекомендації щодо проведення тестування на основі властивостей для смарт-контрактів

Запуск тестування на основі властивостей зазвичай починається з визначення властивості (наприклад, відсутності переповнення цілих чисел (opens in a new tab)) або набору властивостей, які ви хочете перевірити в смарт-контракті. Вам також може знадобитися визначити діапазон значень, у межах якого програма може генерувати дані для вхідних даних транзакції під час написання тестів властивостей.

Після належного налаштування інструмент тестування властивостей виконуватиме функції вашого смарт-контракту з випадково згенерованими вхідними даними. Якщо є будь-які порушення тверджень, ви повинні отримати звіт з конкретними вхідними даними, які порушують оцінювану властивість. Перегляньте деякі з наведених нижче посібників, щоб розпочати тестування на основі властивостей за допомогою різних інструментів:

Ручне тестування смарт-контрактів

Ручне тестування смарт-контрактів часто відбувається на пізніших етапах циклу розробки після запуску автоматизованих тестів. Ця форма тестування оцінює смарт-контракт як один повністю інтегрований продукт, щоб перевірити, чи працює він так, як зазначено в технічних вимогах.

Тестування контрактів на локальному блокчейні

Хоча автоматизоване тестування, виконане в локальному середовищі розробки, може надати корисну інформацію для налагодження, ви захочете дізнатися, як ваш смарт-контракт поводиться у виробничому середовищі. Однак розгортання в головному ланцюзі Етеріуму вимагає сплати комісій за газ — не кажучи вже про те, що ви або ваші користувачі можете втратити реальні гроші, якщо у вашому смарт-контракті все ще є помилки.

Тестування вашого контракту на локальному блокчейні (також відомому як мережа розробки) є рекомендованою альтернативою тестуванню в Головній мережі. Локальний блокчейн — це копія блокчейну Етеріуму, що працює локально на вашому комп'ютері і моделює поведінку рівня виконання Етеріуму. Таким чином, ви можете програмувати транзакції для взаємодії з контрактом без значних накладних витрат.

Запуск контрактів на локальному блокчейні може бути корисним як форма ручного інтеграційного тестування. Смарт-контракти є висококомпонованими, що дозволяє вам інтегруватися з існуючими протоколами — але вам все одно потрібно буде переконатися, що такі складні ончейн-взаємодії дають правильні результати.

Більше про мережі розробки.

Тестування контрактів у тестових мережах

Тестова мережа працює точно так само, як головна мережа Ethereum, за винятком того, що вона використовує етер (ETH), який не має реальної цінності. Розгортання вашого контракту в тестовій мережі означає, що будь-хто може взаємодіяти з ним (наприклад, через фронтенд децентралізованого застосунку (dapp)) без ризику для коштів.

Ця форма ручного тестування корисна для оцінки наскрізного потоку вашого застосунку з точки зору користувача. Тут бета-тестувальники також можуть виконувати пробні запуски та повідомляти про будь-які проблеми з бізнес-логікою контракту та загальною функціональністю.

Розгортання в тестовій мережі після тестування на локальному блокчейні є ідеальним, оскільки перша ближча до поведінки віртуальної машини Етеріуму. Тому для багатьох нативних проєктів Етеріуму є звичайною практикою розгортати dapp у тестових мережах для оцінки роботи смарт-контракту в реальних умовах.

Більше про тестові мережі Етеріуму.

Тестування та формальна верифікація

Хоча тестування допомагає підтвердити, що контракт повертає очікувані результати для деяких вхідних даних, воно не може остаточно довести те саме для вхідних даних, які не використовувалися під час тестів. Тому тестування смарт-контракту не може гарантувати «функціональну правильність» (тобто воно не може показати, що програма поводиться належним чином для всіх наборів вхідних значень).

Формальна верифікація — це підхід до оцінки правильності програмного забезпечення шляхом перевірки того, чи відповідає формальна модель програми формальній специфікації. Формальна модель — це абстрактне математичне представлення програми, тоді як формальна специфікація визначає властивості програми (тобто логічні твердження про виконання програми).

Оскільки властивості записуються в математичних термінах, стає можливим перевірити, чи задовольняє формальна (математична) модель системи специфікацію, використовуючи логічні правила виведення. Таким чином, кажуть, що інструменти формальної верифікації створюють «математичний доказ» правильності системи.

На відміну від тестування, формальна верифікація може використовуватися для перевірки того, що виконання смарт-контракту задовольняє формальну специфікацію для всіх виконань (тобто він не має помилок) без необхідності виконувати його з вибірковими даними. Це не тільки скорочує час, витрачений на запуск десятків модульних тестів, але й є більш ефективним у виявленні прихованих вразливостей. З огляду на це, методи формальної верифікації знаходяться в спектрі залежно від складності їх реалізації та корисності.

Більше про формальну верифікацію смарт-контрактів.

Тестування, аудити та програми винагород за знайдені помилки (bug bounties)

Як уже згадувалося, ретельне тестування рідко може гарантувати відсутність помилок у контракті; підходи формальної верифікації можуть надати сильніші гарантії правильності, але наразі вони складні у використанні та вимагають значних витрат.

Тим не менш, ви можете ще більше збільшити ймовірність виявлення вразливостей контракту, отримавши незалежну перевірку коду. Аудити смарт-контрактів (opens in a new tab) та програми винагород за знайдені помилки (opens in a new tab) — це два способи залучити інших до аналізу ваших контрактів.

Аудити проводяться аудиторами, які мають досвід виявлення випадків недоліків безпеки та поганих практик розробки в смарт-контрактах. Аудит зазвичай включає тестування (і, можливо, формальну верифікацію), а також ручну перевірку всієї кодової бази.

Навпаки, програма винагород за знайдені помилки зазвичай передбачає пропозицію фінансової винагороди особі (яку часто називають білими хакерами (opens in a new tab)), яка виявляє вразливість у смарт-контракті та розкриває її розробникам. Програми винагород за знайдені помилки схожі на аудити, оскільки вони передбачають прохання до інших допомогти знайти дефекти в смарт-контрактах.

Головна відмінність полягає в тому, що програми винагород за знайдені помилки відкриті для ширшої спільноти розробників/хакерів і залучають широкий клас етичних хакерів та незалежних фахівців з безпеки з унікальними навичками та досвідом. Це може бути перевагою над аудитами смарт-контрактів, які в основному покладаються на команди, що можуть мати обмежений або вузький досвід.

Інструменти та бібліотеки для тестування

Інструменти для модульного тестування

  • solidity-coverage (opens in a new tab)Інструмент покриття коду для смарт-контрактів, написаних на Solidity.

  • Waffle (opens in a new tab)Фреймворк для розширеної розробки та тестування смарт-контрактів (на основі Ethers.js).

  • Remix Tests (opens in a new tab)Інструмент для тестування смарт-контрактів Solidity. Працює під плагіном Remix IDE «Solidity Unit Testing», який використовується для написання та запуску тестових випадків для контракту.

  • OpenZeppelin Test Helpers (opens in a new tab)Бібліотека тверджень для тестування смарт-контрактів Етеріуму. Переконайтеся, що ваші контракти поводяться так, як очікувалося!

  • Фреймворк модульного тестування Brownie (opens in a new tab)Brownie використовує Pytest, багатофункціональний фреймворк для тестування, який дозволяє писати невеликі тести з мінімальним кодом, добре масштабується для великих проєктів і має широкі можливості розширення.

  • Foundry Tests (opens in a new tab)Foundry пропонує Forge, швидкий і гнучкий фреймворк для тестування Етеріуму, здатний виконувати прості модульні тести, перевірки оптимізації газу та фазинг контрактів.

  • Hardhat Tests (opens in a new tab)Фреймворк для тестування смарт-контрактів на основі Ethers.js, Mocha та Chai.

  • ApeWorx (opens in a new tab)Фреймворк для розробки та тестування смарт-контрактів на базі Python, орієнтований на віртуальну машину Етеріуму.

  • Wake (opens in a new tab)Фреймворк на базі Python для модульного тестування та фазингу з потужними можливостями налагодження та підтримкою кросчейн-тестування, що використовує pytest та Anvil для найкращого користувацького досвіду та продуктивності.

Інструменти для тестування на основі властивостей

Інструменти статичного аналізу

  • Слізер (opens in a new tab)Фреймворк статичного аналізу Solidity на базі Python для пошуку вразливостей, покращення розуміння коду та написання власних аналізів для смарт-контрактів.

  • Ethlint (opens in a new tab)Лінтер для забезпечення дотримання найкращих практик стилю та безпеки для мови програмування смарт-контрактів Solidity.

  • Cyfrin Aderyn (opens in a new tab)Статичний аналізатор на базі Rust, спеціально розроблений для безпеки та розробки смарт-контрактів Web3.

  • Wake (opens in a new tab)Фреймворк статичного аналізу на базі Python з детекторами вразливостей та якості коду, принтерами для вилучення корисної інформації з коду та підтримкою написання власних підмодулів.

  • Slippy (opens in a new tab)Простий і потужний лінтер для Solidity.

Інструменти динамічного аналізу

  • Ехідна (opens in a new tab)Швидкий фазер контрактів для виявлення вразливостей у смарт-контрактах за допомогою тестування на основі властивостей.

  • Diligence Fuzzing (opens in a new tab)Автоматизований інструмент фазингу, корисний для виявлення порушень властивостей у коді смарт-контракту.

  • Мантікора (opens in a new tab)Фреймворк динамічного символьного виконання для аналізу байт-коду EVM.

  • Mythril (opens in a new tab)Інструмент оцінки байт-коду EVM для виявлення вразливостей контракту за допомогою аналізу забруднення (taint analysis), конколічного аналізу та перевірки потоку керування.

  • Diligence Scribble (opens in a new tab)Scribble — це мова специфікацій та інструмент верифікації під час виконання, який дозволяє анотувати смарт-контракти властивостями, що дозволяють автоматично тестувати контракти за допомогою таких інструментів, як Diligence Fuzzing або MythX.

Додаткова література

Посібники: Тестування смарт-контрактів на Етеріумі