Часть III · Глава 11 из 43 · Byzantium

EIP-214: STATICCALL — вызов, который обещает ничего не трогать

Контракты постоянно вызывают другие контракты просто ради чтения значения — цены, баланса, результата view-функции. Но обычный вызов позволяет вызываемому делать что угодно, включая изменение состояния или повторный вход в вас. STATICCALL — это вызов, для которого EVM гарантирует режим только для чтения: любая попытка изменить состояние внутри него откатывается. Маленькая гарантия, на которой незаметно держатся оракулы, view-функции и защита от reentrancy.

Обновлено 13 сент. 2026 г. · 8 мин
Предполагается
  • модель вызовов EVM
  • состояние и reentrancy

Byzantium, октябрь 2017. Третье знаковое изменение Byzantium (после REVERT и zk-прекомпайлов) — новый тип вызова. Контрактам постоянно нужно спросить что-то у другого контракта — текущую цену у оракула, баланс, результат view-функции. Но в EVM «спросить» и «сделать» использовали одну и ту же инструкцию: CALL запускает код вызываемого, и этот код может делать что угодно. Так что простое чтение из контракта, которому вы не полностью доверяете, означало вручить ему ключи. EIP-214 добавляет STATICCALL: вызов, для которого протокол гарантирует, что ничего не изменится. Давайте выведем его.

1 шаг Чтение опасно

Проблема: CALL ради чтения даёт полную власть

Когда ваш контракт делает обычный CALL, чтобы получить значение, EVM выполняет код вызываемого, и ничто не мешает этому коду сделать больше, чем просто ответить. Он может выполнить SSTORE в своё собственное хранилище, испустить логи, создать контракты — или, что опаснее всего, устроить reentrancy Когда контракт, который вы вызвали, вызывает обратно вас же, прежде чем ваша первая функция завершилась, — потенциально застав вас в наполовину обновлённом состоянии. Классическая атака «опустошить хранилище». Чтение, способное запустить произвольный код, — это риск reentrancy. — вызвать обратно вас посреди выполнения, застав вас в противоречивом состоянии. Так что вызов ненадёжного контракта просто ради чтения числа был настоящей поверхностью атаки: сам акт вопроса позволял отвечающему выполнить произвольный код.

вы просто хотите прочитать — но CALL позволяет вызываемому делать что угодно ваш контракт CALL oracle.price() чтение? входит в вас повторно ↩ недоверенный контракт SSTORE · reenter · LOG может менять состояние или войти повторно — чтение стало атакой раньше нельзя было сказать «вызови это, но не меняй ничего»
Обычный CALL ради чтения значения всё равно выполняет весь код вызываемого — он может записывать состояние, испускать логи или входить обратно в ваш контракт посреди вызова. Чтение из ненадёжного контракта стало поверхностью атаки, а способа сказать «вызови это, но ничего не меняй» не было.

→ Шаг 2: вызов, который EVM принудительно делает только для чтения.

2 шаг Вызов только для чтения

STATICCALL: изменения состояния откатываются — гарантированно

STATICCALL Опкод Byzantium 0xFA. Вызывает контракт так же, как CALL, но помечает весь фрейм вызова — и каждый его вложенный вызов — как статический: любой опкод, изменяющий состояние, встреченный внутри, откатывается. В результате получается вызов, гарантированно являющийся чтением. (опкод 0xFA) выполняет вызываемого точно так же, как CALL, но с одним железным правилом: на протяжении этого фрейма и всех его вложенных вызовов любой опкод, меняющий состояние, откатывается. SSTORE, CREATE, SELFDESTRUCT, LOG, CALL с передачей значения — всё запрещено. Вызываемый может читать сколько угодно (SLOAD, вычисления, вызовы других view-функций), чтобы получить ответ, но не может изменить хранилище, испустить события или переместить средства. Так что STATICCALL — это гарантированное чтение: вызываемый не может войти обратно и изменить что-то, потому что не может изменить вообще ничего.

STATICCALL — выполнить, но запретить любое изменение состояния ваш контракт STATICCALL 0xFA read-only кадр SLOAD · view ✓ SSTORE · CREATE ✗ любая запись → revert весь call frame (и вложенные вызовы) не трогает состояние гарантированное чтение — вызываемый не может войти повторно и изменить
STATICCALL (0xFA) выполняет вызываемого во фрейме только для чтения: SLOAD и чистые вычисления допустимы, но любой опкод, меняющий состояние, — SSTORE, CREATE, LOG, передача значения — откатывается. Ограничение охватывает весь фрейм и его вложенные вызовы, так что вызываемый просто не может ничего изменить.

→ Шаг 3: что открывает гарантия только для чтения.

3 шаг Где это несущая конструкция

Выигрыш: безопасные чтения, view-функции, защита от reentrancy

Гарантированный вызов только для чтения — маленький примитив с широким охватом. Он позволяет контракту читать ненадёжный оракул, не боясь, что тот изменит состояние или войдёт обратно. Именно так Solidity обеспечивает view / pure calls Функции Solidity, помеченные как view (читают состояние) или pure (не читают ничего), вызываются через STATICCALL, так что обещание компилятора «не изменяет состояние» на самом деле обеспечивается EVM, а не просто заявляется по договорённости. на уровне EVM — компилятор компилирует вызовы view-функций как STATICCALL, так что обещание «не изменяет состояние» именно обеспечивается, а не просто декларируется. И это фундаментальный инструмент для защиты от reentrancy: статический вызов не может войти обратно и записать что-то, так что пути чтения становятся безопасными по построению.

гарантированный read-only вызов — кирпичик для… safe oracle reads read untrusted safely view / pure calls compiler-enforced reentrancy safety can't reenter + write чтение, но не запись — малая гарантия с широким охватом
Гарантированный вызов только для чтения лежит в основе безопасного чтения ненадёжных оракулов, обеспеченных EVM view/pure-функций и защиты от reentrancy — статический вызов не может войти обратно и записать что-то. Одна маленькая гарантия — широкий охват.
04 Глубже Куда двигаться дальше