Безопасность Web3: с нуляЧасть 9 из 12SmartAlert6 мин чтения

Что мониторить после аудита: чек-лист по типам контрактов

Аудит проверяет код в один день. Чек-лист того, за чем следить после него, по типам контрактов: токен, прокси, Safe, мост или хранилище, кипер.

Аудит отвечает на один вопрос: есть ли в этом коде, в том виде, как он лежит сегодня, ошибки, которые смогли найти аудиторы. Что будет дальше, он не говорит. Ключ владельца может утечь через год. Новая реализация может выйти с пропущенным инициализатором. Подписант может одобрить то, что не читал.

Большинство случаев из части 5 и части 6 не были ошибками кода в том смысле, в каком их ищет аудит. Это был человек с нужными правами, сделавший не то, или законное изменение, сломавшее чьё-то допущение. Для этого и нужен мониторинг: не искать баги, а сообщать, что была использована власть.

Эта часть — чек-лист. Найдите свой тип контракта и включите то, что в списке. Если вы ещё ни разу не настраивали монитор, сначала прочитайте «Как настроить мониторинг смарт-контракта за 5 минут». Шаги я не повторяю, здесь сказано, на что их направить.

Правило, от которого всё считается

Возьмите двери, которые вы нашли в части 2: каждая функция, которую вправе вызвать только владелец, держатель роли или мультисиг. По каждой два вопроса.

  1. Есть ли у неё событие? Если да, ставьте алерт на любое срабатывание. Админские события на здоровом контракте редки, шума почти нет.
  2. Что я сделаю, если алерт придёт? Если ответ «ничего», вам нужен не алерт, а рычаг. Это часть 12.

Всё ниже — применение этих двух вопросов.

1. Токен (ERC-20 и подобные)

Что делает атакующий с привилегированным ключом: чеканит, ставит на паузу, вносит в чёрный список или передаёт ключ дальше.

OwnershipTransferred
ЗачемНовый владелец получает власть над всеми админскими функциями
RoleGranted / RoleRevoked
ЗачемНовый минтер или админ на токене с AccessControl
Рецептrole-granted
Свои события ролей, например MinterAdded
ЗачемСтарые токены не используют AccessControl, у них свои события. У PlayDapp так и было: алерт пришёл бы за 1,5–3 минуты до первого несанкционированного минта1
Рецептлюбое событие из ABI, Critical
Paused / Unpaused и paused()
ЗачемПауза останавливает переводы. Неожиданная пауза значит, что выключатель у кого-то ещё
Крупные переводы
ЗачемМинт и сразу сброс видны как большой вывод

Добавьте transfer-spike для необычной частоты переводов. Он сравнивает с нормой, а не с числом, см. часть 11.

2. Прокси с обновлением

Адрес остаётся, код за ним меняется (часть 3). Апгрейд — самое сильное одиночное действие.

  • Upgraded: алерт на каждое срабатывание, Critical. Рецепт proxy-upgraded.
  • AdminChanged: админ прокси — это ключ к следующему апгрейду. Если прокси выпускает это событие и оно есть в ABI, добавьте правило «любое событие».
  • Состояние, от которого зависит новый код. Апгрейд, сделанный вами самими, тоже даст Upgraded, и вы прочтёте алерт как «да, это наш». Так было с Ronin в августе 2024: плановое Upgraded было в порядке, а порог внутри контракта стал 02. Для каждого апгрейда выпишите два-три значения, которые не должны меняться, и следите за ними правилом по состоянию. Об этом часть 10.

3. Safe или другой мультисиг как владелец

Если владелец ваших контрактов — Safe, то самый внимательный присмотр нужен именно ему (часть 4).

  • Смена владельцев и порога (AddedOwner, RemovedOwner, ChangedThreshold): safe-owners-changed. У UXLINK в сентябре 2025 атакующий сначала убрал трёх владельцев, и алерт пришёл бы за 1–2,5 минуты до первого вывода3.
  • Упавшая execTransaction. Кто-то пробовал и не вышло. В SmartAlert есть правило по доле упавших транзакций к контракту: с minTxs 1 и порогом больше 0 оно срабатывает на один упавший прямой вызов. В Safe 1.3 и новее упавшая execTransaction откатывается целиком и события ExecutionFailure не выпускает, поэтому рецепт safe-transaction-failed её не увидит, а это правило увидит. У WazirX упавший вызов случился за 3–4,5 минуты до апгрейда Safe на код атакующего4.
  • Незнакомый исполнитель. Правило «разрешённые отправители» на ExecutionSuccess: вы перечисляете адреса владельцев и релееров, которым можно слать execTransaction, и правило срабатывает на любого другого. У Bybit исполнитель, которого Safe раньше не видел, появился в том же блоке, что и вывод5.

4. Мост или хранилище

Деньги лежат в одном месте, и оно — цель. Нужны и двери, и выходы.

  • Всё из списков для прокси и Safe: для шлюза и для того, кто им владеет.
  • Параметры безопасности через view-функции. Порог подписей, лимит вывода, флаг паузы, адрес стража. Если они лежат в хранилище и никакое событие об изменении не сообщает, нужно правило по состоянию. Пример — minimumVoteWeight() у Ronin, следующая часть как раз о нём.
  • Выводы в долларах. large-transfer-usd на каждый токен и treasury-below-usd на общую сумму. Второе заодно закрывает нативный ETH: у нативных переводов нет события Transfer.
  • Сам рычаг паузы. Следите за Paused и за тем, у кого ключ от неё.

5. Киперы, релееры и боты

Это не ваш контракт, а кошелёк, от которого зависит система. Когда в нём кончается газ, ничего не происходит, и событий об этом нет.

  • keeper-gas-runway: алерт, когда баланса при нынешней скорости трат хватает меньше чем на N дней. Фиксированный минимум в загруженный день срабатывает поздно, в тихий — зря.
  • low-balance-no-topup: баланс ниже минимума и нет пополнения за час, так что исправный бот автопополнения никого не будит.
  • contract-went-quiet: контракт, который обслуживает кипер, N часов ничего не получал.

Чего этот чек-лист не даёт

  • Алертов на упавший вызов админской функции. У Zoth незнакомый адрес вызвал upgradeToAndCall и упал на проверке доступа за 19,5 часа до настоящего апгрейда. Был ли это атакующий, не подтверждено. Правило на такое мы не делаем: упавший вызов стоит доли цента, сканеры перебирают админские функции у всех контрактов постоянно, и такой алерт легко заспамить. *Успешный* вызов с незнакомого адреса — настоящий сигнал, его ловит правило «разрешённые отправители» выше.
  • Атак в одной транзакции. См. часть 8.
  • Рычага. Что можно сделать при инциденте — пауза, владелец, кто может заменить код, можно ли отозвать роль — SmartAlert показывает на странице контракта. Если рычага нет, это правка устройства контракта, а не алерт. См. часть 12.

Как поймать

Минимальный набор для нового контракта, в том порядке, в каком мы бы его добавляли:

  1. Добавьте контракт (и свой Safe, и кошельки киперов) по инструкции.
  2. Включите предложенные админские правила: владелец, роли, Upgraded, пауза. Severity Critical, канал, который читают ночью.
  3. Для Safe: safe-owners-changed, правило на упавшую execTransaction и правило «разрешённые отправители» на ExecutionSuccess.
  4. Для моста или хранилища: large-transfer-usd, treasury-below-usd и по одному правилу по состоянию на каждый параметр безопасности (Pro и выше).
  5. Для киперов: keeper-gas-runway и low-balance-no-topup.
  6. Каждое правило проверьте на истории, прежде чем на него полагаться. Бэктест покажет, сколько раз оно сработало бы за последние дни.

Источники

  1. 1.PlayDapp, февраль 2024: addMinter в 13:39:23, первый несанкционированный минт в 13:45:11. Halborn, Explained: The PlayDapp hack. Время алерта — оценка SmartAlert: 12 подтверждений в Ethereum плюс опрос и проверка правила, около 2,5–4 минут. ↩
  2. 2.SmartAlert, разбор взлома моста Ronin, август 2024, проверено ончейн. ↩
  3. 3.UXLINK, 22 сентября 2025: владельцы убраны в 14:45:59, первые выводы с 14:50:59. Rekt, UXLINK. ↩
  4. 4.WazirX, 18 июля 2024: упавшая execTransaction в 06:10:47, апгрейд Safe в 06:17:47. Rekt, WazirX. Связь упавшего вызова с атакой — наш вывод по времени и calldata. ↩
  5. 5.CertiK, Bybit incident technical analysis. ↩

Попробуйте бесплатно

Достаточно адреса. Первые 14 дней действуют лимиты Pro, а правила проверяются на истории до включения.

Ещё статьи