Перейти к содержимому
Все статьи
Блокчейн

Контроль доступа в смарт-контракте: Ownable, роли и мультиподпись

·30 октября 2026 г.· 11 мин чтения
Нужен смарт-контракт или dApp?Разработка смарт-контрактов

Контроль доступа в смарт-контракте решает, кто может вызывать опасные функции: выпуск токенов, паузу, вывод средств, смену параметров и обновление логики. Для одного администратора подходит Ownable2Step из OpenZeppelin, для нескольких обязанностей — AccessControl с отдельной ролью на каждую. Сами права в боевой сети должны принадлежать не личному кошельку, а мультиподписи, а для самых опасных действий — таймлоку с задержкой. Ошибки в правах — одна из самых частых причин потерь, потому что такой контракт работает ровно так, как написан: отдаёт всё тому, у кого ключ.

Что именно нужно защищать

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

ФункцияЧем опасна в чужих рукахТипичное решение
Выпуск токенов (mint)Неограниченная эмиссия, обесцениваниеРоль MINTER_ROLE у мультиподписи, лимит на выпуск
Пауза (pause)Блокировка всех пользователейОтдельная роль PAUSER_ROLE, снимать паузу — другая роль
Вывод средств из контрактаПрямая кражаМультиподпись плюс таймлок, лимиты
Смена оракула или параметровПодмена цены, ликвидацииТаймлок с задержкой, проверки диапазонов
Обновление логики (upgrade)Полная замена правилМультиподпись плюс таймлок
Выдача ролейЗахват всех остальных правАдминистратор ролей с задержкой смены

Ownable и Ownable2Step

Ownable — самый простой вариант: у контракта один владелец, функции с модификатором onlyOwner может вызвать только он. В OpenZeppelin 5 владелец передаётся в конструктор явно: constructor(address initialOwner) Ownable(initialOwner) {}. Это сделано, чтобы владельцем по ошибке не стал адрес, с которого шёл деплой.

У обычного Ownable есть неприятная особенность: transferOwnership сразу передаёт права, и опечатка в адресе отдаёт контракт в никуда. Ownable2Step добавляет подтверждение: новый владелец должен вызвать acceptOwnership(). Если адрес ошибочный, подтверждения не будет, и права останутся у старого владельца. Для боевых контрактов с одним администратором берите именно Ownable2Step.

AccessControl: роли вместо одного владельца

AccessControl позволяет завести несколько ролей, у каждой — свои держатели и свой администратор. Роль — это идентификатор вида bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");. Функция защищается модификатором onlyRole(MINTER_ROLE). Главная роль DEFAULT_ADMIN_ROLE (значение 0x00) по умолчанию управляет всеми остальными.

  • Разделяйте обязанности: тот, кто ставит паузу, не должен быть тем, кто выводит средства.
  • Давайте роли контрактам и мультиподписям, а не сотрудникам лично.
  • Фиксируйте событиями каждую выдачу и отзыв роли — OpenZeppelin делает это сам (RoleGranted, RoleRevoked).
  • Ведите реестр: какой адрес держит какую роль и почему. Ончейн это видно, но без реестра неясно, кто за адресом стоит.

AccessControlDefaultAdminRules

Расширение AccessControlDefaultAdminRules делает главную роль безопаснее. У неё всегда один держатель, передача идёт в два шага, а между запросом и принятием действует задержка, которую задают при деплое. Если ключ администратора скомпрометирован и злоумышленник пытается передать права себе, у команды есть время это заметить. Расширение также не позволяет случайно отказаться от главной роли без процедуры.

Какой вариант выбрать

СитуацияПодходитПочему
Простой контракт, один администраторOwnable2StepМинимум кода, защита от опечатки при передаче
Несколько обязанностей и исполнителейAccessControlРазделение прав, роли можно выдавать контрактам
Продукт с деньгами пользователейAccessControlDefaultAdminRules плюс мультиподписьЗадержка смены главного администратора
Опасные изменения параметров и логикиTimelockController как держатель ролиПользователи видят изменение заранее
Контракт после запуска не должен менятьсяОтказ от правНечего украсть, но и нечего исправить

Мультиподпись: кто держит ключи

Мультиподпись — это кошелёк-контракт, который исполняет транзакцию только после подписей нескольких владельцев, например 3 из 5. Самый распространённый вариант в EVM-сетях — Safe. Если держателем роли или владельцем контракта сделать мультиподпись, кража одного ключа уже не даёт контроля над контрактом.

  1. Подписанты — разные люди с разными устройствами, лучше с аппаратными кошельками.
  2. Порог выбирают так, чтобы потеря одного-двух ключей не блокировала работу, а сговор двух людей не давал контроля.
  3. Перед подписью каждый проверяет, что именно подписывает: адрес контракта, функцию, параметры.
  4. Есть регламент замены подписанта при увольнении или утере устройства.
  5. Тестовая транзакция проходит после каждого изменения состава подписантов.

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

Таймлок: время на реакцию

TimelockController из OpenZeppelin ставит операции в очередь и исполняет их не раньше заданной задержки. Мультиподпись предлагает операцию, таймлок держит её, например, двое суток, затем любой разрешённый исполнитель её выполняет. Пользователи и мониторинг видят запланированное изменение и успевают отреагировать. Для экстренной паузы таймлок обычно не используют: она должна срабатывать сразу, но снимать её стоит уже через задержку.

Права в обновляемых контрактах

Если контракт стоит за прокси, права работают через инициализатор, а не конструктор: __Ownable_init(initialOwner) или __AccessControl_init() вызываются в initialize. Отдельное право — обновление логики, оно задаётся в _authorizeUpgrade. Это самое сильное право в системе: тот, кто им владеет, может заменить все остальные проверки. Поэтому его держатель — мультиподпись с таймлоком, а не та же роль, что ставит паузу. Подробности — в статье о proxy и UUPS.

Принцип минимальных прав

Каждая роль должна давать ровно то, что нужно для её задачи, и ничего сверх. Бот, который раз в сутки начисляет награды, получает только роль начисления с лимитом суммы, а не права администратора. Если ключ бота утечёт, ущерб ограничится лимитом. Этот же принцип работает в обычной инфраструктуре: о том, как устроить ключи интеграций и их ротацию, мы писали в статье о ротации API-ключа.

Типичные ошибки

  • Владелец контракта — личный кошелёк разработчика, который «потом передаст».
  • DEFAULT_ADMIN_ROLE остался у адреса деплоя после передачи остальных ролей.
  • Функция без модификатора прав, потому что её «никто не знает». Все функции контракта видны в байт-коде.
  • Проверка прав через tx.origin вместо msg.sender.
  • Инициализатор обновляемого контракта не вызван, и права забирает первый, кто его вызовет.
  • Роль выдана внешнему контракту, который сам не защищён.

Проверка прав после деплоя

После развёртывания обязательно проверьте ончейн: кто owner(), кто держит каждую роль, нет ли прав у адреса деплоя, правильная ли задержка у таймлока. Запишите результаты в документацию проекта. О порядке деплоя и передачи прав — в статье о деплое и верификации. Изменения ролей стоит отслеживать постоянно, это часть мониторинга контрактов. Если нужна помощь со схемой прав, обращайтесь к команде разработки смарт-контрактов.

Вопросы и ответы

Чем Ownable отличается от AccessControl?

Ownable даёт один уровень прав: владелец может всё, остальные ничего. AccessControl позволяет завести несколько ролей с разными держателями и разделить обязанности.

Зачем нужен Ownable2Step?

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

Что такое мультиподпись простыми словами?

Это кошелёк, из которого транзакция уходит только после подписей нескольких владельцев, например трёх из пяти. Кража одного ключа не даёт злоумышленнику контроля.

Что будет, если вызвать renounceOwnership?

Контракт навсегда останется без владельца, и все функции с onlyOwner станут недоступны. Если отказ от прав не планируется, эту функцию лучше переопределить так, чтобы она откатывалась.

Нужен ли таймлок для небольшого проекта?

Для функций, которые меняют логику, оракулы и вывод средств, таймлок полезен даже в небольшом проекте. Он даёт пользователям и команде время заметить нежелательное изменение.

Нужна помощь с проектом?

Обсудим задачу и предложим решение — от сайта до SaaS и защиты.

Связаться