ГЛАВНАЯ / ИНСТРУКЦИИ / CALL, STATICCALL И DELEGATECALL: КАК МЕНЯЕТСЯ КОНТЕКСТ ВЫПОЛНЕНИЯ
CALL, STATICCALL и DELEGATECALL: как меняется контекст выполнения
CALL, STATICCALL и DELEGATECALL передают управление к коду по разным правилам. Для проверки контракта важно отличать адрес, откуда взят исполняемый код, от адреса, чьё состояние он может менять. Это помогает читать proxy-вызовы, библиотеки, симуляции и низкоуровневые trace.
В EVM вызов может сменить аккаунт выполнения по-разному
В обычном CALL целевой адрес задаёт аккаунт, чьи код и контекст участвуют в следующем frame. Его собственные storage и баланс используются для выполнения. Вложенный вызов видит вызывающий контракт как msg.sender; переданное значение задаётся параметром вызова, а не наследуется автоматически.
DELEGATECALL берёт код с целевого адреса, но выполняет его в контексте вызывающего аккаунта. Поэтому storage-записи и адрес this относятся к вызывающей стороне, а msg.sender и msg.value сохраняют значения родительского вызова. Именно это поведение позволяет proxy передавать управление implementation и менять состояние proxy.
- адрес кода и адрес текущего контекста записаны отдельно
- storage относится к текущему frame
- msg.sender и msg.value проверяются по типу вызова
| Вызов | Код | Storage и address(this) | msg.sender / msg.value |
|---|---|---|---|
| CALL | Целевой аккаунт | Целевой аккаунт | Новый frame: вызывающий контракт / заданное значение |
| STATICCALL | Целевой аккаунт | Целевой аккаунт, без записи состояния | Новый frame, значение не передаётся |
| DELEGATECALL | Целевой аккаунт | Вызывающий аккаунт | Сохраняются из родительского frame |
STATICCALL запрещает побочные изменения состояния
STATICCALL выполняет код целевого аккаунта в режиме, где попытка изменить состояние приводит к отказу. Ограничение распространяется на вложенные вызовы, поэтому режим нельзя снять переходом к другому контракту. Чтение при этом допустимо, а само слово view в интерфейсе не доказывает корректность возвращённых данных.
Компилятор обычно использует STATICCALL для внешних вызовов функций view при подходящей EVM-цели. Для внутреннего анализа важно смотреть сам opcode и полный путь вызова. Библиотечные view-вызовы через DELEGATECALL имеют отдельные правила Solidity и статические ограничения компилятора, поэтому их не следует механически считать обычным STATICCALL.
У proxy состояние и логика могут находиться по разным адресам
Если адрес proxy вызывает реализацию через DELEGATECALL, implementation предоставляет код, а storage остаётся у proxy. Запись по slot в таком вызове меняет состояние proxy. Само наличие delegatecall не говорит, какая функция будет выбрана: нужно также разобрать calldata, селектор, fallback dispatcher и адрес реализации на конкретном блоке.
В обозревателе сопоставьте верхний вызов и внутренний trace: адрес инициатора, адрес proxy, implementation, opcode, calldata, storage changes и итоговый receipt. Не делайте вывод по одной странице implementation: вызов напрямую к ней и вызов через proxy имеют различный контекст.
- верхний вызов отделён от внутренних frame
- proxy и implementation не смешаны
- state changes сопоставлены с адресом контекста
Успех opcode и ожидаемый эффект – разные проверки
Низкоуровневые call, staticcall и delegatecall возвращают признак успеха и байты ответа. При ошибке вызванный frame откатывается, но вызывающая сторона может проверить false, обработать returndata или передать ошибку дальше. Если результат проигнорирован, внешняя функция иногда продолжает работу без ожидаемого эффекта.
Проверяйте, что адрес кода существует и соответствует ожидаемой версии. EVM может сообщить об успешном низкоуровневом вызове отсутствующего аккаунта; значит, одного success == true недостаточно, если наличие кода является условием операции. Сверьте формат returndata с ABI и состоянием после транзакции.
Частые вопросы
DELEGATECALL меняет storage implementation?
Нет. Код implementation работает в storage-контексте вызывающего аккаунта.
Сохраняет ли DELEGATECALL msg.sender?
Да, он сохраняет msg.sender и msg.value родительского frame.
STATICCALL только читает и всегда возвращает верные данные?
Он запрещает изменение состояния, но не подтверждает достоверность результата.
Успешный низкоуровневый вызов подтверждает, что адрес – контракт?
Нет. Проверяйте код целевого аккаунта, если его наличие важно.
Почему прямой вызов implementation отличается от вызова proxy?
У них разные адреса и storage-контексты; proxy использует собственное состояние при делегировании кода.
Чек-лист: call, staticcall и delegatecall: как меняется контекст выполнения
- opcode установлен по trace;
- адрес кода отделён от контекста исполнения;
- storage и address(this) определены;
- msg.sender и msg.value проверены по frame;
- ограничение STATICCALL учтено;
- returndata разобраны по ожидаемому ABI;
- адрес целевого кода и версия проверены на нужном блоке.
Материал подготовлен редакцией сайта и проверен 2 ноября 2026 года. Условия бирж, кошельков и блокчейн-сетей могут меняться. Перед операцией сверяйте данные в интерфейсе выбранного сервиса.