Введение в распространенные уязвимости и атаки в смарт-контрактах
Что такое смарт-контракт?
В Ethereum существует два распространенных типа аккаунтов: внешние аккаунты (EOA) и аккаунты смарт-контрактов (SCA).
EOA очень похожи на электронные финансовые счета, которые мы обычно используем для хранения средств и взаимодействия с приложениями. Например, пользователи вносят фиатную валюту через PayPal и взаимодействуют с различными веб-сайтами, магазинами и приложениями для осуществления платежей. Майнеры DeFi обычно хранят криптовалюту на своих EOA, взаимодействуют с DeFi dApps и вносят средства в dApps для получения прибыли. Тем не менее, EOA обладают функцией, которой нет у электронных финансовых счетов: пользователи должны подтверждать свой контроль над EOA через владение приватными ключами — нет ваших ключей, нет ваших монет.
SCA также являются типом аккаунта, который по существу связан с сегментом исполняемого байт-кода (также известного как смарт-контракт). Смарт-контракт описывает различную бизнес-логику и служит бэкендом для dApps. Однако, несмотря на большие ограничения по сравнению с традиционными полными по Тьюрингу языками разработки, квази-полные по Тьюрингу смарт-контракты все еще были уязвимы для многочисленных атак, нанося бесчисленные удары по индустрии блокчейна.
Распространенные атаки на смарт-контракты
1. Атака повторного входа
Наиболее распространенной и печально известной атакой является атака повторного входа, которая привела к форку Ethereum и созданию Ethereum Classic. В 2016 году хакеры осуществили атаку повторного входа на контракт DAO, украв 3,600,000 ETH стоимостью более 150 миллионов долларов на тот момент. Эта атака, произошедшая на ранних этапах существования Ethereum, опустошила экосистему и подорвала доверие инвесторов, в конечном итоге приведя к форку.
Конкретная логика
Вот пример, который поможет вам лучше понять принцип атаки повторного входа. Банк Б ранее одолжил некоторую сумму денег Банку А. Однажды Банк Б инициирует перевод в Банк А, запрашивая перевод всех денег обратно в Банк Б. Нормальный путь выглядит следующим образом:
Шаг 1: Банк Б запрашивает вывод средств
Шаг 2: Банк А переводит средства в Банк Б
Шаг 3: Банк А подтверждает успешный перевод в Банк Б
Шаг 4: Банк A обновляет баланс счета Банка B.
Однако, если Банк B создает лазейку после Шага 2 и продолжает запрашивать все деньги у Банка A без подтверждения на Шаге 3, то баланс счета Банка A в Банке B останется неизменным. Этот рекурсивный вызов опустошит все активы Банка A.
:quality(80)/2024-06-12/00BA7889B1C774695FD35EC286921E80.jpg)
Связанные смарт-контракты
Контракт Банка A включает две функции:
- deposit(): Функция депозита, которая вносит деньги в Банк A и обновляет баланс пользователя;
- withdraw(): Функция вывода, которая позволяет пользователям снять все свои средства из Банка A.
:quality(80)/2024-06-12/663337160F9075D91709173CC894966C.png)
- Атакующий контракт Банка B в основном включает цикл, который запускает функцию обратного вызова receive(), которая, в свою очередь, вызывает функцию withdraw() контракта Банка для вывода активов Банка A через последовательность из 1 депозита, 1 вывода и вызовов функции обратного вызова receive(), и наконец обновляет баланс B в A. Он включает две функции:receive(): Функция обратного вызова, запускаемая при получении ETH, которая рекурсивно вызывает функцию withdraw() контракта Банка для осуществления выводов.
- attack(): Сначала вызывает функцию deposit() контракта Банка для обновления баланса, а затем функцию withdraw() для инициирования первого вывода, и запускает функцию обратного вызова receive() для рекурсивного вызова withdraw() для вывода активов из контракта Банка.
:quality(80)/2024-06-12/BAE440DF529E4A08896A3FD4C2EB3E5A.png)
Решение
Внедрение блокировки повторного входа
Блокировка повторного входа - это модификатор, используемый для предотвращения повторного входа, обеспечивающий завершение выполнения вызова перед тем, как он может быть вызван снова. Например, поскольку атака со стороны Банка B требует многократного вызова функции withdraw() контракта Bank, она не удастся при реализации блокировки повторного входа.
:quality(80)/2024-06-12/F5A9D61404DE2A7CFAC0557C35A3B2A0.png)
Как использовать
:quality(80)/2024-06-12/B44E9137F7F01168E686D9531A62893A.png)
2. Неправильное использование tx.origin
Основная функция tx.origin в смарт-контракте заключается в получении исходного аккаунта, инициировавшего транзакцию. Здесь мы обсудим две распространенные переменные в смарт-контрактах: msg.sender и tx.origin . msg.sender получает аккаунт, непосредственно вызывающий смарт-контракт, в то время как в мире блокчейна, из-за вложенных и взаимных вызовов различных смарт-контрактов (например, DeFi Lego), tx.origin необходим для получения исходного аккаунта, инициировавшего транзакцию. Уязвимость возникает, когда разработчики dApp проверяют безопасность только tx.origin в коде, пренебрегая проверкой безопасности атакующих, развертывающих промежуточные контракты для обхода tx.origin и запуска атак.
Конкретная логика
Вот пример, который поможет вам глубже понять распространенный сценарий атаки. У Билла есть смарт-кошелек, который проверяет, является ли Билл инициатором перевода. Однажды Билл создал NFT на фишинговом сайте. Это позволило сайту получить личность Билла и инициировать перевод с его смарт-кошелька, используя его личность, что привело к потере активов. В обычных обстоятельствах пользователи с меньшей вероятностью попадутся на эту уловку, но при взаимодействии с dApp с использованием кошелька они часто забывают проверять подсказки взаимодействия. Например, если оба включают функцию Mint(), невнимательные пользователи могут легко попасть в фишинговую ловушку. Бизнес-логика внутри фишингового сайта изобилует ловушками, поэтому важно проверять подсказки взаимодействия на наличие ошибок во время обычных взаимодействий.
Контракт смарт-кошелька
Контракт смарт-кошелька включает одну функцию:
- transfer(): Функция вывода, которая может быть инициирована только владельцем кошелька, в данном случае Биллом.
:quality(80)/2024-06-12/DB747D56125A3A4E4CB3E44DA4752417.png)
Контракт фишинговой атаки
В фишинговом атакующем контракте функция Mint() побуждает пользователей переводить средства на адрес хакера. Он включает одну функцию:
- Mint(): При вызове фишинговая функция внутренне выполняет transfer() контракта Wallet. Поскольку исходным инициатором является сам пользователь (в данном примере Билл), проверка require(tx.origin == owner, "Not owner") ; не будет проблемой. Однако целевой адрес для перевода уже был подменен на адрес хакера, что приводит к краже средств.
:quality(80)/2024-06-12/5AD33F696CBAC114CB1842880AF58F22.png)
Решения
1. Использовать msg.sender вместо tx.origin
Независимо от количества вызовов контрактов (Контракт A → Контракт B →…→ целевой контракт), проверяется только msg.sender, то есть непосредственный вызывающий, чтобы избежать атак, вызванных вредоносными промежуточными контрактами.
:quality(80)/2024-06-12/C39571E9DE1754D93825BFB70801D20C.png)
2. Проверка tx.origin == msg.sender
Этот метод может защитить от вредоносных контрактов, но разработчикам необходимо учитывать реальные условия своего бизнеса, так как он эффективно изолирует все другие внешние вызовы контрактов.
:quality(80)/2024-06-12/A0FE949EEF1699DF1C11234BF9D5456B.png)
3. Атака на генератор случайных чисел (RNG)
Это восходит к тенденции азартных игр или ставок в децентрализованных приложениях около 2018 и 2019 годов. Обычно разработчики используют определенные начальные значения в смарт-контрактах для генерации случайных чисел при выборе победителей во время розыгрышей. Распространенными начальными значениями являются block.number, block.timestamp, blockhash и keccak256. Однако майнеры могут полностью контролировать эти начальные значения, поэтому в некоторых случаях злоумышленные майнеры могут манипулировать переменными для получения выгоды.
Распространенные контракты игры в кости
Контракт игры в кости включает одну функцию:
- Bet(): Функция ставки, где пользователи вводят число для ставки и платят ETH. Случайное число генерируется с использованием нескольких начальных значений, и если число ставки совпадает со случайным числом, пользователь выигрывает весь призовой фонд.
:quality(80)/2024-06-12/CF4DBBA2FA1C86F7F107BA74EA02A692.png)
Контракт атаки майнера
Майнеры могут выиграть, если они предварительно вычислят выигрышное случайное число и выполнят его в том же блоке. Это включает одну функцию:
- attack(): Функция атаки ставки, где майнер предварительно вычисляет выигрышное случайное число. Поскольку она выполняется в том же блоке, blockhash(block.number - 1) и block.timestamp в одном блоке одинаковы. Затем майнер вызывает Bet() контракта игры в кости для завершения атаки.
:quality(80)/2024-06-12/8A69F0E663032EA6848A1D856541880B.png)
Решение
Использование внешних случайных чисел, предоставляемых оракульными проектами
Благодаря сервисам, предоставляемым оракульными проектами, такими как Chainlink, случайные числа вводятся в онлайн-контракты для обеспечения случайности и безопасности. Однако оракульные проекты также несут риски централизации, что обусловливает необходимость более зрелых оракульных сервисов.
4. Атака повторного воспроизведения
Атака повторного воспроизведения включает в себя повторное инициирование транзакции с использованием ранее использованной подписи для кражи средств. Одной из самых известных атак повторного воспроизведения за последние годы была кража 20 миллионов токенов $OP у маркет-мейкера Wintermute на Optimism, что представляло собой межсетевую атаку повторного воспроизведения. Поскольку мультиподписной кошелек Wintermute был временно развернут только в основной сети Ethereum, хакер использовал подпись транзакции для развертывания Wintermute мультиподписного адреса на Ethereum, чтобы повторно выполнить ту же транзакцию в сети Optimism, тем самым получив контроль над мультиподписным кошельком на Optimism. Мультиподписной кошелек по сути является смарт-контрактным аккаунтом, что также демонстрирует значительную разницу между SCA и EOA. Для EOA обычному пользователю нужен только один закрытый ключ для контроля всех адресов на Ethereum и совместимых с EVM цепочках (строки адресов полностью идентичны), в то время как SCA эффективен только в одной цепочке после развертывания.
Конкретная логика
Здесь мы приводим пример типичной атаки повторного воспроизведения (атака повторного воспроизведения в одной цепочке). У Билла есть смарт-кошелек, который требует ввода его электронной подписи перед выполнением каждой транзакции. Теперь, когда хакер Люси украла электронную подпись Билла, она может инициировать неограниченное количество транзакций для опустошения смарт-кошелька Билла.
Пример
Контракт с уязвимостями состоит из трех функций:
- checkSig(): Функция проверки ECDSA, обеспечивающая, что результат проверки соответствует изначально установленному подписанту.
- getMsgHash(): Функция генерации хэша, которая объединяет to и amount для формирования хэша.
- transfer(): Функция перевода, позволяющая пользователям выводить средства из пула ликвидности. Из-за отсутствия ограничений на подпись, одна и та же подпись может быть использована повторно, позволяя хакерам постоянно похищать средства.
:quality(80)/2024-06-12/CAE52DE4E3B750CE9F99FA9A503FF0FC.png)
Решение
Включите nonce в комбинацию подписи, чтобы предотвратить атаки повторного воспроизведения. Принцип работы параметра заключается в следующем:
- nonce: Он описывает переменную количества транзакций EOA в сети блокчейн. Он имеет порядок и уникальность. С каждой дополнительной транзакцией значение nonce будет увеличиваться на 1. Сеть блокчейн проверит, соответствует ли nonce транзакции текущему nonce аккаунта. Таким образом, хакер потерпит неудачу, если попытается использовать уже использованную подпись, поскольку значение nonce в комбинации подписи будет меньше текущего значения nonce EOA.
:quality(80)/2024-06-12/546F169E51761020D9CAE39776A22424.png)
5. Атака типа "отказ в обслуживании" (DoS)
Атака типа "отказ в обслуживании" (DoS) не является чем-то новым в традиционном мире Web2. Она относится к любому вмешательству в работу сервера, такому как отправка большого количества мусорной или разрушительной информации, затрудняющей или полностью уничтожающей доступность. Аналогичным образом, смарт-контракты страдают от таких атак, которые по сути направлены на то, чтобы вызвать сбой в работе смарт-контракта.
Конкретная логика
Рассмотрим пример. Проект A проводит публичное предложение токенов протокола, где все пользователи могут вносить средства в пул ликвидности (Смарт-Контракт) для покупки квот по принципу "первым пришел - первым обслужен", а избыточные средства будут возвращены участникам. Хакер Алиса использует атакующий контракт для участия в публичном предложении. Как только пул ликвидности попытается вернуть средства в атакующий контракт Алисы, будет запущена атака DoS, предотвращающая реализацию действия возврата. В результате, большое количество средств оказывается заблокированным в смарт-контракте.
Пример публичного контракта
Пример
Публичный контракт включает две функции:
- deposit(): функция депозита, записывающая адрес вкладчика и внесенную сумму.
- refund(): функция возврата средств, с помощью которой команда проекта возвращает средства инвесторам.
:quality(80)/2024-06-12/04757B0F8779FB0CE77147D719A58350.png)
Контракт атаки DoS
Контракт атаки DoS включает одну функцию:
- attack(): Несмотря на то, что это функция атаки, в ней нет никаких проблем. Основная проблема заключается во встроенной в контракт Hacker функции обратного вызова receive() для платежей, которая включает проверку исключений. Любой внешний контракт, переводящий средства на контракт Hacker, вызовет исключение через revert(), тем самым предотвращая завершение операции.
:quality(80)/2024-06-12/95E6D076B9A174AAE43B1EA6F4CCBD2E.png)
Решения
1. Избегать застревания критически важной функциональности при вызове внешних контрактов
Вывести require(success, "Refund Fail!"); из вышеуказанной функции refund() контракта PublicSale, обеспечивая возможность продолжения операции возврата даже в случае неудачи возврата на отдельный адрес.
2. Разделение
В вышеуказанной функции refund() контракта PublicSale позволить пользователям самостоятельно запрашивать возврат средств, а не распределять возвраты, тем самым минимизируя ненужные взаимодействия с внешними контрактами.
6. Атака permit
При атаке с разрешением Аккаунт A заранее предоставляет подпись для определенной стороны, а затем Аккаунт B, получив эту подпись, может осуществлять авторизованные переводы токенов для кражи определенного количества токенов. Здесь мы в основном обсуждаем две распространенные функции авторизации токенов в смарт-контрактах: approve() и permit().
В обычном контракте ERC20 Аккаунт A может вызвать функцию approve(), чтобы авторизовать определенное количество токенов для Аккаунта B, позволяя последнему переводить эти токены от первого. Кроме того, функция permit() была введена в контракты ERC20 в EIP-2612, а в ноябре 2022 года Uniswap выпустил новый стандарт авторизации токенов, Permit2.
Конкретная логика
Вот пример. Однажды Билл просматривал новостной сайт о блокчейне, когда внезапно появилось всплывающее окно подписи Metamask. Поскольку многие блокчейн-сайты или приложения используют подписи для проверки входа пользователей, Билл не придал этому особого значения и сразу же выполнил подпись. Через пять минут его активы в Metamask были исчерпаны. Затем Билл обнаружил в блокчейн-обозревателе, что неизвестный адрес инициировал транзакцию permit(), за которой последовала транзакция transferFrom(), опустошившая его кошелек.
Пример
Две функции выглядят следующим образом:
- approve(): Стандартная функция авторизации, где Аккаунт A авторизует определенную сумму средств для Аккаунта B.
- permit(): Функция авторизации подписи, где Аккаунт B отправляет и завершает проверку подписи для получения авторизованной суммы от Аккаунта A. Параметры включают владельца, предоставляющего авторизацию, получателя авторизации, авторизованную сумму, срок действия подписи и данные подписи владельца v, r и s.
:quality(80)/2024-06-12/AA5ADC472E3C1598E95C5E120D2D6961.png)
:quality(80)/2024-06-12/E3724BA4E50B55EAFB83EA6E5BD3784F.png)
Решения
1. Обращайте внимание на каждую подпись в онлайн-взаимодействиях
Несмотря на меры, предпринимаемые некоторыми кошельками для декодирования и отображения информации о подписи авторизации approve(), они практически не предупреждают о фишинге подписи permit(), что увеличивает риск атак. Поэтому настоятельно рекомендуется тщательно проверять каждую неизвестную подпись, чтобы убедиться, направлена ли она на функцию permit().
2. Отделите кошелек для регулярного взаимодействия от кошелька для хранения активов
Это чрезвычайно важно для пользователей криптовалют, особенно для охотников за аирдропами, поскольку они ежедневно взаимодействуют с бесчисленными децентрализованными приложениями или веб-сайтами и подвержены ловушкам. Хранение только небольшой суммы средств в кошельке для регулярного взаимодействия может удержать потери в управляемых пределах.
7. Атака "Медовая ловушка"
В блокчейн-индустрии атака "медовая ловушка" относится к типу вредоносных токен-контрактов, развернутых проектными командами. Контракт предоставляет разрешение на продажу только проектной команде, в то время как обычные пользователи могут только покупать, но не продавать, тем самым неся убытки.
Конкретная логика
Вот пример. В объявлении в Telegram Проект A информирует пользователей о том, что токен был развернут в основной сети и доступен для торговли. Поскольку токен можно только покупать, но нельзя продавать, цена сначала продолжала расти, и пользователи, боясь упустить возможность, продолжали покупать. Через некоторое время, когда пользователи обнаруживают, что не могут продать, проектная команда использует эту возможность и сбрасывает токены, вызывая обвал цены.
Пример
Основная функция:
- _beforeTokenTransfer(): Внутренняя функция, вызываемая во время передачи токенов, которая может быть успешно выполнена только при вызове владельцем; вызовы с других аккаунтов будут неудачными.
:quality(80)/2024-06-12/173238C63F95AECEDB818ECEC82A16CF.png)
Решение
Использование инструментов сканирования безопасности
a. Token Sniffer для токенов Ethereum
b. Ave Check для токенов на других блокчейнах
c. Рыночные веб-сайты со встроенными инструментами обнаружения, такие как Dextools
Избегайте торговли токенами с низкими оценками.
8. Атака опережения (Front-Running)
Опережение изначально возникло на традиционных финансовых рынках, где асимметрия информации позволяла финансовым посредникам получать прибыль, предпринимая быстрые действия на основе специфической отраслевой информации. В индустрии блокчейна опережение в основном связано с опережением в цепочке, которое включает в себя манипулирование майнерами для приоритетного включения своих собственных транзакций в блокчейн с целью получения прибыли.
В области блокчейна майнеры могут получать прибыль, манипулируя транзакциями, которые они включают в блоки, например, исключая определенные транзакции и изменяя порядок транзакций. Такая прибыль может быть измерена с помощью Извлекаемой Ценности Майнера (MEV). Прежде чем транзакция пользователя добавляется в основную сеть Ethereum, большинство транзакций агрегируются в мемпуле. Майнеры ищут в этом мемпуле транзакции с более высокими ценами на газ и приоритетно включают их, чтобы максимизировать свою прибыль. Как правило, транзакции с более высокими ценами на газ легче включаются майнерами. Между тем, некоторые MEV-боты также сканируют мемпул на предмет прибыльных транзакций.
Конкретная логика
Вот пример. Билл обнаруживает новый популярный токен со значительными колебаниями цены. Чтобы обеспечить успех транзакций с токенами на Uniswap, Билл устанавливает исключительно широкий диапазон проскальзывания. К сожалению, MEV-бот Алисы обнаруживает эту транзакцию в мемпуле и оперативно увеличивает комиссию за газ, инициируя транзакцию покупки перед транзакцией Билла и вставляя транзакцию продажи после транзакции Билла в том же блоке. После подтверждения блока это приводит к значительным потерям от проскальзывания для Билла, в то время как Алиса получает прибыль от арбитражной операции покупки по низкой цене и продажи по высокой.
Пример
Функция выглядит следующим образом:
- solve(): Функция угадывания, где любой может отправить ответ, и если отправленный ответ совпадает с целевым ответом, отправитель может получить 10 эфиров.
:quality(80)/2024-06-12/A38769A9E35B9AE8FD287D2474A2A7AD.png)
- Процесс: Билл находит правильный ответ.
- Алиса мониторит мемпул, ожидая, когда кто-нибудь отправит правильный ответ.
- Билл вызывает solve() для отправки ответа и устанавливает цену газа на уровне 100 Gwei.
- Алиса видит транзакцию, отправленную Биллом, и обнаруживает ответ. Она устанавливает более высокую цену газа, чем у Билла - 200 Gwei, и вызывает solve().
- Транзакция Алисы упаковывается майнером раньше, чем транзакция Билла.
- Алиса выигрывает награду в 10 эфиров.
Решение
Три основные функции выглядят следующим образом:
- commitSolution(): Функция для отправки результатов, помещающая отправленный пользователем ответ solutionHash, Время выпуска commitTime и состояние revealed в структуру Commit.
- getMySolution(): Функция для получения результатов, позволяющая пользователям просматривать свои отправленные ответы и связанную информацию, включая отправленный пользователем ответ solutionHash, Время выпуска commitTime и состояние revealed.
- revealSolution(): Функция для получения наград за угадывание головоломки, позволяющая пользователям получать награды после предоставления ответа и установленного ими пароля.
:quality(80)/2024-06-12/CE3A062F1FD436DDA8B4D97AEAC4454F.png)
:quality(80)/2024-06-12/6CF29DE911C98547EF0A5A88DB33A765.png)
Процесс:
- Билл находит правильный ответ.
- Билл вызывает функцию commitSolution() для отправки правильного ответа.
- В следующем блоке Билл вызывает функцию revealSolution(), предоставляя ответ и установленный им пароль для получения вознаграждения.
В функции commitSolution() Билл отправляет зашифрованную строку, сохраняя открытый текст только для себя. На этом этапе также записывается время блока отправки commitTime. Затем, в функции revealSolution(), проверяется время блока, чтобы предотвратить опережение в пределах одного блока. Поскольку вызов revealSolution() требует предоставления ответа в открытом виде, этот шаг направлен на предотвращение обхода commitSolution() другими участниками и прямого вызова revealSolution(). После успешной проверки, если ответ окажется правильным, будет распределено вознаграждение.
Заключение
Смарт-контракты играют решающую роль в технологии блокчейн и предлагают множество преимуществ. Во-первых, они обеспечивают децентрализованное автоматизированное выполнение, гарантируя безопасность и надежность транзакций без участия третьих сторон. Во-вторых, смарт-контракты сокращают промежуточные этапы и затраты, повышая эффективность транзакций.
Несмотря на множество преимуществ, смарт-контракты также подвержены риску атак, которые могут привести к финансовым потерям пользователей. Поэтому для пользователей блокчейна важно выработать некоторые привычки. Во-первых, пользователи всегда должны тщательно выбирать dApps для взаимодействия и внимательно изучать код контракта и связанные с ним правила. Кроме того, им следует регулярно обновлять и использовать безопасные кошельки и инструменты для взаимодействия с контрактами, чтобы снизить риск хакерских атак. Более того, рекомендуется хранить свои средства на нескольких адресах, чтобы минимизировать потенциальные потери от атак на контракты.
Для игроков отрасли обеспечение безопасности и стабильности смарт-контрактов имеет равное значение. В первую очередь следует усилить аудит смарт-контрактов для выявления и устранения потенциальных уязвимостей и рисков безопасности. Во-вторых, участники отрасли должны быть в курсе последних разработок в области блокчейна, связанных с атаками на контракты, и принимать соответствующие меры безопасности. И последнее, но не менее важное: они также должны улучшать образование пользователей и повышать их осведомленность в области безопасности с точки зрения правильного использования смарт-контрактов.
В заключение, при согласованных усилиях как пользователей, так и участников отрасли, риски безопасности, связанные со смарт-контрактами, могут быть значительно снижены. Пользователи всегда должны тщательно выбирать контракты и защищать личные активы, в то время как участники отрасли должны усилить аудит контрактов, быть в курсе технологических достижений и улучшать образование пользователей и их осведомленность в области безопасности. Вместе мы будем способствовать безопасному и надежному развитию смарт-контрактов.
Ссылки:
Solidity на примерах:https://solidity-by-example.org/
Знания о блокчейне от SlowMist:https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4ODQ3NTM2OA==&action=getalbum&album_id=1378673890158936067&scene=173&from_msgid=2247498135&from_itemidx=1&count=3&nolastread=1#wechat_redirect
Chainlink - 10 лучших практик безопасности DeFi:https://blog.chain.link/defi-security-best-practices/#post-title
WTF - Solidity 104 Безопасность контрактов:https://www.wtf.academy/solidity-104/
Уязвимости в смарт-контрактах DeFi в 4 категориях и 38 сценариях:https://www.weiyangx.com/381670.html
OpenZeppelin:https://github.com/OpenZeppelin/