weird-erc20

Blockchain / Web3

Коллекция нестандартных ERC20-реализаций, нарушающих стандарт: токены без возврата значения из transfer(), с комиссией при переводе, с blocklist, pausable, reentrant, с изменяемым балансом. Незаменима для аудиторов DeFi-протоколов.

Добавлен 15.07.2026 · Обновлён 15.07.2026 · Blockchain / Web3
Установка
git clone https://github.com/d-xo/weird-erc20
cd weird-erc20 && forge install

# Просмотр всех категорий weird токенов:
ls src/
# TransferFee.sol, BlockList.sol, Pausable.sol,
# ReentrantToken.sol, ReturnsFalse.sol, ...

# Тестирование протокола против weird токенов
forge test -vvv
переведено ИИ

Странные токены ERC20

Этот репозиторий содержит минимальные примеры реализации токенов ERC20 на языке Solidity, поведение которых может быть неожиданным или непредсказуемым. Все токены в этом репозитории основаны на реальных токенах, многие из которых ранее использовались для эксплуатации систем смарт-контрактов. Предполагается, что эти примеры реализаций будут полезны разработчикам и аудиторам.

Спецификация ERC20 настолько свободно определена, что сводится лишь к объявлению интерфейса, и даже несколько семантических требований, которые она накладывает, систематически нарушаются разработчиками токенов в реальном мире.

Это делает создание смарт-контрактов, взаимодействующих напрямую с токенами ERC20, сложной задачей, мягко говоря. Разработчикам смарт-контрактов, как правило, следует по умолчанию придерживаться следующих шаблонов при необходимости взаимодействия с внешним кодом:

  1. Контрактный уровень разрешающего списка известных хороших токенов.
  2. Прямое взаимодействие с токенами следует выполнять в специализированных контрактах-обертках на границе системы. Это позволяет ядру полагаться на согласованную и известную хорошую семантику поведения внешних активов.

В некоторых случаях вышеуказанные шаблоны непрактичны (например, в случае безразрешительного AMM, поддержание ончейн-списка разрешений потребует введения централизованного контроля или сложной системы управления). В таких случаях разработчикам следует проявлять большую осторожность, выполняя эти взаимодействия в highly defensive манере. Следует отметить, что даже если ончейн-список разрешений невозможен, внесписочный список в официальном пользовательском интерфейсе также может защитить неподготовленных пользователей от токенов, нарушающих ожидания контракта, при сохранении бесконтрактного уровня бесконтрольности.

Наконец, если вы создаете токен, настоятельно рекомендуется избегать следующих видов поведения.

Дополнительные ресурсы

Токены

Реентрантные вызовы

Некоторые токены позволяют реентрантные вызовы при передаче (например, токены ERC777).

Это использовалось в реальном мире в нескольких случаях (например, пул imBTC в Uniswap был опустошен, lendf.me был опустошен).

пример: Reentrant.sol

Отсутствующие возвращаемые значения

Некоторые токены не возвращают bool (например, USDT, BNB, OMG) в методах ERC20. Смотрите здесь полный (хоть и несколько устаревший) список.

Некоторые токены (например, BNB) могут возвращать bool для некоторых методов, но не делать этого для других. Это привело к застреванию токенов BNB в Uniswap v1 (подробности).

Некоторые особенно патологические токены (например, Tether Gold) объявляют возврат bool, но затем возвращают false, даже если передача была успешной (код).

Хорошая абстракция безопасной передачи (пример) может в некоторой степени помочь, но учтите, что наличие Tether Gold делает невозможным правильную обработку возвращаемых значений для всех токенов.

Представлены два примера токенов:

  • MissingReturns: не возвращает bool ни для какой операции ERC20.
  • ReturnsFalse: объявляет возврат bool, но затем возвращает false для каждой операции ERC20.

пример: MissingReturns.sol пример: ReturnsFalse.sol

Комиссия при передаче

Некоторые токены берут комиссию за передачу (например, STA, PAXG), некоторые в настоящее время не берут комиссию, но могут сделать это в будущем (например, USDT, USDC).

Комиссия за передачу STA была использована для вывода $500k из нескольких пулов Balancer (подробнее).

пример: TransferFee.sol

Модификации баланса вне передач (rebasing/airdrops)

Некоторые токены могут вносить произвольные изменения в баланс вне передач (например, токены с rebasing в стиле Ampleforth, airdrops токенов управления в стиле Compound, mintable/burnable токены).

Некоторые системы смарт-контрактов кэшируют балансы токенов (например, Balancer, Uniswap-V2), и произвольные модификации базовых балансов могут означать, что контракт работает с устаревшей информацией.

В случае Ampleforth, некоторые пулы Balancer и Uniswap имеют специальную обработку, чтобы гарантировать, что кэшированные балансы пула обновляются атомарно как часть процедуры rebasing (подробности).

пример: TODO: реализовать rebasing токен

Обновляемые токены

Некоторые токены (например, USDC, USDT) обновляемы, что позволяет владельцам токенов вносить произвольные изменения в логику токена в любой момент времени.

Изменение семантики токена может сломать любой смарт-контракт, зависящий от прошлого поведения.

Разработчикам, интегрирующим обновляемые токены, следует рассмотреть возможность добавления логики, которая будет замораживать взаимодействие с соответствующим токеном в случае обнаружения обновления (например, адаптер TUSD, используемый MakerDAO).

пример: Upgradable.sol

Токены с возможностью Flash Minting

Некоторые токены (например, DAI) позволяют так называемый "flash minting", который позволяет чеканить токены только на время одной транзакции при условии их возврата в контракт токена к концу транзакции.

Это похоже на flash loan, но не требует, чтобы токены, которые нужно занять, существовали до начала транзакции. Токен, который может быть отчеканен через flash minting, потенциально может иметь общую эмиссию, равную uint256.

Документация по модулю flash mint MakerDAO доступна здесь.

Токены со списком блокировки

Некоторые токены (например, USDC, USDT) имеют управляющий администратором контракта список блокировки адресов. Если адрес заблокирован, передача на этот адрес и с него запрещена.

Злонамеренные или скомпрометированные владельцы токенов могут заморозить средства в контракте, добавив адрес контракта в список блокировки. Это может быть результатом регуляторных действий против самого контракта, против отдельного пользователя контракта (например, LP в Uniswap), или также может быть частью попытки шантажа пользователей заблокированного контракта.

пример: BlockList.sol

Токены с возможностью приостановки

Некоторые токены могут быть приостановлены администратором (например, BNB, ZIL).

Аналогично проблеме со списком блокировки выше, функция приостановки под контролем администратора подвергает пользователей токена риску со стороны злонамеренного или скомпрометированного владельца токена.

пример: Pausable.sol

Защита от гонки одобрений (Approval Race Protections)

Некоторые токены (например, USDT, KNC) не позволяют одобрять сумму M > 0, когда уже одобрена существующая сумма N > 0. Это сделано для защиты от вектора атаки ERC20, описанного здесь.

Этот PR показывает некоторые проблемы, возникающие в реальном мире из-за этой проблемы.

пример: Approval.sol

Revert при одобрении нулевого адреса

Некоторые токены (например, OpenZeppelin) будут делать revert, если пытаться одобрить нулевой адрес для траты токенов (т.е. при вызове approve(address(0), amt)).

Интеграторам может потребоваться добавить специальные случаи для обработки этой логики при работе с таким токеном.

пример: ApprovalToZeroAddress.sol

Revert при одобрении нулевого значения

Некоторые токены (например, BNB) делают revert при одобрении суммы с нулевым значением (т.е. при вызове approve(address, 0)).

Интеграторам может потребоваться добавить специальные случаи для обработки этой логики при работе с таким токеном.

пример: ApprovalWithZeroValue.sol

Revert при передаче нулевого значения

Некоторые токены (например, LEND) делают revert при передаче суммы с нулевым значением.

пример: RevertZero.sol

Множественные адреса токенов

Некоторые проксированные токены имеют несколько адресов. В качестве примера рассмотрите следующий фрагмент. Функция rescueFunds предназначена для того, чтобы позволить владельцу контракта вернуть токены, не относящиеся к пулу, которые случайно были отправлены на контракт. Однако она предполагает, что у токена только один адрес, и поэтому позволит владельцу украсть все средства в пуле.

mapping isPoolToken(address => bool);
constructor(address tokenA, address tokenB) public {
  isPoolToken[tokenA] = true;
  isPoolToken[tokenB] = true;
}
function rescueFunds(address token, uint amount) external nonReentrant onlyOwner {
    require(!isPoolToken[token], "access denied");
    token.transfer(msg.sender, amount);
}

пример: Proxied.sol

Низкая точность (Low Decimals)

Некоторые токены имеют низкую точность (например, USDC имеет 6 знаков после запятой). Еще более крайний случай — некоторые токены, такие как Gemini USD, имеют всего 2 знака после запятой.

Это может привести к большей, чем ожидается, потере точности.

пример: LowDecimals.sol

Высокая точность (High Decimals)

Некоторые токены имеют более 18 знаков после запятой (например, YAM-V2 имеет 24).

Это может вызвать непредвиденные откаты из-за переполнения, создавая риск нарушения работоспособности контракта.

пример: HighDecimals.sol

transferFrom при src == msg.sender

Некоторые реализации токенов (например, DSToken) не пытаются уменьшить allowance (разрешение) вызывающего, если отправитель совпадает с вызывающим. Это придает transferFrom ту же семантику, что и transfer, в данном случае. Другие реализации (например, OpenZeppelin, Uniswap-v2) пытаются уменьшить allowance вызывающего от отправителя в transferFrom, даже если вызывающий и отправитель — это один и тот же адрес, придавая transfer(dst, amt) и transferFrom(address(this), dst, amt) разную семантику в этом случае.

примеры:

Предоставлены примеры обеих семантик:

Метаданные не в формате string

Некоторые токены (например, MKR) имеют поля метаданных (name / symbol), закодированные как bytes32 вместо string, предписанного спецификацией ERC20.

Это может вызвать проблемы при попытке чтения метаданных этих токенов.

пример: Bytes32Metadata.sol

Откат при переводе на нулевой адрес

Некоторые токены (например, openzeppelin) откатываются при попытке перевода на address(0).

Это может нарушить системы, которые рассчитывают на возможность сжигания токенов переводом их на address(0).

пример: RevertToZero.sol

Отсутствие отката при ошибке

Некоторые токены не откатываются при ошибке, а вместо этого возвращают false (например, ZRX, EURS).

Хотя технически это соответствует стандарту ERC20, это противоречит общепринятым практикам написания кода на solidity и может быть упущено разработчиками, которые забывают оборачивать вызовы transfer в require.

пример: NoRevert.sol

Откат при больших одобрениях и переводах

Некоторые токены (например, UNI, COMP) откатываются, если значение, переданное в approve или transfer, больше uint96.

Оба вышеуказанных токена имеют специальную логику в approve, которая устанавливает allowance в type(uint96).max, если сумма одобрения равна uint256(-1), что может вызвать проблемы в системах, которые ожидают, что значение, переданное в approve, будет отражено в отображении allowances.

пример: Uint96.sol

Инъекция кода через имя токена

Было обнаружено, что некоторые вредоносные токены включают вредоносный javascript в свой атрибут name, позволяя злоумышленникам извлекать приватные ключи у пользователей, которые выбирают взаимодействовать с этими токенами через уязвимые фронтенды.

Это использовалось для эксплуатации пользователей etherdelta (ссылка).

Нестандартная функция Permit

Некоторые токены (DAI, RAI, GLM, STAKE, CHAI, HAKKA, USDFL, HNY) имеют реализацию permit(), которая не следует EIP2612. Токены, которые не поддерживают permit, могут не откатываться, что может привести к выполнению последующих строк кода в непредвиденных сценариях. Permit2 от Uniswap может предоставить более совместимую альтернативу.

пример: DaiPermit.sol

Перевод суммы меньшей, чем amount

Некоторые токены (например, cUSDCv3) содержат специальный случай для amount == type(uint256).max в своих функциях перевода, который приводит к переводу только баланса пользователя.

Это может вызвать проблемы в системах, которые переводят пользовательскую сумму amount на свой контракт, а затем начисляют пользователю такое же значение в хранилище (например, системы типа Vault), не проверяя фактическую переведенную сумму.

ERC-20 представление нативной валюты

Некоторые блокчейны имеют ERC-20 представление своей нативной валюты, чтобы избежать необходимости в отдельном контракте-обертке. При обзоре протоколов, которые взаимодействуют как с нативной валютой, так и с ERC-20 токенами, предназначенными для развертывания в этих конкретных блокчейнах, всегда учитывайте этот сценарий и оценивайте правильность функционирования протокола для защиты от проблем с двойным расходованием или других уязвимостей, связанных с этой особенностью.

Примеры таких блокчейнов включают: * Celo с CELO (адрес 0x471EcE3750Da237f93B8E339c536989b8978a438). * Polygon с POL (адрес 0x0000000000000000000000000000000000001010). * zkSync Era с ETH (адрес 0x000000000000000000000000000000000000800A).

Это привело к критической уязвимости в Uniswap V4 в сети CELO.

Комментарии
Войдите, чтобы оставить комментарий