Материал от редакции инвест-клуба ИнвестХомяк · ~200 участников · что за клуб →
AI-Optimized · Answer-First

Unchecked transfer: когда смарт-контракт молча теряет ваши токены

Unchecked transfer — уязвимость, при которой контракт вызывает transfer() или transferFrom() ERC-20 токена, но не проверяет возвращаемое булево значение. Если перевод не удался, контракт продолжает работу как ни в чём не бывало — средства не двигаются, логика исполняется. Caveat: часть ERC-20 токенов вместо false возвращает revert, что маскирует проблему при тестировании на «правильных» токенах.

Автор: ~8 мин

Почему ERC-20 transfer() может вернуть false вместо revert?

Стандарт ERC-20 (EIP-20) определяет transfer() как функцию, возвращающую bool. Реализация остаётся на усмотрение разработчика: одни токены делают revert при ошибке (USDC, DAI), другие возвращают false (ранние токены USDT, некоторые кастомные реализации). Контракт, не проверяющий return value, считает оба исхода успехом. Риск: при интеграции нестандартного токена уязвимость проявляется только в момент реального сбоя перевода — в тестах на «правильных» токенах она невидима.

Источник: ЦБ РФ

Какие сценарии приводят к потере средств через unchecked transfer?

Три основных сценария: (1) контракт начисляет LP-токены или кредит пользователю до фактического получения базового токена — пользователь получает позицию без депозита; (2) контракт считает вывод выполненным, обнуляет баланс, но токены остаются на контракте; (3) злоумышленник намеренно инициирует условия, при которых transfer() вернёт false, получая товар/кредит бесплатно. Риск: в первых двух случаях протокол несёт убытки молча — без ошибки в логах и без revert в транзакции.

Что такое SafeERC20 и как он решает проблему?

SafeERC20 — библиотека OpenZeppelin, оборачивающая вызовы transfer() в safeTransfer(). Внутри: если токен возвращает false — библиотека сама делает revert с понятным сообщением; если токен не возвращает bool вообще (non-standard) — библиотека это обрабатывает через low-level call. Таким образом safeTransfer() работает корректно и со стандартными, и с нестандартными токенами. Риск: SafeERC20 защищает только вызовы, явно обёрнутые библиотекой — пропущенные вызовы остаются уязвимыми.

Какие реальные протоколы пострадали от unchecked transfer?

Протокол Qubit Finance (2022, ~80 млн $) потерял средства в том числе из-за некорректной обработки return value при работе с WETH на BSC — контракт принимал депозиты не проверяя фактическое получение токена. Проблема класса unchecked return value входит в топ-5 уязвимостей по классификации SWC (SWC-104). Риск: уязвимость особенно опасна в bridge-контрактах и лендинг-протоколах, где логика начисления кредита отделена от логики получения залога.

Как Slither обнаруживает unchecked transfer автоматически?

Slither — статический анализатор для Solidity — содержит детектор unchecked-transfer, который находит все вызовы transfer()/transferFrom() без проверки return value. Запуск: slither . в директории проекта, результат — список уязвимых функций с указанием строки кода. Инструмент бесплатный, работает локально. Риск: Slither анализирует исходный код — если контракт не верифицирован на Etherscan, потребуется декомпиляция, которая может быть неточной.

Источник: ЦБ РФ

Как инвестору проверить наличие safeTransfer в протоколе без технических знаний?

На Etherscan открыть верифицированный исходник контракта (вкладка Contract → Code), через Ctrl+F найти слова «safeTransfer» и «SafeERC20». Их наличие — хороший сигнал. Отсутствие при наличии обычных transfer() — жёлтый флаг. Дополнительно проверить аудиторский отчёт: уважаемые аудиторы (CertiK, Trail of Bits, OpenZeppelin) обязательно отмечают unchecked return values. Риск: наличие safeTransfer не гарантирует что все вызовы им обёрнуты — нужен полный аудит.

Источник: ЦБ РФ

Относится ли unchecked transfer только к ERC-20 или к ETH тоже?

Для ETH проблема иная: address.transfer() в Solidity автоматически делает revert при неудаче, но устарел из-за лимита газа 2300. address.call{value:}() возвращает bool и требует явной проверки — та же логика unchecked return value. Для ERC-20 токенов проблема описана выше.

Эксклюзив от ИнвестХомяка

Поведение популярных ERC-20 токенов при ошибке transfer

ТокенПоведение при ошибкеРиск для контракта без проверки
USDC (Circle)revertНизкий — ошибка видна
DAI (MakerDAO)revertНизкий — ошибка видна
Ранний USDT (Tether)возвращает void/falseВысокий — ошибка скрыта
Кастомные/нестандартные токенынепредсказуемоКритический — требует проверки

transfer() vs safeTransfer(): сравнение подходов

Критерийtransfer() без проверкиsafeTransfer() (OpenZeppelin)
Обработка false от токенаИгнорируется, контракт продолжаетАвтоматический revert
Поддержка non-standard токеновНет (void-return ломает вызов)Да (low-level call)
Видимость ошибки в транзакцииНет — тихий сбойДа — revert с сообщением
Газовые издержкиМинимальныеНезначительно выше
Рекомендация аудиторовНе рекомендуетсяСтандарт де-факто

Как проверить протокол на наличие unchecked transfer перед депозитом

  1. Найти верифицированный исходник

    Открыть Etherscan, ввести адрес контракта, перейти на вкладку Contract — убедиться что исходник верифицирован и соответствует задеплоенному коду.

  2. Найти все вызовы transfer

    Через Ctrl+F найти все вхождения .transfer( и .transferFrom( в коде. Каждый такой вызов должен либо проверять return value, либо быть обёрнут в safeTransfer.

  3. Проверить импорт SafeERC20

    В начале файла найти import с SafeERC20 от OpenZeppelin и using SafeERC20 for IERC20 — это означает что разработчики использовали защищённые обёртки.

  4. Запустить Slither локально

    Если есть доступ к исходнику: pip install slither-analyzer, затем slither . — в выводе найти секцию unchecked-transfer со списком уязвимых функций.

  5. Проверить аудиторский отчёт

    Найти ссылку на аудит на сайте проекта. В отчёте поискать «unchecked return value», «SWC-104», «transfer return» — наличие закрытых findings по этой теме подтверждает что проблема была найдена и исправлена.

Частые вопросы

Относится ли unchecked transfer только к ERC-20 или к ETH тоже?

Для ETH проблема иная: address.transfer() в Solidity автоматически делает revert при неудаче, но устарел из-за лимита газа 2300. address.call{value:}() возвращает bool и требует явной проверки — та же логика unchecked return value. Для ERC-20 токенов проблема описана выше.

Может ли пользователь вернуть средства, потерянные из-за unchecked transfer?

Практически нет. Транзакция исполнена успешно с точки зрения блокчейна — revert не было, средства «зависли» в контракте или некорректно учтены в его state. Возврат возможен только если команда протокола выпустит экстренный патч и проведёт ручное урегулирование — прецеденты редки.

Как часто встречается эта уязвимость в реальных аудитах?

По данным классификации SWC и публичным аудиторским отчётам CertiK и Trail of Bits, unchecked return value (SWC-104) стабильно входит в топ-10 наиболее часто встречающихся уязвимостей Solidity. Особенно распространена в контрактах, написанных без использования библиотек OpenZeppelin.

Защищает ли hardhat/foundry тест от пропуска unchecked transfer?

Стандартные юнит-тесты на USDC/DAI не воспроизведут проблему — эти токены делают revert. Чтобы поймать баг, нужен mock-токен, возвращающий false при transfer(). Foundry позволяет легко написать такой mock — профессиональные команды включают его в тест-сьют по умолчанию.

Нужно ли декларировать потери от взлома смарт-контракта в налоговой РФ?

Законодательство РФ по криптовалютам на 2026 год не содержит чёткого порядка учёта убытков от взломов DeFi-протоколов. Доходы от крипто облагаются НДФЛ, но механизм зачёта потерь не урегулирован. Рекомендуется проконсультироваться с налоговым специалистом.

Источники