Не просто пишіть оператори require для певних функцій; пишіть оператори require для ваших протоколів. Перевірки відповідності функцій (вимоги)-ефективності (ефекти)-взаємодії (INteractions) + інваріантність протоколу (Iniants) або режим FREI-PI можуть зробити ваш контракт більш безпечним, оскільки він змушує розробників зосереджуватися на безпеці на рівні функцій. Також слідкуйте для інваріантів на рівні протоколу.
мотивація
У березні 2023 року Euler Finance було зламано та втратило 200 мільйонів доларів. Euler Finance — це ринок позик, де користувачі можуть вносити заставу та позичати під неї. Він має деякі унікальні особливості, насправді це ринок кредитування, який можна порівняти з Compound Finance та Aave.
Ви можете прочитати посмертний опис цього злому тут. Його основним змістом є відсутність перевірок працездатності в певній функції, що дозволяє користувачам порушити фундаментальну незмінність ринку кредитування.
Фундаментальні ініанти
В основі більшості протоколів DeFi лежить незмінність, властивість стану програми, яка завжди має бути істинною. Також можливо мати кілька інваріантів, але загалом вони побудовані навколо основної ідеї. Ось кілька прикладів:
Як і на ринку кредитування: користувачі не можуть вживати жодних дій для розміщення будь-якого облікового запису в небезпечній або менш безпечній позиції застави («менш безпечна» означає, що вона вже нижче мінімального порогу безпеки, і тому не може бути виведена надалі).
У AMM DEX: x * y == k, x + y == k тощо.
У стейкінгу для майнінгу ліквідності: користувачі повинні мати можливість зняти лише ту кількість жетонів стейкінгу, яку вони внесли.
Те, що пішло не так з Euler Finance, не обов’язково полягало в тому, що вони додали функції, не написали тести або не дотримувалися традиційних передових практик. Вони перевірили оновлення та провели тести, але воно все одно прослизнуло крізь щілини. Основна проблема полягає в тому, що вони забувають про основний інваріант ринку кредитування (як і аудитори!).
*Примітка: я не намагаюся приставати до Ейлера, вони талановита команда, але це недавній випадок. *
Суть проблеми
Ви, напевно, думаєте: «Ну, це так. Ось чому їх зламали; вони забули оператор require». Так і ні.
Загальним шаблоном, рекомендованим для розробників solidity, є шаблон перевірок-ефектів-взаємодій. Це дуже корисно для усунення помилок, пов’язаних із повторним входом, і часто збільшує кількість перевірки введених даних, яку розробник має виконати. _Але_, він схильний до проблеми, коли дерева не бачать лісу.
Розробник навчається наступному: «Спочатку я пишу свою вимогу, потім виконую перевірку, потім, можливо, я зроблю будь-яку взаємодію, тоді я в безпеці». Проблема в тому, що частіше за все це стає сумішшю перевірок і ефектів — непогано, га? Взаємодії все ще остаточні, тому повторний доступ не є проблемою. Але це змушує користувачів зосереджуватися на більш конкретних функціях і окремих переходах між станами, а не на глобальному ширшому контексті. Це означає що:
Простий шаблон взаємодії перевірка-перевірка змушує розробників забути про основні інваріанти їхніх протоколів.
Це все ще чудовий зразок для розробників, але незмінність протоколу завжди повинна бути забезпечена (серйозно, ви все ще повинні використовувати CEI!).
Робіть це правильно: режим FREI-PI
Візьмемо, наприклад, цей фрагмент із контракту SoloMargin від dYdX (вихідний код), який є ринком кредитування та торговим контрактом із кредитним плечем. Це гарний приклад того, що я називаю шаблоном «Вимоги до функції-ефекти-взаємодії + ініціатори протоколу» або шаблоном FREI-PI.
Таким чином, я вважаю, що це єдиний ринок кредитування на ринку кредитування на ранній стадії, який не має жодних ринкових уразливостей. Compound і Aave не мають прямих проблем, але їхні форки мають. І bZx був зламаний багато разів.
Перегляньте наведений нижче код і зверніть увагу на такі абстракції:
Перевірте вхідні параметри (_verifyInputs).
Дія (перетворення даних, маніпулювання станом)
Перевірте кінцевий стан (_verifyFinalState).
Звичайні перевірки-ефекти-взаємодії все ще виконуються. Варто зазначити, що перевірка перевірки взаємодії з додатковими перевірками не еквівалентна FREI-PI — вони схожі, але служать принципово іншим цілям. Тому розробники повинні думати про них як про різні: FREI-PI, як вища абстракція, націлений на безпеку протоколу, тоді як CEI націлений на функціональну безпеку.
Структура цього контракту дійсно цікава - користувачі можуть виконувати дії, які вони хочуть (депонувати, позичати, торгувати, передавати, ліквідувати тощо) у ланцюжку дій. Хочете внести 3 різні токени, зняти 4-й і ліквідувати рахунок? Це один дзвінок.
Це сила FREI-PI: користувачі можуть робити все, що завгодно, у межах протоколу, доки інваріанти основного ринку кредитування зберігаються наприкінці виклику: окремий користувач не може виконувати жодних дій, які можуть зробити будь-який обліковий запис небезпечним. або більше. Незахищені додаткові позиції. Для цього контракту це виконується в _verifyFinalState, перевіряючи забезпечення кожного постраждалого облікового запису, щоб переконатися, що угода краща, ніж на момент початку транзакції.
Є деякі додаткові інваріанти, включені в цю функцію, які доповнюють основні інваріанти та допомагають із побічними функціями, такими як закриття ринків, але саме основні перевірки забезпечують безпеку протоколу.
FREI-PI, орієнтований на сутності
Іншою проблемою FREI-PI є концепція, орієнтована на сутності. Візьмемо як приклад ринок кредитування та припущені основні інваріанти:
Технічно це не єдиний інваріант, але він стосується сутності користувача (це все ще інваріант основного протоколу, і зазвичай інваріанти користувача є інваріантами основного протоколу). Ринки кредитування також зазвичай мають 2 додаткові суб’єкти:
Оракул
Управління / Управління
Кожна додаткова незмінність ускладнює гарантування протоколу, тому чим менше, тим краще. Насправді це те, що сказав Ден Елітцер у своїй статті під назвою: Чому DeFi зламаний і як це виправити, протокол №1 без Oracle (підказка: у статті насправді не сказано, що проблема в оракулах).
Оракул
Для оракулів візьміть експлойт Cream Finance вартістю 130 мільйонів доларів. Основна незмінність сутностей оракула:
Як виявилося, перевірити оракули під час виконання за допомогою FREI-PI складно, але здійснимо, якщо подумати заздалегідь. Загалом, Chainlink — це хороший вибір, на який можна здебільшого покладатися, задовольняючи більшість незмінності. У рідкісних випадках маніпуляції або несподіванки може бути корисним мати засоби захисту, які зменшують гнучкість на користь точності (наприклад, перевірка того, що останнє відоме значення було на кілька відсотків більшим за поточне). Крім того, система SoloMargin від dYdX чудово справляється з їхнім оракулом DAI, ось код (якщо ви не можете сказати, я думаю, що це найкраща комплексна система смарт-контрактів, коли-небудь написана).
Щоб дізнатися більше про оцінку оракула та підкреслити можливості команди Ейлера, вони написали хорошу статтю про обчислювальне маніпулювання цінами оракула Uniswap V3 TWAP.
Адміністрація / Управління
Створення інваріантів для керованих об’єктів є найскладнішим. Це головним чином пов'язано з тим, що більша частина їх ролі полягає у зміні існуючих інших інваріантів. Тим не менш, якщо ви можете уникнути використання адміністративних ролей, ви повинні.
По суті, основними інваріантами керованої сутності можуть бути:
Інтерпретація: адміністратори можуть робити речі, які в кінцевому підсумку не повинні порушувати інваріанти, якщо вони не змінюють щось кардинально, щоб захистити кошти користувачів (наприклад: переміщення активів у договір порятунку є видаленням інваріантів). Адміністратори також повинні розглядатися як користувачі, тому незмінність користувачів основного ринку кредитування також повинна зберігатися для них (це означає, що вони не можуть атакувати інших користувачів або протоколи). Наразі деякі дії адміністратора неможливо перевірити під час виконання за допомогою FREI-PI, але, маючи достатньо сильні інваріанти в інших місцях, можна сподіватися, що більшість проблем можна пом’якшити. Я кажу зараз, тому що можна уявити використання системи перевірки zk, яка може перевіряти весь стан контракту (на користувача, на оракул тощо).
Як приклад того, що адміністратор порушує незмінність, візьмемо дію Compound management, яка розбила ринок cETH у серпні 2022 року. По суті, це оновлення порушує незмінність Oracle: Oracle надає точну та (відносно) інформацію в реальному часі. Через відсутність функціональних можливостей Oracle може надати невірну інформацію. Перевірка FREI-PI під час виконання, яка перевіряє, чи може уражений Oracle надавати інформацію в реальному часі, може запобігти цьому під час оновлення. Це можна включити в _setPriceOracle, щоб перевірити, чи всі активи отримали інформацію в реальному часі. Приємна річ про FREI-PI для ролей адміністратора полягає в тому, що ролі адміністратора відносно нечутливі до ціни (або, принаймні, вони повинні бути), тому більше споживання газу не повинно бути великою проблемою.
Складність небезпечна
Таким чином, хоча найважливіші інваріанти є основними інваріантами протоколу, також можуть існувати деякі інваріанти, орієнтовані на сутність, які повинні зберігатися основними інваріантами. Однак найпростіший (і найменший) набір інваріантів, ймовірно, найбезпечніший. Просто – це добре. Яскравим прикладом є Uniswap…
Чому Uniswap ніколи не зламали (ймовірно)
AMM можуть мати найпростішу базову інваріантність будь-якого примітиву DeFi: tokenBalanceX * tokenBalanceY == k (наприклад, постійна модель продукту). Кожна функція в Uniswap V2 обертається навколо цього простого інваріанта:
Монетний двір: додано до к
Опік: відняти від k
Поміняти місцями: перенести x і y, але залишити k.
Зніміть: переналаштуйте tokenBalanceX * tokenBalanceY, щоб зробити його рівним k, і видаліть зайву частину.
Секрет безпеки Uniswap V2: ядро є простою незмінністю, і всі функції служать йому. Єдиною іншою сутністю, про яку можна сперечатися, є управління, яке може ввімкнути перемикач комісії, що не торкається основної незмінності, а лише розподіл права власності на баланс токенів. Ця простота в їхній заяві про безпеку є причиною того, що Uniswap ніколи не було зламано. Простота насправді не є презирством до чудових розробників смарт-контрактів Uniswap. Навпаки, щоб знайти простоту, потрібні чудові інженери.
Проблема з газом
Мій Twitter уже сповнений оптимізаційних криків жаху та болю, що ці перевірки непотрібні та неефективні. Дві речі щодо цього питання:
Чи знаєте ви, що ще неефективно? Довелося надсилати повідомлення ~~Laurence~~ північнокорейським хакерам через etherscan, переказувати гроші за допомогою ETH і погрожувати, що ФБР втрутиться.
Можливо, ви вже завантажили всі потрібні дані зі сховища, тому наприкінці розмови просто додайте невелику перевірку вимоги до гарячих даних. Ви хочете, щоб ваша угода коштувала незначну плату, чи нехай вона помре?
Якщо вартість непомірно висока, переосмисліть основні змінні та спробуйте спростити.
Що це означає для мене?
Як розробнику, важливо визначити та висловити основні інваріанти на ранніх стадіях процесу розробки. Як конкретна пропозиція: першою функцією, яку потрібно написати, є _verifyAfter, щоб перевіряти ваші інваріанти після кожного виклику вашого контракту. Внесіть це у свій контракт і розгорніть його там. Доповніть цей інваріант (та інші інваріанти, орієнтовані на об’єкти) ширшими інваріантними тестами, які перевіряються перед розгортанням (посібник Foundry).
Тимчасові сховища відкривають деякі цікаві оптимізації та вдосконалення, з якими Nascent буде експериментувати. Я пропоную вам розглянути, як тимчасові сховища можна використовувати як інструмент для кращої безпеки в контекстах викликів.
У цій статті небагато часу приділено впровадженню моделі FREI-PI для перевірки вхідних даних, але це також дуже важливо. Визначення меж введення є складним завданням, щоб уникнути переповнення та подібних ситуацій. Перевірте та стежте за розвитком нашого інструменту: пірометр (наразі в бета-версії, позначте нас зірочкою). Він може деталізувати та допомогти знайти місця, де ви, можливо, не виконуєте перевірку введення.
на завершення
На додаток до будь-якої помітної абревіатури (FREI-PI) або назви схеми, дійсно важливий біт:
Знайдіть простоту в основній незмінності вашого протоколу. І працюйте як біс, щоб переконатися, що його ніколи не знищили (або не спіймали раніше).
Переглянути оригінал
This page may contain third-party content, which is provided for information purposes only (not representations/warranties) and should not be considered as an endorsement of its views by Gate, nor as financial or professional advice. See Disclaimer for details.
Нова модель безпеки контрактів DeFi: фокус на інваріантності протоколу
Резюме
Не просто пишіть оператори require для певних функцій; пишіть оператори require для ваших протоколів. Перевірки відповідності функцій (вимоги)-ефективності (ефекти)-взаємодії (INteractions) + інваріантність протоколу (Iniants) або режим FREI-PI можуть зробити ваш контракт більш безпечним, оскільки він змушує розробників зосереджуватися на безпеці на рівні функцій. Також слідкуйте для інваріантів на рівні протоколу.
мотивація
У березні 2023 року Euler Finance було зламано та втратило 200 мільйонів доларів. Euler Finance — це ринок позик, де користувачі можуть вносити заставу та позичати під неї. Він має деякі унікальні особливості, насправді це ринок кредитування, який можна порівняти з Compound Finance та Aave.
Ви можете прочитати посмертний опис цього злому тут. Його основним змістом є відсутність перевірок працездатності в певній функції, що дозволяє користувачам порушити фундаментальну незмінність ринку кредитування.
Фундаментальні ініанти
В основі більшості протоколів DeFi лежить незмінність, властивість стану програми, яка завжди має бути істинною. Також можливо мати кілька інваріантів, але загалом вони побудовані навколо основної ідеї. Ось кілька прикладів:
Те, що пішло не так з Euler Finance, не обов’язково полягало в тому, що вони додали функції, не написали тести або не дотримувалися традиційних передових практик. Вони перевірили оновлення та провели тести, але воно все одно прослизнуло крізь щілини. Основна проблема полягає в тому, що вони забувають про основний інваріант ринку кредитування (як і аудитори!).
*Примітка: я не намагаюся приставати до Ейлера, вони талановита команда, але це недавній випадок. *
Суть проблеми
Ви, напевно, думаєте: «Ну, це так. Ось чому їх зламали; вони забули оператор require». Так і ні.
Але чому б їм забути про вимогу?
перевірка - перевірка - взаємодія недостатньо хороша
Загальним шаблоном, рекомендованим для розробників solidity, є шаблон перевірок-ефектів-взаємодій. Це дуже корисно для усунення помилок, пов’язаних із повторним входом, і часто збільшує кількість перевірки введених даних, яку розробник має виконати. _Але_, він схильний до проблеми, коли дерева не бачать лісу.
Розробник навчається наступному: «Спочатку я пишу свою вимогу, потім виконую перевірку, потім, можливо, я зроблю будь-яку взаємодію, тоді я в безпеці». Проблема в тому, що частіше за все це стає сумішшю перевірок і ефектів — непогано, га? Взаємодії все ще остаточні, тому повторний доступ не є проблемою. Але це змушує користувачів зосереджуватися на більш конкретних функціях і окремих переходах між станами, а не на глобальному ширшому контексті. Це означає що:
Простий шаблон взаємодії перевірка-перевірка змушує розробників забути про основні інваріанти їхніх протоколів.
Це все ще чудовий зразок для розробників, але незмінність протоколу завжди повинна бути забезпечена (серйозно, ви все ще повинні використовувати CEI!).
Робіть це правильно: режим FREI-PI
Візьмемо, наприклад, цей фрагмент із контракту SoloMargin від dYdX (вихідний код), який є ринком кредитування та торговим контрактом із кредитним плечем. Це гарний приклад того, що я називаю шаблоном «Вимоги до функції-ефекти-взаємодії + ініціатори протоколу» або шаблоном FREI-PI.
Таким чином, я вважаю, що це єдиний ринок кредитування на ринку кредитування на ранній стадії, який не має жодних ринкових уразливостей. Compound і Aave не мають прямих проблем, але їхні форки мають. І bZx був зламаний багато разів.
Перегляньте наведений нижче код і зверніть увагу на такі абстракції:
Звичайні перевірки-ефекти-взаємодії все ще виконуються. Варто зазначити, що перевірка перевірки взаємодії з додатковими перевірками не еквівалентна FREI-PI — вони схожі, але служать принципово іншим цілям. Тому розробники повинні думати про них як про різні: FREI-PI, як вища абстракція, націлений на безпеку протоколу, тоді як CEI націлений на функціональну безпеку.
Структура цього контракту дійсно цікава - користувачі можуть виконувати дії, які вони хочуть (депонувати, позичати, торгувати, передавати, ліквідувати тощо) у ланцюжку дій. Хочете внести 3 різні токени, зняти 4-й і ліквідувати рахунок? Це один дзвінок.
Це сила FREI-PI: користувачі можуть робити все, що завгодно, у межах протоколу, доки інваріанти основного ринку кредитування зберігаються наприкінці виклику: окремий користувач не може виконувати жодних дій, які можуть зробити будь-який обліковий запис небезпечним. або більше. Незахищені додаткові позиції. Для цього контракту це виконується в _verifyFinalState, перевіряючи забезпечення кожного постраждалого облікового запису, щоб переконатися, що угода краща, ніж на момент початку транзакції.
Є деякі додаткові інваріанти, включені в цю функцію, які доповнюють основні інваріанти та допомагають із побічними функціями, такими як закриття ринків, але саме основні перевірки забезпечують безпеку протоколу.
FREI-PI, орієнтований на сутності
Іншою проблемою FREI-PI є концепція, орієнтована на сутності. Візьмемо як приклад ринок кредитування та припущені основні інваріанти:
Технічно це не єдиний інваріант, але він стосується сутності користувача (це все ще інваріант основного протоколу, і зазвичай інваріанти користувача є інваріантами основного протоколу). Ринки кредитування також зазвичай мають 2 додаткові суб’єкти:
Кожна додаткова незмінність ускладнює гарантування протоколу, тому чим менше, тим краще. Насправді це те, що сказав Ден Елітцер у своїй статті під назвою: Чому DeFi зламаний і як це виправити, протокол №1 без Oracle (підказка: у статті насправді не сказано, що проблема в оракулах).
Оракул
Для оракулів візьміть експлойт Cream Finance вартістю 130 мільйонів доларів. Основна незмінність сутностей оракула:
Як виявилося, перевірити оракули під час виконання за допомогою FREI-PI складно, але здійснимо, якщо подумати заздалегідь. Загалом, Chainlink — це хороший вибір, на який можна здебільшого покладатися, задовольняючи більшість незмінності. У рідкісних випадках маніпуляції або несподіванки може бути корисним мати засоби захисту, які зменшують гнучкість на користь точності (наприклад, перевірка того, що останнє відоме значення було на кілька відсотків більшим за поточне). Крім того, система SoloMargin від dYdX чудово справляється з їхнім оракулом DAI, ось код (якщо ви не можете сказати, я думаю, що це найкраща комплексна система смарт-контрактів, коли-небудь написана).
Щоб дізнатися більше про оцінку оракула та підкреслити можливості команди Ейлера, вони написали хорошу статтю про обчислювальне маніпулювання цінами оракула Uniswap V3 TWAP.
Адміністрація / Управління
Створення інваріантів для керованих об’єктів є найскладнішим. Це головним чином пов'язано з тим, що більша частина їх ролі полягає у зміні існуючих інших інваріантів. Тим не менш, якщо ви можете уникнути використання адміністративних ролей, ви повинні.
По суті, основними інваріантами керованої сутності можуть бути:
Інтерпретація: адміністратори можуть робити речі, які в кінцевому підсумку не повинні порушувати інваріанти, якщо вони не змінюють щось кардинально, щоб захистити кошти користувачів (наприклад: переміщення активів у договір порятунку є видаленням інваріантів). Адміністратори також повинні розглядатися як користувачі, тому незмінність користувачів основного ринку кредитування також повинна зберігатися для них (це означає, що вони не можуть атакувати інших користувачів або протоколи). Наразі деякі дії адміністратора неможливо перевірити під час виконання за допомогою FREI-PI, але, маючи достатньо сильні інваріанти в інших місцях, можна сподіватися, що більшість проблем можна пом’якшити. Я кажу зараз, тому що можна уявити використання системи перевірки zk, яка може перевіряти весь стан контракту (на користувача, на оракул тощо).
Як приклад того, що адміністратор порушує незмінність, візьмемо дію Compound management, яка розбила ринок cETH у серпні 2022 року. По суті, це оновлення порушує незмінність Oracle: Oracle надає точну та (відносно) інформацію в реальному часі. Через відсутність функціональних можливостей Oracle може надати невірну інформацію. Перевірка FREI-PI під час виконання, яка перевіряє, чи може уражений Oracle надавати інформацію в реальному часі, може запобігти цьому під час оновлення. Це можна включити в _setPriceOracle, щоб перевірити, чи всі активи отримали інформацію в реальному часі. Приємна річ про FREI-PI для ролей адміністратора полягає в тому, що ролі адміністратора відносно нечутливі до ціни (або, принаймні, вони повинні бути), тому більше споживання газу не повинно бути великою проблемою.
Складність небезпечна
Таким чином, хоча найважливіші інваріанти є основними інваріантами протоколу, також можуть існувати деякі інваріанти, орієнтовані на сутність, які повинні зберігатися основними інваріантами. Однак найпростіший (і найменший) набір інваріантів, ймовірно, найбезпечніший. Просто – це добре. Яскравим прикладом є Uniswap…
Чому Uniswap ніколи не зламали (ймовірно)
AMM можуть мати найпростішу базову інваріантність будь-якого примітиву DeFi: tokenBalanceX * tokenBalanceY == k (наприклад, постійна модель продукту). Кожна функція в Uniswap V2 обертається навколо цього простого інваріанта:
Секрет безпеки Uniswap V2: ядро є простою незмінністю, і всі функції служать йому. Єдиною іншою сутністю, про яку можна сперечатися, є управління, яке може ввімкнути перемикач комісії, що не торкається основної незмінності, а лише розподіл права власності на баланс токенів. Ця простота в їхній заяві про безпеку є причиною того, що Uniswap ніколи не було зламано. Простота насправді не є презирством до чудових розробників смарт-контрактів Uniswap. Навпаки, щоб знайти простоту, потрібні чудові інженери.
Проблема з газом
Мій Twitter уже сповнений оптимізаційних криків жаху та болю, що ці перевірки непотрібні та неефективні. Дві речі щодо цього питання:
Якщо вартість непомірно висока, переосмисліть основні змінні та спробуйте спростити.
Що це означає для мене?
Як розробнику, важливо визначити та висловити основні інваріанти на ранніх стадіях процесу розробки. Як конкретна пропозиція: першою функцією, яку потрібно написати, є _verifyAfter, щоб перевіряти ваші інваріанти після кожного виклику вашого контракту. Внесіть це у свій контракт і розгорніть його там. Доповніть цей інваріант (та інші інваріанти, орієнтовані на об’єкти) ширшими інваріантними тестами, які перевіряються перед розгортанням (посібник Foundry).
Тимчасові сховища відкривають деякі цікаві оптимізації та вдосконалення, з якими Nascent буде експериментувати. Я пропоную вам розглянути, як тимчасові сховища можна використовувати як інструмент для кращої безпеки в контекстах викликів.
У цій статті небагато часу приділено впровадженню моделі FREI-PI для перевірки вхідних даних, але це також дуже важливо. Визначення меж введення є складним завданням, щоб уникнути переповнення та подібних ситуацій. Перевірте та стежте за розвитком нашого інструменту: пірометр (наразі в бета-версії, позначте нас зірочкою). Він може деталізувати та допомогти знайти місця, де ви, можливо, не виконуєте перевірку введення.
на завершення
На додаток до будь-якої помітної абревіатури (FREI-PI) або назви схеми, дійсно важливий біт:
Знайдіть простоту в основній незмінності вашого протоколу. І працюйте як біс, щоб переконатися, що його ніколи не знищили (або не спіймали раніше).