Аудит безопасности смарт-контракта CACEIS EURXT

Источник: OpenZeppelin

Аудит безопасности смарт-контракта CACEIS EURXT

Источник: OpenZeppelin

OpenZeppelin провела аудит евро-стейблкоина EURXT от CACEIS — токена электронных денег (EMT), регулируемого MiCA. Критических, высоких или средних уязвимостей не обнаружено. Выявлено три операционных улучшения с низким уровнем риска.

•Обновлено: 6 октября 2026 г.

Краткий обзор

Тип: Стейблкоины. Сроки: 20.05.2026 → 21.05.2026. Языки: Solidity

Результаты. Всего проблем: 3 (0 исправлено). Критические: 0 (0 исправлено) · Высокие: 0 (0 исправлено) · Средние: 0 (0 исправлено) · Низкие: 3 (0 исправлено)

Примечания и дополнительная информация. 15 примечаний (0 исправлено)

Проблемы, о которых сообщил клиент. 0 проблем (0 исправлено)

Исполнительное резюме

Компания OpenZeppelin была привлечена CACEIS для проведения аудита безопасности смарт-контракта EURXT — обновляемого стейблкоина евро с одним эмитентом, разрабатываемого для выпуска в рамках системы MiCA. Целью этой работы было выявление потенциальных уязвимостей безопасности, проверка надежности бизнес-логики и обеспечение соответствия системы передовым методам для архитектур токенизации с ограниченным доступом. Проверка была строго ограничена смарт-контрактами, предназначенными для промышленной эксплуатации. В область аудита не входили макеты контрактов, модульные тесты, скрипты развертывания или скрипты инициализации прокси. Transparent Proxy и администратор прокси, управляющие обновлениями, мультиподписные кошельки и внесетевые процедуры управления, отвечающие за назначение привилегированных ролей, доверенный контракт-форвардер, работающий через платформу хранения Taurus, а также внесетевая инфраструктура расчетов, отвечающая за атрибуцию погашения и запуск сжигания, также находились вне рамок проверки.

Аудит безопасности смарт-контракта не выявил проблем критического, высокого или среднего уровня серьезности. Были отмечены три проблемы низкого уровня серьезности, каждая из которых описывает скорее операционные улучшения дизайна, чем эксплуатируемые дефекты безопасности. Эти три находки касаются минимизации рутинного использования наиболее привилегированной роли, предотвращения прямой отправки изъятых средств на адрес погашения и предотвращения выпуска токенов до настройки адреса погашения. Ни одна из этих находок не описывает путь, который внешняя сторона могла бы использовать против контракта или его держателей. Каждая из них требует, чтобы уже доверенный привилегированный оператор выполнил действие, которое руководство по эксплуатации CACEIS явно исключает из стандартных процедур, и каждое из них может быть исправлено с помощью обычных административных действий в случае возникновения. В результате, даже если эти три находки останутся в текущем виде на момент запуска, они не создают существенного риска для держателей токенов, резервов эмитента или регуляторного статуса контракта. Аудит безопасности смарт-контракта не выявил дефектов, которые позволили бы несанкционированный выпуск, несанкционированное сжигание, кражу балансов держателей, обход паузы, обход черного списка или компрометацию модели контроля доступа на основе ролей.

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

Область аудита

OpenZeppelin провела аудит реализации контракта стейблкоина EURXT. Кодовая база была предоставлена в виде ZIP-файла с хешем SHA-256 345e7f3ca1b6d2d726650af4baaf2ec3eae2df4468a7cf732b6a2920c7633951. Хеши отдельных файлов перечислены в Приложении.

В область аудита вошли следующие файлы:

src├── EURXT.sol└── library ├── LibErrors.sol └── LibModifiers.sol

Ни одна из проблем, выявленных в этом отчете, на данный момент не была устранена. CACEIS предоставила следующее заявление:

«CACEIS признает три находки низкого уровня. После тщательного анализа мы решили включить необходимые исправления в следующую версию смарт-контракта и продолжить запуск с этим релизом».

Обзор системы

EURXT (Euro eXchange Token) — это контракт стейблкоина евро с одним эмитентом от CACEIS. Контракт представляет один евро обязательств эмитента на токен, использует шесть знаков после запятой и предназначен для однократного развертывания в качестве реализации Transparent Proxy, которая делегирует вызовы единственному контракту логики EURXT. CACEIS была авторизована французским регулятором ACPR в рамках MiCA для оказания услуг с криптоактивами, и EURXT разрабатывается как регулируемый MiCA токен электронных денег (EMT), а не как стейблкоин без разрешений. На момент проверки развертывание находится на стадии до запуска, поэтому рекомендации в этом отчете даны без ограничений по обратной совместимости структуры хранилища или внешних интерфейсов.

Контракт построен на базе обновляемых контрактов OpenZeppelin v4.9.4. Он предоставляет стандартный интерфейс токена ERC-20, поддерживает одобрения на основе EIP-2612 (permit) и поддерживает метатранзакции EIP-2771, что позволяет осуществлять одобрения и переводы без газа через доверенный форвардер. Механизм экстренной паузы может остановить все перемещения токенов, а контроль доступа на основе ролей используется для ограничения каждой привилегированной операции. Администрирование ролей дополнительно ограничено на уровне сети, чтобы гарантировать, что ни одна роль не может быть непреднамеренно потеряна, требуя постоянного наличия как минимум одного держателя.

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

Каждое перемещение токенов проходит через центральный хук перевода, который обеспечивает состояние паузы, проверку черного списка отправителя (обходится во время регуляторного изъятия и не применяется к выпуску, так как у выпуска нет отправителя) и проверку черного списка получателя, которая применяется к каждому переводу, включая выпуск. Все стандартные пути перевода проходят через этот хук, включая прямые переводы, переводы на основе разрешений (allowance) и метатранзакции, передаваемые через доверенный форвардер. В результате ни один путь перевода не позволяет адресу из черного списка отправлять токены, и ни один путь перевода не позволяет контракту зачислить средства получателю из черного списка вне явного потока изъятия. Создание разрешений, будь то через прямые вызовы одобрения или EIP-2612, не проходит через этот хук, поэтому разрешения могут быть предоставлены, пока контракт приостановлен или адресом из черного списка. Ограничивается только последующий перевод. Исключение черного списка для разрешений задокументировано в контракте, а эквивалентное исключение для паузы является стандартным поведением OpenZeppelin и унаследовано без изменений.

Модель безопасности и доверительные допущения

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

DEFAULT_ADMIN_ROLE: Выполняет функции администратора всех остальных ролей. Может предоставлять, отзывать и отказываться от ролей, менять адрес погашения и обновлять URI метаданных в сети. Предоставляется при инициализации; все остальные роли предоставляются после развертывания.

Администратору доверяется следование внесетевым процедурам управления, задокументированным в политике управления доступом CACEIS при управлении ролями. В частности, следующие предположения о доверии при управлении ролями не обеспечиваются на уровне сети и зависят от дисциплины администратора:

  • Роль никогда не предоставляется адресу, который в данный момент находится в черном списке. Сетевой уровень не выполняет перекрестную проверку черного списка при предоставлении ролей, поэтому случайное предоставление может привести к тому, что операционная роль окажется у адреса, который не может ее использовать.
  • Роль никогда не предоставляется адресу, который известен как вредоносный или иным образом непригодный, включая адреса, подпадающие под санкции OFAC, адреса, связанные с подтвержденными эксплойтами, и адреса, помеченные другими применимыми санкционными или комплаенс-списками. Сетевой уровень не выполняет подобных перекрестных проверок.
  • Роль никогда не предоставляется адресу доверенного форвардера (trusted forwarder). Проверки ролей определяют вызывающую сторону через путь разрешения отправителя EIP-2771, который может вернуть сам адрес форвардера при прямых вызовах от форвардера, если calldata короче суффикса добавленного отправителя. Таким образом, форвардер, обладающий ролью, может использовать эту роль через такие вызовы с короткими calldata.
  • Роль никогда не предоставляется самому контракту, нулевому адресу или любому другому неактивному или невосстановимому адресу. Сетевой уровень разрешает такие предоставления, что привело бы к появлению владельца роли, который не может подписывать транзакции.
  • Все предоставления и отзывы ролей соответствуют требованиям к кворуму между командами, определенным в политике внесетевого управления. Функции администрирования ролей в сети проверяют только то, что вызывающая сторона обладает правами администратора для целевой роли; любая дальнейшая многосторонняя проверка является ответственностью платформы кастодиального хранения.

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

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

PAUSER_ROLE: Может приостанавливать и возобновлять любое движение токенов, включая операции по изъятию; создание разрешений (allowance) не затрагивается приостановкой. Предполагается, что лицо, использующее эту роль, применяет данную возможность только в качестве аварийного выключателя и осознает, что разрешения могут продолжать накапливаться во время окон приостановки и стать доступными для использования сразу после снятия приостановки.

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

RESCUER_ROLE: Может спасать произвольные сторонние токены ERC-20, хранящиеся на контракте, переводя их на любой ненулевой адрес. Путь спасения явно отклоняет EURXT в качестве аргумента токена, что предотвращает случайное изъятие спасателем токенов EURXT, отправленных пользователями на контракт. Предполагается, что спасатель направляет спасенные средства на законные адреса восстановления, соответствующие намерениям первоначального отправителя.

Дополнительные соображения

Доверенный форвардер (EIP-2771): Контракт поддерживает метатранзакции EIP-2771 для обеспечения выполнения транзакций операторов с оплатой газа через функциональность плательщика комиссии платформы кастодиального хранения Taurus. Адрес доверенного форвардера фиксируется в байт-коде реализации при развертывании, а не сохраняется, поэтому смена форвардера требует развертывания новой реализации и обновления прокси-контракта. CACEIS подтвердила, что форвардер ограничен внутренними адресами, контролируемыми Taurus/CACEIS, и не доступен для публичных или сторонних ретрансляторов, а сам контракт форвардера основан на эталонной реализации OpenZeppelin, которая проверяет подпись исходного отправителя перед добавлением адреса отправителя к суффиксу calldata. Таким образом, форвардер не может создавать вызовы от имени владельцев ролей; он может только ретранслировать транзакции, уже подписанные учетной записью, обладающей соответствующей ролью для целевой привилегированной функции, а стандартные проверки управления доступом продолжают ограничивать доступ на основе разрешенного исходного отправителя. К оператору форвардера применяются те же предположения о доверии, что и к владельцам привилегированных ролей, чьи метатранзакции он ретранслирует, и предполагается, что форвардер остается ограниченным внутренним набором операторов Taurus/CACEIS. Компрометация ключа оператора форвардера сама по себе не расширяет возможности выполнения привилегированных действий за пределы того, что уже подразумевает существующая модель административного доверия, поскольку базовое управление доступом по-прежнему требует, чтобы исходный подписант обладал соответствующей ролью.

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

Прокси и полномочия на обновление: Контракт предназначен для развертывания за прозрачным обновляемым прокси (Transparent Upgradeable Proxy), управляемым внешним администратором прокси. Скрипты развертывания, сам прокси, конфигурация администратора прокси и полномочия на обновление не входят в рамки данной проверки и не могут быть оценены только на основе исходного кода. Владельцы ключа администратора прокси могут обновить EURXT до произвольной логики, включая логику, которая переписывает балансы или снимает операционные ограничения, и им оказывается соответствующее доверие.

Тестовое покрытие и скрипты развертывания: В качестве материалов, подлежащих проверке, не были предоставлены набор автоматизированных тестов, скрипты развертывания или скрипты инициализации прокси. Таким образом, оценка корректности инициализации, рабочих процессов предоставления ролей и операционных проверок после развертывания опирается на процедуры, описанные в руководстве по эксплуатации CACEIS, а не на наблюдаемый код. Внутренний набор тестов, который CACEIS поддерживает для данного контракта, не был включен в область проверки, поэтому аудит не смог напрямую оценить его покрытие. Следующие области рекомендуется подтвердить на предмет их явного включения в существующий набор тестов перед развертыванием: исчерпывающие тесты контроля доступа по ролям, подтверждающие, что каждый привилегированный субъект может вызывать только те функции, которые предназначены для этой роли, и что происходит откат при любой другой попытке доступа; покрытие ветвей для каждого пути отката в контракте и его библиотеках; полное покрытие жизненного цикла процессов выпуска (mint), перевода (transfer), внесения в черный список (blacklist), изъятия (seize), сжигания (burn), приостановки (pause) и обновления (upgrade); а также интеграционные тесты, проверяющие работу доверенного ретранслятора (trusted forwarder) для каждой точки входа, доступной пользователю. Без такого покрытия регрессии, внесенные во время будущих обновлений, могут незаметно расширить поверхность атаки, доступную для любой роли, и не будут обнаружены только при аудите исходного кода.

Низкий уровень серьезности

Функции обслуживания, ограниченные ролью DEFAULT_ADMIN_ROLE, увеличивают риск компрометации ключа администратора

В шаблоне AccessControl от OpenZeppelin роль DEFAULT_ADMIN_ROLE используется для предоставления и отзыва всех остальных ролей в контракте и предназначена исключительно для администрирования ролей. В EURXT эта роль также используется для ограничения двух операций обслуживания: setRedemptionAddress, которая обновляет адрес, с которого токены могут быть сожжены во время погашения, и setContractURI, которая обновляет URI метаданных вне сети. Обе функции защищены модификатором onlyRole(DEFAULT_ADMIN_ROLE), что требует использования того же ключа, который управляет администрированием ролей, для рутинных изменений состояния.

Это отклоняется от принципа наименьших привилегий. Поскольку DEFAULT_ADMIN_ROLE управляет администрированием ролей для всего контракта, увеличение частоты, с которой ключ должен подписывать транзакции, также увеличивает его подверженность операционным ошибкам, фишингу подписывающего лица и атакам с «слепым» подписанием на аппаратных кошельках. Выделенная роль с более низкими привилегиями для этих действий позволила бы ключу администратора оставаться в «холодном» состоянии и ограничить последствия любой компрометации ключа в момент подписания только неадминистративным состоянием.

Рассмотрите возможность введения одной или нескольких ролей с более низкими привилегиями для ограничения доступа к setContractURI и setRedemptionAddress, а также ограничьте DEFAULT_ADMIN_ROLE только обязанностями по администрированию ролей.

Токены могут быть выпущены до установки адреса погашения

После вызова initialize переменная _redemptionAddress по умолчанию принимает значение address(0). В этом состоянии функция burn безусловно вызывает откат, так как она требует, чтобы from == _redemptionAddress, в то время как LibModifiers.checkNonZero(from) одновременно запрещает from == address(0). Функция mint не имеет эквивалентного предварительного условия, поэтому учетная запись с ролью MINTER_ROLE может выпускать токены до того, как будет вызвана функция setRedemptionAddress. Любые токены, выпущенные в этот период, не имеют пути погашения в сети до тех пор, пока не будет настроен адрес погашения, несмотря на то, что операционный жизненный цикл требует наличия адреса погашения до начала выпуска.

Рассмотрите возможность добавления проверки в функцию mint, которая вызывает откат, если _redemptionAddress == address(0), обеспечивая соблюдение порядка развертывания в сети, чтобы токены не могли быть выпущены до настройки адреса погашения. Альтернативно, рассмотрите возможность принятия начального адреса погашения в качестве параметра функции initialize, чтобы исключить существование такого окна.

Функция seizeBlacklistedFunds позволяет отправлять изъятые средства на адрес погашения

Функция seizeBlacklistedFunds переводит токены с адреса, находящегося в черном списке, на произвольный адрес to. Единственные ограничения, наложенные на to, заключаются в том, что он не должен быть нулевым и не должен находиться в черном списке. В частности, отсутствует проверка того, что to не равен _redemptionAddress.

_redemptionAddress является промежуточным адресом для процесса погашения: пользователи отправляют EURXT на него, а инфраструктура погашения вне сети отслеживает эти поступления для проведения фиатных выплат и последующего запуска сжигания через роль BURNER_ROLE. Отправка изъятых средств напрямую на _redemptionAddress смешивает их с бухгалтерским учетом погашений и, в зависимости от того, как система вне сети атрибутирует поступления, может спровоцировать непреднамеренную фиатную выплату или иным образом исказить учет погашений. Даже если система вне сети требует явного тикета погашения для каждого поступления, смешивание изъятых балансов с добровольными погашениями создает операционный риск, который контракт может предотвратить на уровне сети.

Рассмотрите возможность отклонения транзакции, если to == _redemptionAddress в функции seizeBlacklistedFunds. Если нормативное предписание требует уничтожения изъятых средств, оператор может сначала переместить их на казначейский адрес, контролируемый ролью BURNER_ROLE, или любое другое промежуточное место, а затем перевести их в процесс погашения через отдельный, осознанный шаг, а не как побочный эффект изъятия.

Примечания и дополнительная информация

Осиротевший блок NatSpec

Файл EURXT.sol содержит блок NatSpec в строках 424–430, который документирует функцию burn, но за ним следует заголовок раздела Storage без объявления функции под ним. Этот блок, по-видимому, является артефактом предыдущего рефакторинга или перемещения кода, из-за чего документация осталась отделенной от какого-либо кода.

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

Резерв хранилища __gap составляет 49 слотов вместо стандартных 50

В EURXT объявлены четыре переменные состояния перед зазором хранилища (storage gap), но Solidity упаковывает _seizureInProgress (bool) и _redemptionAddress (address) в один слот, так как они объявлены последовательно и вместе занимают 32 байта. Таким образом, четыре переменные занимают три слота, и в сочетании с __gap[46] контракт резервирует 49 слотов вместо 50, которые стандарт OpenZeppelin для обновляемого хранилища резервирует для каждого контракта.

Рассмотрите возможность увеличения зазора до __gap[47], чтобы область хранилища соответствовала стандартному резервированию в 50 слотов.

Функция __AccessControlEnumerable_init не вызывается в initialize

В EURXT.sol функция initialize вызывает __AccessControl_init, но не вызывает __AccessControlEnumerable_init из унаследованного AccessControlEnumerableUpgradeable. В OpenZeppelin Contracts версии 4.9.x этот инициализатор не выполняет никакой настройки, поэтому в данный момент это упущение безопасно. Если в будущей версии OpenZeppelin Contracts в __AccessControlEnumerable_init будет добавлена процедура настройки, она будет пропущена при любом последующем обновлении реализации до этой версии.

Рекомендуется вызывать __AccessControlEnumerable_init сразу после __AccessControl_init в функции initialize.

Пользовательские ошибки в операторах require

Начиная с Solidity 0.8.26, пользовательские ошибки могут использоваться внутри операторов require. Первоначальная поддержка была ограничена конвейером IR. Solidity 0.8.27 расширил эту возможность и на устаревший конвейер.

Во всей кодовой базе каждый шаблон if (...) revert ... можно эквивалентно выразить как оператор require, который вызывает откат с той же пользовательской ошибкой.

Рекомендуется заменить шаблоны if-revert на эквивалентные операторы require с использованием тех же пользовательских ошибок для краткости и небольшой экономии газа.

Отсутствие контактной информации по безопасности

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

Рекомендуется добавить комментарий NatSpec с контактной информацией по безопасности над определением каждого контракта. Рекомендуется использовать соглашение @custom:security-contact, так как оно было принято в OpenZeppelin Wizard и ethereum-lists.

Отсутствие именованных параметров в отображениях (mapping)

Начиная с Solidity 0.8.18, отображения могут включать именованные параметры в формате mapping(KeyType KeyName? => ValueType ValueName?), чтобы прояснить роль ключа и значения.

Отображение _blacklisted в EURXT не объявляет ни имени ключа, ни имени значения.

Рекомендуется добавить именованные параметры в отображения для улучшения читаемости и поддержки кодовой базы.

Неиспользуемая ошибка EmptyString в LibErrors

Ошибка EmptyString объявлена в LibErrors, но нигде в кодовой базе не вызывается для отката.

Рекомендуется удалить объявление ошибки EmptyString.

Сигнатура события ContractURIUpdated расходится со стандартом ERC-7572

Функция setContractURI генерирует событие ContractURIUpdated(string newURI), в то время как каноническая сигнатура события ERC-7572 — ContractURIUpdated() без параметров. Хеши этих двух сигнатур дают разные значения topic[0], поэтому внесетевые индексаторы, обозреватели блоков и службы обновления метаданных, подписанные на стандартное событие ERC-7572, не будут обнаруживать обновления, генерируемые этим контрактом.

Рекомендуется изменить объявление события на event ContractURIUpdated() и удалить аргумент newURI из вызова emit. Потребители, которым нужно новое значение URI, могут прочитать его через contractURI() в следующем блоке, как это предусмотрено стандартом ERC-7572.

Ошибка CannotRenounceLastRole повторно используется в revokeRole

Функция revokeRole вызывает откат с ошибкой LibErrors.CannotRenounceLastRole(role), когда вызывающий удаляет последнего владельца роли. Название звучит противоречиво в данном месте вызова, так как вызывающий отзывает роль у другой учетной записи, а не отказывается от своей собственной.

Рекомендуется переименовать ошибку в нейтральную форму, например CannotRemoveLastRoleHolder, чтобы она корректно читалась как при вызове renounceRole, так и при revokeRole.

Защита последнего участника для всех ролей ограничивает реагирование на инциденты

Защита последнего участника дублируется между revokeRole и renounceRole

Неточные строки документации (docstrings)

Отклонения от руководства по стилю Solidity

Избыточный код

  • Высокий
  • Низкий

О чём эта статья

Что-то непонятно? Спросите по статье — объясню простыми словами.

Не хотите разбираться сами? Мы поможем.