Оновлення смарт-контрактів
Смарт-контракти в Етеріумі — це самовиконувані програми, які працюють у віртуальній машині Етеріуму (EVM). Ці програми є незмінними за своєю природою, що запобігає будь-яким оновленням бізнес-логіки після розгортання контракту.
Хоча незмінність є необхідною для бездовірності, децентралізації та безпеки смарт-контрактів, у певних випадках вона може бути недоліком. Наприклад, незмінний код може унеможливити виправлення розробниками вразливих контрактів.
Однак активні дослідження щодо вдосконалення смарт-контрактів призвели до появи кількох патернів оновлення. Ці патерни оновлення дозволяють розробникам оновлювати смарт-контракти (зберігаючи незмінність), розміщуючи бізнес-логіку в різних контрактах.
Передумови
Ви повинні добре розуміти смарт-контракти, анатомію смарт-контрактів та віртуальну машину Етеріуму (EVM). Цей посібник також передбачає, що читачі мають уявлення про програмування смарт-контрактів.
Що таке оновлення смарт-контракту?
Оновлення смарт-контракту передбачає зміну бізнес-логіки смарт-контракту зі збереженням стану контракту. Важливо уточнити, що можливість оновлення та мінливість — це не одне й те саме, особливо в контексті смарт-контрактів.
Ви все ще не можете змінити програму, розгорнуту за адресою в мережі Етеріум. Але ви можете змінити код, який виконується, коли користувачі взаємодіють зі смарт-контрактом.
Це можна зробити за допомогою таких методів:
-
Створення кількох версій смарт-контракту та міграція стану (тобто даних) зі старого контракту до нового екземпляра контракту.
-
Створення окремих контрактів для зберігання бізнес-логіки та стану.
-
Використання проксі-патернів для делегування викликів функцій від незмінного проксі-контракту до модифікованого логічного контракту.
-
Створення незмінного головного контракту, який взаємодіє з гнучкими супутніми контрактами та покладається на них для виконання певних функцій.
-
Використання патерна «діамант» (diamond pattern) для делегування викликів функцій від проксі-контракту до логічних контрактів.
Механізм оновлення №1: Міграція контрактів
Міграція контрактів базується на версіонуванні — ідеї створення та управління унікальними станами одного й того ж програмного забезпечення. Міграція контрактів передбачає розгортання нового екземпляра існуючого смарт-контракту та перенесення сховища й балансів до нового контракту.
Щойно розгорнутий контракт матиме порожнє сховище, що дозволить вам відновити дані зі старого контракту та записати їх у нову реалізацію. Після цього вам потрібно буде оновити всі контракти, які взаємодіяли зі старим контрактом, щоб вони вказували на нову адресу.
Останній крок у міграції контрактів — переконати користувачів перейти на використання нового контракту. Нова версія контракту збереже баланси та адреси користувачів, що зберігає незмінність. Якщо це контракт на основі токенів, вам також потрібно буде зв'язатися з біржами, щоб вони відмовилися від старого контракту та використовували новий.
Міграція контрактів — це відносно простий і безпечний захід для оновлення смарт-контрактів без порушення взаємодії з користувачами. Однак ручна міграція сховища користувачів і балансів до нового контракту вимагає багато часу та може призвести до високих витрат газу.
Детальніше про міграцію контрактів. (opens in a new tab)
Механізм оновлення №2: Розділення даних
Інший метод оновлення смарт-контрактів полягає в розділенні бізнес-логіки та зберігання даних на окремі контракти. Це означає, що користувачі взаємодіють з логічним контрактом, тоді як дані зберігаються в контракті сховища.
Логічний контракт містить код, який виконується, коли користувачі взаємодіють із застосунком. Він також містить адресу контракту сховища та взаємодіє з ним для отримання та встановлення даних.
Тим часом контракт сховища зберігає стан, пов'язаний зі смарт-контрактом, наприклад, баланси та адреси користувачів. Зверніть увагу, що контракт сховища належить логічному контракту і налаштовується з адресою останнього під час розгортання. Це запобігає виклику контракту сховища або оновленню його даних неавторизованими контрактами.
За замовчуванням контракт сховища є незмінним, але ви можете замінити логічний контракт, на який він вказує, новою реалізацією. Це змінить код, який виконується в EVM, зберігаючи при цьому сховище та баланси недоторканими.
Використання цього методу оновлення вимагає оновлення адреси логічного контракту в контракті сховища. Ви також повинні налаштувати новий логічний контракт з адресою контракту сховища з причин, пояснених раніше.
Патерн розділення даних, мабуть, легше реалізувати порівняно з міграцією контрактів. Однак вам доведеться керувати кількома контрактами та впроваджувати складні схеми авторизації, щоб захистити смарт-контракти від зловмисних оновлень.
Механізм оновлення №3: Проксі-патерни
Проксі-патерн також використовує розділення даних для зберігання бізнес-логіки та даних в окремих контрактах. Однак у проксі-патерні контракт сховища (який називається проксі) викликає логічний контракт під час виконання коду. Це протилежність методу розділення даних, де логічний контракт викликає контракт сховища.
Ось що відбувається в проксі-патерні:
-
Користувачі взаємодіють із проксі-контрактом, який зберігає дані, але не містить бізнес-логіки.
-
Проксі-контракт зберігає адресу логічного контракту та делегує всі виклики функцій логічному контракту (який містить бізнес-логіку) за допомогою функції
delegatecall. -
Після переспрямування виклику до логічного контракту повернуті дані з логічного контракту отримуються та повертаються користувачеві.
Використання проксі-патернів вимагає розуміння функції delegatecall. По суті, delegatecall — це опкод, який дозволяє контракту викликати інший контракт, тоді як фактичне виконання коду відбувається в контексті контракту, що викликає. Наслідком використання delegatecall у проксі-патернах є те, що проксі-контракт читає та записує у своє сховище та виконує логіку, що зберігається в логічному контракті, так, ніби викликає внутрішню функцію.
З документації Solidity (opens in a new tab):
Існує спеціальний варіант виклику повідомлення під назвою delegatecall, який ідентичний виклику повідомлення, за винятком того, що код за цільовою адресою виконується в контексті (тобто за адресою) контракту, що викликає, а
msg.senderтаmsg.valueне змінюють своїх значень. Це означає, що контракт може динамічно завантажувати код з іншої адреси під час виконання. Сховище, поточна адреса та баланс усе ще посилаються на контракт, що викликає, лише код береться з викликаної адреси.
Проксі-контракт знає, що потрібно викликати delegatecall щоразу, коли користувач викликає функцію, оскільки в нього вбудована функція fallback. У програмуванні на Solidity резервна функція (opens in a new tab) виконується, коли виклик функції не збігається з функціями, вказаними в контракті.
Щоб проксі-патерн працював, потрібно написати спеціальну резервну функцію, яка визначає, як проксі-контракт повинен обробляти виклики функцій, які він не підтримує. У цьому випадку резервна функція проксі запрограмована на ініціювання delegatecall і перенаправлення запиту користувача до поточної реалізації логічного контракту.
Проксі-контракт за замовчуванням є незмінним, але можна створювати нові логічні контракти з оновленою бізнес-логікою. Виконання оновлення тоді зводиться до зміни адреси логічного контракту, на який посилається проксі-контракт.
Спрямовуючи проксі-контракт на новий логічний контракт, код, який виконується, коли користувачі викликають функцію проксі-контракту, змінюється. Це дозволяє нам оновлювати логіку контракту, не просячи користувачів взаємодіяти з новим контрактом.
Проксі-патерни є популярним методом оновлення смарт-контрактів, оскільки вони усувають труднощі, пов'язані з міграцією контрактів. Однак проксі-патерни складніші у використанні та можуть призвести до критичних недоліків, таких як конфлікти селекторів функцій (opens in a new tab), якщо їх використовувати неправильно.
Детальніше про проксі-патерни (opens in a new tab).
Механізм оновлення №4: Патерн стратегії
На цю техніку вплинув патерн стратегії (opens in a new tab), який заохочує створення програмного забезпечення, що взаємодіє з іншими програмами для реалізації певних функцій. Застосування патерна стратегії до розробки в Етеріумі означало б створення смарт-контракту, який викликає функції з інших контрактів.
Головний контракт у цьому випадку містить основну бізнес-логіку, але взаємодіє з іншими смарт-контрактами («супутніми контрактами») для виконання певних функцій. Цей головний контракт також зберігає адресу кожного супутнього контракту та може перемикатися між різними реалізаціями супутнього контракту.
Ви можете створити новий супутній контракт і налаштувати головний контракт з новою адресою. Це дозволяє вам змінювати стратегії (тобто впроваджувати нову логіку) для смарт-контракту.
Хоча він схожий на проксі-патерн, який обговорювався раніше, патерн стратегії відрізняється тим, що головний контракт, з яким взаємодіють користувачі, містить бізнес-логіку. Використання цього патерна дає вам можливість вносити обмежені зміни до смарт-контракту, не впливаючи на основну інфраструктуру.
Головний недолік полягає в тому, що цей патерн здебільшого корисний для розгортання незначних оновлень. Крім того, якщо головний контракт скомпрометовано (наприклад, через злам), ви не зможете використовувати цей метод оновлення.
Механізм оновлення №5: Патерн «діамант»
Патерн «діамант» можна вважати вдосконаленням проксі-патерна. Патерни «діамант» відрізняються від проксі-патернів тим, що проксі-контракт «діамант» може делегувати виклики функцій більш ніж одному логічному контракту.
Логічні контракти в патерні «діамант» відомі як фасети (facets). Щоб патерн «діамант» працював, вам потрібно створити відображення (mapping) у проксі-контракті, яке зіставляє селектори функцій (opens in a new tab) з різними адресами фасетів.
Коли користувач робить виклик функції, проксі-контракт перевіряє відображення, щоб знайти фасет, відповідальний за виконання цієї функції. Потім він викликає delegatecall (використовуючи резервну функцію) і перенаправляє виклик до відповідного логічного контракту.
Патерн оновлення «діамант» має деякі переваги над традиційними патернами оновлення проксі:
-
Він дозволяє оновити невелику частину контракту без зміни всього коду. Використання проксі-патерна для оновлень вимагає створення абсолютно нового логічного контракту, навіть для незначних оновлень.
-
Усі смарт-контракти (включно з логічними контрактами, що використовуються в проксі-патернах) мають обмеження розміру 24 КБ, що може бути обмеженням — особливо для складних контрактів, які потребують більше функцій. Патерн «діамант» дозволяє легко вирішити цю проблему, розділивши функції між кількома логічними контрактами.
-
Проксі-патерни застосовують універсальний підхід до контролю доступу. Сутність із доступом до функцій оновлення може змінити весь контракт. Але патерн «діамант» забезпечує модульний підхід до дозволів, де ви можете обмежити сутності оновленням лише певних функцій у межах смарт-контракту.
Детальніше про патерн «діамант» (opens in a new tab).
Переваги та недоліки оновлення смарт-контрактів
| Переваги | Недоліки |
|---|---|
| Оновлення смарт-контракту може полегшити виправлення вразливостей, виявлених на етапі після розгортання. | Оновлення смарт-контрактів суперечить ідеї незмінності коду, що має наслідки для децентралізації та безпеки. |
| Розробники можуть використовувати оновлення логіки для додавання нових функцій до децентралізованих застосунків. | Користувачі повинні довіряти розробникам, що ті не будуть довільно змінювати смарт-контракти. |
| Оновлення смарт-контрактів може підвищити безпеку для кінцевих користувачів, оскільки помилки можна швидко виправити. | Програмування функціональності оновлення в смарт-контрактах додає ще один рівень складності та збільшує ймовірність критичних недоліків. |
| Оновлення контрактів дають розробникам більше простору для експериментів із різними функціями та вдосконалення децентралізованих застосунків (dapps) з часом. | Можливість оновлення смарт-контрактів може спонукати розробників швидше запускати проєкти без належної перевірки на етапі розробки. |
| Небезпечний контроль доступу або централізація в смарт-контрактах може полегшити зловмисникам виконання несанкціонованих оновлень. |
Міркування щодо оновлення смарт-контрактів
-
Використовуйте безпечні механізми контролю доступу/авторизації, щоб запобігти несанкціонованим оновленням смарт-контрактів, особливо якщо використовуються проксі-патерни, патерни стратегії або розділення даних. Прикладом є обмеження доступу до функції оновлення таким чином, щоб її міг викликати лише власник контракту.
-
Оновлення смарт-контрактів — це складна діяльність, яка вимагає високого рівня ретельності, щоб запобігти появі вразливостей.
-
Зменште припущення довіри шляхом децентралізації процесу впровадження оновлень. Можливі стратегії включають використання контракту гаманця з мультипідписом для контролю оновлень або вимогу до учасників DAO віддати свій голос за схвалення оновлення.
-
Пам'ятайте про витрати, пов'язані з оновленням контрактів. Наприклад, копіювання стану (наприклад, балансів користувачів) зі старого контракту в новий під час міграції контрактів може вимагати більше ніж однієї транзакції, що означає більші комісії за газ.
-
Розгляньте можливість впровадження часових блокувань (timelocks) для захисту користувачів. Часове блокування означає затримку, яка накладається на зміни в системі. Часові блокування можна поєднати з системою управління з мультипідписом для контролю оновлень: якщо запропонована дія досягає необхідного порогу схвалення, вона не виконується, доки не мине заздалегідь визначений період затримки.
Часові блокування дають користувачам певний час на вихід із системи, якщо вони не згодні із запропонованою зміною (наприклад, оновленням логіки або новими схемами комісій). Без часових блокувань користувачам потрібно довіряти розробникам, що ті не будуть впроваджувати довільні зміни в смарт-контракт без попереднього повідомлення. Недоліком тут є те, що часові блокування обмежують можливість швидкого виправлення вразливостей.
Ресурси
Плагіни оновлень ОупенЗеппелін — Набір інструментів для розгортання та захисту оновлюваних смарт-контрактів.
Посібники
- Оновлення ваших смарт-контрактів | Посібник на YouTube (opens in a new tab) від Патріка Коллінза (Patrick Collins)
- Посібник із міграції смарт-контрактів Етеріуму (opens in a new tab) від Остіна Гріффіта (Austin Griffith)
- Використання проксі-патерна UUPS для оновлення смарт-контрактів (opens in a new tab) від Пранеша А.С. (Pranesh A.S)
- Посібник із Web3: Написання оновлюваного смарт-контракту (проксі) за допомогою ОупенЗеппелін (opens in a new tab) від fangjun.eth
Додаткова література
- Стан оновлень смарт-контрактів (opens in a new tab) від Сантьяго Палладіно (Santiago Palladino)
- Кілька способів оновлення смарт-контракту на Solidity (opens in a new tab) — блог Crypto Market Pool
- Навчання: Оновлення смарт-контрактів (opens in a new tab) — Документація ОупенЗеппелін
- Проксі-патерни для можливості оновлення контрактів на Solidity: Transparent проти UUPS Proxies (opens in a new tab) від Навіна Саху (Naveen Sahu)
- Як працюють оновлення «діамант» (opens in a new tab) від Ніка Маджа (Nick Mudge)