Перейти до основного вмісту
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. Тестуйте смарт-контракти та перевіряйте правильність коду

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

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

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

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

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

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

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

Аудити

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

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

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

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

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

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

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

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

  • Зберігайте весь код у системі контролю версій, такій як 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 («роби це простіше, дурнику»), який радить не вводити непотрібну складність у дизайн програмного забезпечення. Це випливає з давньої думки про те, що «складні системи дають збої складними способами» і більш схильні до дорогих помилок.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Одним із підходів до боротьби з повторним входом є дотримання шаблону перевірки-ефекти-взаємодії (відкривається в новій вкладці). Цей шаблон впорядковує виконання функцій таким чином, що код, який виконує необхідні перевірки перед продовженням виконання, йде першим, за ним слідує код, який маніпулює станом контракту, а код, який взаємодіє з іншими контрактами або 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 (відкривається в новій вкладці)), яка перевіряє наявність недостачі/переповнення.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Платформи bug bounty

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

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

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

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

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

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

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

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

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