Безопасность

Как проверить proxy admin и implementation смарт-контракта

Пошаговая проверка обновляемого контракта: как найти implementation, ProxyAdmin, beacon, владельца обновления, историю Upgraded и реальные полномочия.

Как проверить proxy admin и implementation смарт-контракта

Адрес токена или DeFi-протокола может быть не самостоятельным контрактом, а proxy. Он хранит баланс и состояние, но передаёт выполнение другому адресу – implementation. Если уполномоченная сторона заменит implementation, поведение знакомого адреса изменится без миграции пользовательских активов. Поэтому отметки «контракт проверен» недостаточно: нужно понять, обновляем ли код и кто контролирует обновление.

Ниже разобран практический порядок для EVM-сетей. Он помогает проверить распространённые Transparent, UUPS и Beacon Proxy, но не охватывает все самописные конструкции. Проводите проверку на точном адресе и в правильной сети. Одинаковый адрес в Ethereum, Base или Arbitrum может содержать разный код.

Что такое proxy и implementation

Proxy принимает вызов пользователя и выполняет delegatecall к implementation. Код берётся у implementation, а storage, адрес контракта и баланс остаются у proxy. Благодаря этому приложение сохраняет один публичный адрес после обновления логики.

Implementation обычно не хранит пользовательское состояние этого экземпляра. Если открыть его напрямую, balances и owner могут выглядеть пустыми или относиться к другому контексту. Чтение функций нужно выполнять через proxy с ABI implementation. Отправлять токены или вызывать рабочие методы по адресу implementation обычно не следует.

Admin – адрес, который способен изменить реализацию в Transparent Proxy. В современных развёртываниях OpenZeppelin это часто отдельный ProxyAdmin, у которого есть собственный owner. В UUPS право обновления проверяет сама implementation, а у Beacon Proxy несколько proxy читают implementation из общего beacon. Поэтому отсутствие admin в одном месте ещё не доказывает неизменяемость.

Шаг 1. Зафиксируйте адрес и сеть

  1. Получите адрес из официального интерфейса, документации проекта или подтверждённого on-chain registry.
  2. Запишите название сети и chain ID.
  3. Откройте адрес в обозревателе именно этой сети.
  4. Убедитесь, что у адреса есть bytecode, а не только история переводов.
  5. Сохраните текущий block number, чтобы позднее можно было повторить проверку на том же состоянии.

Не полагайтесь на название токена, поисковую рекламу или визуально похожий адрес. Сначала сверьте всю строку. Подмена адреса делает бессмысленной даже технически идеальную проверку proxy.

Шаг 2. Посмотрите, распознал ли proxy обозреватель

Популярные обозреватели отмечают обнаруженный proxy и показывают ссылку на implementation. Интерфейс может объединять ABI реализации с состоянием proxy в разделе чтения контракта. Это удобная отправная точка, но не единственное доказательство: метка бывает устаревшей, proxy может быть неверифицирован, а custom pattern – не распознан.

Проверьте отдельно верификацию proxy и implementation. Verified source означает, что опубликованный исходный код воспроизводит развернутый bytecode. Он не означает аудит, отсутствие уязвимости или неизменяемость. Если implementation не верифицирован, пользователь не может нормально оценить текущую логику по обозревателю.

Сравните адрес implementation, показанный в верхнем proxy-блоке, с результатом низкоуровневого чтения. Если значения расходятся, не продолжайте финансовую операцию, пока не выясните причину. Возможны устаревший индекс, beacon-схема или нестандартная реализация.

Шаг 3. Прочитайте слоты ERC-1967

ERC-1967 закрепляет специальные storage slots, которые не пересекаются с обычными переменными Solidity. Для чтения можно использовать метод обозревателя, RPC eth_getStorageAt или проверенный инструмент анализа. Результат возвращается как 32 байта, а EVM-адрес занимает последние 20 байт.

  • Implementation slot: 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc.
  • Admin slot: 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103.
  • Beacon slot: 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50.

Запрашивайте storage у адреса proxy, а не implementation. Если implementation slot содержит ненулевой адрес контракта, это вероятная текущая реализация. Если он пуст, проверьте beacon slot. Ненулевой admin slot характерен для Transparent Proxy, но в UUPS он часто пуст, потому что авторизация обновления находится в коде реализации.

Нулевые значения во всех трёх слотах не доказывают отсутствие proxy. Минимальные clones по ERC-1167 кодируют target в bytecode, diamond хранит facets по другой схеме, а custom proxy может использовать собственный storage layout. Изучите runtime bytecode и исходный код, если стандартный путь ничего не показывает.

Шаг 4. Проверьте, что implementation действительно контракт

Полученный 20-байтовый адрес должен иметь ненулевой bytecode. Откройте его как отдельный контракт и проверьте:

  • верифицирован ли исходный код;
  • совпадает ли имя и назначение с протоколом;
  • есть ли функции upgrade, initialize, pause, mint, sweep или delegatecall;
  • не является ли найденный адрес ещё одним proxy;
  • когда контракт развернут и каким deployer;
  • есть ли публичные события предыдущих обновлений.

Proxy может вести к implementation, которая сама делегирует вызовы дальше. Повторяйте проверку до конечного исполняемого кода. Запишите всю цепочку адресов. Длинная цепочка не обязательно вредоносна, но усложняет понимание полномочий и мониторинг изменений.

Шаг 5. Разберите Transparent Proxy

В Transparent Proxy вызовы admin отделены от пользовательских. Admin способен выполнять upgrade, но не должен пользоваться обычными функциями implementation через тот же proxy. OpenZeppelin применяет отдельный ProxyAdmin, чтобы централизовать операции обновления и сделать владельца полномочий явным.

Если admin slot ведёт на контракт, откройте его и вызовите read-функцию owner(), когда ABI это позволяет. Owner может быть EOA, multisig или timelock. Затем проверьте конечных владельцев и порог подписей. Инструкция по структуре такого контроля есть в обзоре Safe.

Не останавливайтесь на надписи «ProxyAdmin». Если owner – обычный адрес, один ключ способен обновить код. Если owner – multisig, проверьте количество owners и threshold. Если owner – timelock, найдите минимальную задержку, proposer, executor и возможность отмены. Multisig из пяти адресов с порогом один не даёт полноценной коллективной защиты.

Шаг 6. Разберите UUPS Proxy

UUPS использует тот же implementation slot ERC-1967, но функция обновления находится в implementation. Admin slot может быть пуст. Ищите upgradeToAndCall, интерфейс UUPS и функцию авторизации, которая в исходном коде часто реализована через _authorizeUpgrade.

Определите, какой modifier защищает обновление. Это может быть onlyOwner, роль AccessControl, governance executor или custom условие. Затем проследите owner или role admin до конечного адреса. Проверка одной сигнатуры функции без проверки доступа ничего не говорит о реальном контроле.

У UUPS право обновления может быть отключено новой implementation, если код намеренно убирает механизм. Но пока текущая реализация допускает upgrade, обещание будущей неизменяемости не является действующим ограничением. Также нужно проверить совместимость storage layout: ошибочное обновление способно повредить balances и настройки даже без злого умысла.

Шаг 7. Разберите Beacon Proxy

Beacon Proxy не хранит implementation непосредственно. В beacon slot находится адрес beacon-контракта, а его функция implementation() возвращает текущую логику. Откройте beacon, прочитайте implementation, затем найдите owner или иную роль, которая способна изменить это значение.

Главная особенность – один beacon может обслуживать много proxy. Обновление beacon одновременно меняет код всех связанных экземпляров. Это удобно для однотипных vault или аккаунтов, но увеличивает радиус ошибки. Нужно знать не только owner, но и перечень критичных систем, которые используют тот же beacon.

Шаг 8. Проверьте историю обновлений

ERC-1967 рекомендует публиковать события Upgraded, AdminChanged и BeaconUpgraded. Найдите их в logs proxy или beacon и выстройте хронологию. Сопоставьте каждый новый implementation с транзакцией, инициатором и датой верификации кода.

Отсутствие событий не гарантирует отсутствие изменений: самописная логика способна записать slot без стандартного event. Для критичной позиции сравните storage на нескольких исторических блоках. RPC с архивным состоянием позволяет увидеть, когда значение изменилось, даже если индексатор пропустил событие.

Частые обновления сами по себе не означают мошенничество. Они могут отражать активную разработку. Но внезапный upgrade непосредственно перед крупной операцией, непроверенная реализация, новый неизвестный owner или изменение admin без timelock – повод остановиться и дождаться объяснения.

Шаг 9. Проверьте initializer и storage layout

У implementation constructor не инициализирует storage proxy. Для этого применяется отдельная функция initialize или reinitialize, обычно вызываемая при развертывании или обновлении. Если реализация осталась неинициализированной и её собственное состояние имеет значение для защиты, посторонний адрес иногда может захватить роль или повредить логику.

Проверьте, была ли initialization data передана вместе с созданием proxy, и нет ли публичного повторного initializer без защиты версии. Для последнего upgrade изучите calldata: upgradeAndCall может одновременно сменить код и выполнить миграцию состояния.

Storage layout между версиями должен оставаться совместимым. Перестановка старых переменных, изменение их типов или неправильное наследование может заставить новый код читать balances как роли и наоборот. Верификация исходника не проверяет корректность миграции автоматически.

Шаг 10. Оцените реальные полномочия

Составьте короткую карту контроля:

  1. proxy и текущая implementation;
  2. тип proxy;
  3. адрес, способный обновить код;
  4. конечные владельцы, threshold и timelock;
  5. отдельные права pause, mint, blacklist, rescue и parameter changes;
  6. история недавних обновлений;
  7. возможность пользователя выйти до исполнения upgrade.

Upgrade admin – не единственный риск. Контракт может быть неизменяемым, но передавать критичные решения внешнему registry, oracle или модулю. И наоборот, обновляемый контракт с публичным timelock и ограниченным governance может давать пользователю время на выход. Оценивайте путь влияния на активы, а не одно поле.

Что нельзя считать доказательством безопасности

  • Зелёная галочка обозревателя. Она подтверждает соответствие исходника bytecode, а не корректность логики.
  • Известное имя implementation. Proxy может указывать на изменённую или старую версию.
  • Пустой admin slot. В UUPS право обновления находится в implementation.
  • Multisig без параметров. Важны owners, threshold и возможность их замены.
  • Обещание renounce. Проверяйте фактическую транзакцию и отсутствие альтернативного upgrade path.
  • Аудит прошлой версии. После upgrade исполняется другой bytecode.
  • Успешная симуляция. Она не предсказывает будущую смену implementation.

Связь с кошельками и подписями

Обновляемым может быть не только DeFi-протокол, но и smart account. В ERC-4337 правила подписи и recovery определяет account implementation. Замена этой реализации способна добавить новый signer, изменить nonce или разрешить модулю перевод активов.

У аккаунта с EIP-7702 код делегата тоже нужно рассматривать как исполняемую реализацию. Там указатель хранится в коде EOA, а не в обычном ERC-1967 slot, поэтому проверка отличается. Перед подтверждением typed data дополнительно сверяйте поля по инструкции о проверке EIP-712.

Краткий чек-лист

  1. Сверьте сеть и полный proxy address.
  2. Прочитайте implementation, admin и beacon slots ERC-1967.
  3. Убедитесь, что найденные адреса содержат ожидаемый bytecode.
  4. Определите Transparent, UUPS, Beacon или нестандартный pattern.
  5. Найдите конечного владельца upgrade и его порог.
  6. Проверьте timelock, pause, mint и другие административные роли.
  7. Просмотрите события и исторические значения slots.
  8. Проверьте верификацию текущей implementation и initializer.
  9. Сопоставьте аудит именно с текущей версией.
  10. Повторите проверку перед крупной операцией.

Итог

Проверка proxy начинается не с чтения красивого имени контракта, а с точного адреса, сети и storage. ERC-1967 позволяет найти implementation, admin и beacon, но затем нужно пройти до конечного владельца полномочий, оценить threshold, timelock, историю upgrade и верификацию кода.

Главный вопрос звучит не «есть ли proxy», а «кто, как быстро и в каких пределах способен изменить поведение адреса, где находятся активы». Ответ на него превращает техническую метку обозревателя в понятную модель риска.