Только в образовательных целях; не является инвестиционным советом. Инвестиции могут привести к убыткам.
Краткий ответ
delegatecall выполняет код целевого контракта в контексте вызывающего контракта. Вызывающий контракт сохраняет собственное хранилище, баланс и address(this), а msg.sender и msg.value сохраняют значения исходного вызова.
Это поведение позволяет создавать прокси, библиотеки и модули смарт-кошельков, но одновременно предоставляет делегированному коду фактические полномочия вызывающего контракта. Запись в хранилище, перевод активов, предоставление разрешения или внешний вызов выполняются от имени вызывающего контракта, а не цели, предоставившей код.
Считайте любую доступную цель delegatecall привилегированным кодом. Безопасность зависит от правил выбора цели, совместимости схем хранилища, состояния инициализации, механизмов управления обновлениями и конкретной реализации, активной для данной транзакции.
Как это работает
При обычном внешнем вызове вызываемый контракт читает и изменяет собственное хранилище. При delegatecall байт-код цели работает с хранилищем вызывающего контракта: инструкция SSTORE изменяет принадлежащий ему слот. Имена переменных цели во время выполнения не имеют значения; важны только вычисленные позиции слотов.
Это образует четыре границы, которые необходимо проверять:
- Контроль цели: определите, является ли адрес назначения фиксированным, выбирается ли пользователем, разрешается через реестр или может изменяться администратором.
- Совместимость хранилища: сравните порядок и типы переменных, наследование, резервные промежутки и именованные либо стандартизированные слоты во всех версиях реализации.
- Инициализация и авторизация: убедитесь, что инициализаторы нельзя запустить повторно, а функции обновления или управления модулями проверяют предусмотренного вызывающего субъекта и задержку управления.
- Обработка результатов: убедитесь, что ошибки передаются вызывающей стороне, а возвращённые данные декодируются как ожидаемый тип; низкоуровневые вызовы не выполняют обычные для Solidity проверки типа контракта.
ERC-1967 снижает риск коллизий в прокси, помещая адреса реализации, маяка и администратора в стандартизированные слоты вне обычного распределения компилятора. Однако стандарт не доказывает, что реализация безопасна или что авторизованное обновление безвредно.
Пример
Предположим, кошелёк хранит owner в slot 0. Плагин, скомпилированный с переменной counter в slot 0, увеличивает счётчик, когда кошелёк обращается к нему через delegatecall.
Запись изменяет значение owner кошелька, поскольку хранилище принадлежит кошельку. Если итоговое слово кодирует адрес под контролем злоумышленника, последующие проверки авторизации могут признать его владельцем, хотя плагин никогда не владел активами кошелька.
Успешная квитанция не отличает запланированные изменения состояния от вредоносных. Поэтому при симуляции транзакции следует проверять изменения хранилища, активов и разрешений, созданные события и последующие вызовы для точных адресов прокси и реализации.
Риски
- Выполнение произвольной цели: цели, контролируемые пользователем или недостаточно проверенные, могут выполнять вредоносный код с полномочиями вызывающего контракта.
- Коллизия хранилища: реализация может перезаписать владельца, балансы, состояние паузы или даже слот, определяющий следующую реализацию.
- Небезопасное обновление: скомпрометированный администратор или процесс управления может заменить ранее проверенный код после того, как пользователи внесли активы или предоставили разрешения.
- Ошибка инициализации: неинициализированный прокси или реализация могут позволить другому аккаунту присвоить привилегированные роли или настроить опасные зависимости.
- Обманчивый результат проверки: проверка только исходного кода прокси, текущей реализации или интерфейса может не выявить маяк, ожидающее обновление, реестр модулей или альтернативный путь выполнения.
Перед подписанием определите реализацию на недавнем блоке, проверьте, кто и с какой задержкой может её изменить, изучите верифицированный байт-код цели и схему хранилища, симулируйте полные calldata и сравните критичные слоты хранилища и разрешения токенов до и после выполнения. Для смарт-кошелька также проверьте, как модули включаются, отключаются и получают возможность выбирать цели.
Распространённые заблуждения
- «Цель не может затронуть активы вызывающего контракта». Делегированный код выполняется от имени вызывающего контракта и может обращаться к внешним контрактам, переводить активы или создавать разрешения, если у вызывающего контракта есть такие возможности.
- «Совпадающие имена переменных предотвращают коллизии». EVM использует слоты хранилища, а не имена в исходном коде. Порядок переменных, наследование и типы должны оставаться совместимыми.
- «Верифицированный код прокси означает, что верифицирована вся система». Активная реализация, маяк, администратор обновления, состояние инициализации и разрешения модулей представляют собой отдельные части границы доверия.
Связанные темы
- Криптоэкономическая безопасность
- RPC для приватных транзакций
- Прокси-контракт
- Смарт-контракт
- Симуляция транзакций
Источники
- Introduction to Smart Contracts - Solidity Documentation (дата обращения: 2026-08-20)
- Units and Globally Available Variables - Solidity Documentation (дата обращения: 2026-08-20)
- ERC-1967: Proxy Storage Slots - Ethereum Improvement Proposals (дата обращения: 2026-08-20)