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

Безпека смарт-контрактів

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

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

Хоча цифри різняться, за оцінками, загальна сума цінностей, вкрадених або втрачених через дефекти безпеки в смарт-контрактах, легко перевищує 1 мільярд доларів. Це включає такі гучні інциденти, як злам DAO (відкривається в новій вкладці) (вкрадено 3,6 млн ETH, що за сьогоднішніми цінами становить понад 1 млрд доларів), злам гаманця з мультипідписом Parity (відкривається в новій вкладці) (хакери вкрали 30 млн доларів) та проблема замороженого гаманця Parity (відкривається в новій вкладці) (понад 300 млн доларів у ETH заблоковано назавжди).

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

Передумови

Переконайтеся, що ви ознайомлені з основами розробки смарт-контрактів, перш ніж переходити до питань безпеки.

Рекомендації щодо створення безпечних смарт-контрактів Етеріуму

1. Розробіть належний контроль доступу

У смарт-контрактах функції, позначені як public або external, можуть бути викликані будь-якими зовнішньо керованими акаунтами (EOA) або акаунтами контрактів. Встановлення публічної видимості для функцій є необхідним, якщо ви хочете, щоб інші взаємодіяли з вашим контрактом. Однак функції, позначені як private, можуть бути викликані лише функціями всередині смарт-контракту, а не зовнішніми акаунтами. Надання кожному учаснику мережі доступу до функцій контракту може спричинити проблеми, особливо якщо це означає, що будь-хто може виконувати конфіденційні операції (наприклад, карбування нових токенів).

Щоб запобігти несанкціонованому використанню функцій смарт-контракту, необхідно реалізувати безпечний контроль доступу. Механізми контролю доступу обмежують можливість використання певних функцій у смарт-контракті для затверджених суб'єктів, таких як акаунти, відповідальні за управління контрактом. Шаблон Ownable та контроль на основі ролей — це два шаблони, корисні для реалізації контролю доступу в смарт-контрактах:

Шаблон Ownable

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

Контроль доступу на основі ролей

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

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

Використання гаманців з мультипідписом

Іншим підходом до реалізації безпечного контролю доступу є використання акаунта з мультипідписом для управління контрактом. На відміну від звичайного зовнішньо керованого акаунта (EOA), акаунти з мультипідписом належать кільком суб'єктам і вимагають підписів від мінімальної кількості акаунтів — скажімо, 3 з 5 — для виконання транзакцій.

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

2. Використовуйте оператори require(), assert() та revert() для захисту операцій контракту

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

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

assert(): assert() використовується для виявлення внутрішніх помилок і перевірки порушень «інваріантів» у вашому коді. Інваріант — це логічне твердження про стан контракту, яке має залишатися істинним для всіх виконань функцій. Прикладом інваріанта є максимальна загальна пропозиція або баланс контракту токена. Використання assert() гарантує, що ваш контракт ніколи не досягне вразливого стану, а якщо це станеться, усі зміни змінних стану будуть скасовані.

revert(): revert() можна використовувати в операторі if-else, який викликає виняток, якщо необхідна умова не виконується. Наведений нижче зразок контракту використовує revert() для захисту виконання функцій:

3. Тестуйте смарт-контракти та перевіряйте правильність коду

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

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

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

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

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

4. Зверніться за незалежною перевіркою вашого коду

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

Аудити

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

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

Програми винагород за виявлення помилок (Bug bounties)

Створення програми винагород за виявлення помилок (bug bounty) — це ще один підхід до реалізації зовнішніх перевірок коду. Bug bounty — це фінансова винагорода, яка надається особам (зазвичай «білим» хакерам), які виявляють вразливості в програмі.

При правильному використанні програми bug bounty дають членам хакерської спільноти стимул перевіряти ваш код на наявність критичних недоліків. Реальний приклад — «помилка нескінченних грошей», яка дозволила б зловмиснику створити необмежену кількість етеру в Optimism (відкривається в новій вкладці), протоколі рівня 2 (l2), що працює на Етеріумі. На щастя, «білий» хакер виявив недолік (відкривається в новій вкладці) і повідомив команду, отримавши при цьому велику виплату (відкривається в новій вкладці).

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

5. Дотримуйтесь найкращих практик під час розробки смарт-контрактів

Існування аудитів та програм bug bounty не звільняє вас від відповідальності за написання високоякісного коду. Хороша безпека смарт-контрактів починається з дотримання належних процесів проєктування та розробки:

  • Зберігайте весь код у системі контролю версій, такій як git

  • Вносьте всі зміни до коду через pull-запити

  • Переконайтеся, що pull-запити мають принаймні одного незалежного рецензента — якщо ви працюєте над проєктом самостійно, подумайте про те, щоб знайти інших розробників і обмінюватися перевірками коду

  • Використовуйте середовище розробки для тестування, компіляції та розгортання смарт-контрактів

  • Пропускайте свій код через базові інструменти аналізу коду, такі як Cyfrin Aderyn (відкривається в новій вкладці), Mythril та Слізер. В ідеалі ви повинні робити це перед злиттям кожного pull-запиту та порівнювати відмінності у виводі

  • Переконайтеся, що ваш код компілюється без помилок, а компілятор Solidity не видає жодних попереджень

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

6. Реалізуйте надійні плани аварійного відновлення

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

Оновлення контрактів

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

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

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

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

Більше про оновлення контрактів.

Аварійні зупинки

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

Радикальним варіантом є реалізація функції «аварійної зупинки», яка блокує виклики вразливих функцій у контракті. Аварійні зупинки зазвичай складаються з таких компонентів:

  1. Глобальна логічна змінна, що вказує, чи перебуває смарт-контракт у зупиненому стані. Ця змінна встановлюється на false під час налаштування контракту, але зміниться на true після зупинки контракту.

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

  3. Суб'єкт, який має доступ до функції аварійної зупинки, що встановлює логічну змінну на true. Щоб запобігти зловмисним діям, виклики цієї функції можна обмежити довіреною адресою (наприклад, власником контракту).

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

Цей приклад показує основні особливості аварійних зупинок:

  • isStopped — це логічна змінна, яка має значення false на початку та true, коли контракт переходить в аварійний режим.

  • Модифікатори функцій onlyWhenStopped та stoppedInEmergency перевіряють змінну isStopped. stoppedInEmergency використовується для контролю функцій, які мають бути недоступними, коли контракт є вразливим (наприклад, deposit()). Виклики цих функцій будуть просто скасовані.

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

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

Моніторинг подій

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

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

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

7. Розробіть безпечні системи управління

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

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

Одним із способів запобігання проблемам, пов'язаним з ончейн-управлінням, є використання часового блокування (timelock) (відкривається в новій вкладці). Часове блокування запобігає виконанню смарт-контрактом певних дій доти, доки не мине певний проміжок часу. Інші стратегії включають призначення «ваги голосу» кожному токену на основі того, як довго він був заблокований, або вимірювання сили голосу адреси в історичний період (наприклад, 2-3 блоки в минулому) замість поточного блоку. Обидва методи зменшують можливість швидкого накопичення сили голосу для впливу на ончейн-голосування.

Більше про розробку безпечних систем управління (відкривається в новій вкладці), різні механізми голосування в децентралізованих автономних організаціях (DAO) (відкривається в новій вкладці) та поширені вектори атак на DAO з використанням DeFi (відкривається в новій вкладці) за наданими посиланнями.

8. Зведіть складність коду до мінімуму

Традиційні розробники програмного забезпечення знайомі з принципом KISS («keep it simple, stupid» — роби це простіше), який радить не вводити непотрібну складність у дизайн програмного забезпечення. Це випливає з давньої думки про те, що «складні системи виходять з ладу складними способами» і більш схильні до дорогих помилок.

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

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

9. Захищайтеся від поширених вразливостей смарт-контрактів

Повторний вхід

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

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

Розглянемо простий смарт-контракт («Victim»), який дозволяє будь-кому вносити та виводити етер:

Цей контракт відкриває функцію withdraw(), щоб дозволити користувачам виводити ETH, раніше внесені в контракт. Під час обробки виведення контракт виконує такі операції:

  1. Перевіряє баланс ETH користувача
  2. Надсилає кошти на адресу, що викликає
  3. Скидає їхній баланс до 0, запобігаючи додатковим виведенням від користувача

Функція withdraw() у контракті Victim дотримується шаблону «перевірки-взаємодії-ефекти» (checks-interactions-effects). Вона перевіряє, чи виконуються умови, необхідні для виконання (тобто користувач має позитивний баланс ETH), і виконує взаємодію, надсилаючи ETH на адресу абонента, перш ніж застосувати ефекти транзакції (тобто зменшення балансу користувача).

Якщо withdraw() викликається із зовнішньо керованого акаунта (EOA), функція виконується належним чином: msg.sender.call.value() надсилає ETH абоненту. Однак, якщо msg.sender є акаунтом смарт-контракту, який викликає withdraw(), надсилання коштів за допомогою msg.sender.call.value() також запустить виконання коду, що зберігається за цією адресою.

Уявіть, що це код, розгорнутий за адресою контракту:

Цей контракт призначений для виконання трьох речей:

  1. Прийняти депозит з іншого акаунта (ймовірно, EOA зловмисника)
  2. Внести 1 ETH у контракт Victim
  3. Вивести 1 ETH, що зберігається в смарт-контракті

Тут немає нічого поганого, за винятком того, що Attacker має іншу функцію, яка знову викликає withdraw() у Victim, якщо газ, що залишився від вхідного msg.sender.call.value, перевищує 40 000. Це дає Attacker можливість повторно увійти в Victim і вивести більше коштів до завершення першого виклику withdraw. Цикл виглядає так:

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

Як запобігти атакам повторного входу

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

Шаблон перевірки-ефекти-взаємодії використовується в переглянутій версії контракту Victim, показаній нижче:

contract NoLongerAVictim {
    function withdraw() external {
        uint256 amount = balances[msg.sender];
        balances[msg.sender] = 0;
        (bool success, ) = msg.sender.call.value(amount)("");
        require(success);
    }
}

Цей контракт виконує перевірку балансу користувача, застосовує ефекти функції withdraw() (шляхом скидання балансу користувача до 0) і переходить до виконання взаємодії (надсилання ETH на адресу користувача). Це гарантує, що контракт оновлює своє сховище перед зовнішнім викликом, усуваючи умову повторного входу, яка уможливила першу атаку. Контракт Attacker все ще міг би викликати назад у NoLongerAVictim, але оскільки balances[msg.sender] було встановлено на 0, додаткові виведення викличуть помилку.

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

Ви також можете використовувати систему платежів за запитом (pull payments) (відкривається в новій вкладці), яка вимагає від користувачів виводити кошти зі смарт-контрактів, замість системи «платежів за ініціативою» (push payments), яка надсилає кошти на акаунти. Це усуває можливість ненавмисного запуску коду за невідомими адресами (а також може запобігти певним атакам типу «відмова в обслуговуванні»).

Недоповнення та переповнення цілих чисел

Переповнення цілого числа відбувається, коли результати арифметичної операції виходять за межі допустимого діапазону значень, змушуючи його «перекотитися» до найнижчого можливого значення. Наприклад, uint8 може зберігати значення лише до 2^8-1=255. Арифметичні операції, які призводять до значень, вищих за 255, переповняться і скинуть uint до 0, подібно до того, як одометр на автомобілі скидається до 0, коли досягає максимального пробігу (999999).

Недоповнення цілих чисел трапляються з подібних причин: результати арифметичної операції падають нижче допустимого діапазону. Скажімо, ви спробували зменшити 0 у uint8, результат просто перекотився б до максимального можливого значення (255).

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

Як запобігти недоповненню та переповненню цілих чисел

Починаючи з версії 0.8.0, компілятор Solidity відхиляє код, який призводить до недоповнення та переповнення цілих чисел. Однак контракти, скомпільовані з нижчою версією компілятора, повинні або виконувати перевірки функцій, що включають арифметичні операції, або використовувати бібліотеку (наприклад, SafeMath (відкривається в новій вкладці)), яка перевіряє на недоповнення/переповнення.

Маніпулювання оракулом

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

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

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

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

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

Як запобігти маніпулюванню оракулом

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

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

Ресурси з безпеки смарт-контрактів для розробників

Інструменти для аналізу смарт-контрактів та перевірки правильності коду

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

Інструменти для безпечного адміністрування смарт-контрактів

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

Платформи баунті за знаходження багів

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

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

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

  • Sherlock (відкривається в новій вкладці) — Андеррайтер у Web3 для безпеки смарт-контрактів, де виплати аудиторам управляються через смарт-контракти, щоб гарантувати справедливу оплату за знайдені помилки.

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

Публікації про відомі вразливості та експлойти смарт-контрактів

Завдання для вивчення безпеки смарт-контрактів

Найкращі практики для захисту смарт-контрактів

Посібники з безпеки смарт-контрактів