Часть III · Глава 32 из 43 · Cancun · Deneb

EIP-1153: черновик, который живёт ровно одну транзакцию

Контрактам часто нужно передавать данные между вызовами внутри одной транзакции — блокировки от реентрантности, временные апрувы, флэш-бухгалтерия. Единственным общим изменяемым хранилищем было постоянное storage: ≈20 000 газа за слот, записанное на диск навсегда, хотя значение выбрасывается по окончании транзакции. EIP-1153 добавляет временное хранилище (transient storage) — та же идея, но исчезает в конце транзакции.

Обновлено 23 июн. 2026 г. · 10 мин
Предполагается
  • storage против memory
  • модель вызовов EVM

EVM даёт контракту два места для хранения данных, и ни одно не подходит для распространённой нужды. Memory (память) дешёвая, но стирается в конце каждого вызова — она не может пронести что-либо через подвызов. Storage (хранилище) переживает вызовы (и остаётся навсегда после), но это самый дорогой ресурс в EVM, потому что записывается на диск. При этом контрактам постоянно нужна третья вещь: место, куда можно спрятать данные, которые переживают вызовы, но только в рамках текущей транзакции, — блокировка от реентрантности, временный апрув, флэш-бухгалтерия в AMM. Имея под рукой только storage, они платят цену диска за стикер-напоминание. EIP-1153 добавляет ровно недостающий инструмент. Давайте его выведем.

1 шаг Платим цену диска за записку

Проблема: storage — единственное общее изменяемое хранилище

Возьмите классическую защиту от реентрантности (reentrancy guard): установите флаг locked перед внешним вызовом, снимите его после и отказывайте в повторном входе, пока он установлен. Флаг должен быть виден реентрантному вызову, поэтому он не может жить в memory — он обязан лежать в постоянное хранилище (persistent storage) Постоянное хранилище контракта вида ключ→значение, читаемое/записываемое через SLOAD/SSTORE. Оно фиксируется в state trie и живёт на диске каждого узла навсегда, поэтому запись в него — самая дорогая распространённая операция EVM. . Поэтому вы делаете SSTORE флага (≈20 000 газа, чтобы установить из нуля, ≈5 000 — чтобы обновить), выполняете работу, затем SSTORE обратно в ноль — при каждом отдельном вызове. Вы пишете на диск каждого узла, навсегда, чтобы удержать значение, чей весь полезный срок жизни — несколько микросекунд внутри одной транзакции.

блокировка от reentrancy на storage SSTORE slot = 1 20k / 5k газа защищённое тело внешний вызов SSTORE slot = 0 сброс · возврат state trie · диск хранится вечно 20k/5k газа и обращение к диску — ради значения, которое сотрётся к концу tx
Защита от реентрантности устанавливает слот storage в 1, выполняет защищённое тело, затем сбрасывает его в 0 — платя 20k/5k газа SSTORE и записывая state trie на диск ради флага, который теряет смысл в момент окончания транзакции.

Хуже того: половина с «сбросом в ноль» опирается на возвраты газа (gas refunds), чтобы отыграть часть стоимости, но EIP-3529 резко урезал и ограничил эти возвраты (ими злоупотребляли как газ-токенами). Так что паттерн дорогой, косвенный и навсегда раздувает состояние значениями, которые больше никому никогда не понадобятся.

→ Шаг 2: дать контрактам черновик.

2 шаг Черновик на одну транзакцию

Добавляем второе хранилище, которое живёт одну транзакцию

Исправление — перестать перегружать storage и добавить параллельное хранилище с другим временем жизни. временное хранилище (transient storage) Второе хранилище на контракт вида ключ→слово, введённое EIP-1153. Оно ведёт себя как storage внутри транзакции, но отбрасывается (сбрасывается в ноль) по окончании транзакции и никогда не записывается в state trie или на диск. — это привязанная к контракту карта slot → 32-байтовое слово, точно как storage, — за исключением того, что она никогда не записывается в state trie или на диск, и она обнуляется целиком по окончании транзакции. Поскольку ничего не сохраняется постоянно, не нужно платить за запись на диск, и нет постоянного следа: это черновик, который протокол выдаёт каждому контракту на время одной транзакции, а затем уничтожает.

как «tx ends» влияет на каждый store tx завершается во время tx после постоянный SSTORE → disk 42 ✓ выживает transient TSTORE → RAM 42 → 0 · стёрто та же модель слотов — отличается время жизни: transient умирает в конце tx
Постоянное storage (SSTORE/SLOAD) фиксируется на диске и хранится вечно; временное хранилище (TSTORE/TLOAD) живёт только внутри транзакции и отбрасывается по её окончании. Та же модель слотов на контракт, совершенно другое время жизни.

Именно это единственное изменение — не сохранять постоянно — делает его дешёвым. Стоимость SSTORE определяется в первую очередь диском и вызванным им ростом состояния; уберите их — и операция становится плоской записью по цене «тёплого» доступа.

→ Шаг 3: два опкода, общих для всего стека вызовов.

3 шаг TSTORE и TLOAD

Два опкода — и они общие для всех вызовов

Transient storage получает два новых опкода, зеркальных паре storage: TSTORE / TLOAD TSTORE(key, value) записывает слово во временное хранилище; TLOAD(key) читает его. Оба имеют фиксированную стоимость (100 газа, как «тёплый» доступ к storage) без механизма возвратов и оперируют собственным временным пространством имён вызывающего контракта. TSTORE(key, value) и TLOAD(key). Каждый стоит плоские 100 газа (цена «тёплого» доступа), без учёта возвратов, о котором нужно думать. И, как и постоянное storage, временное хранилище сохраняется через фреймы вызовов транзакции: если вызывающий делает TSTORE(k, v), а затем совершает подвызов, TLOAD(k) у вызываемого прочитает обратно v. Эта видимость между вызовами — вся суть: именно она позволяет реентрантному внутреннему вызову увидеть блокировку, установленную внешним фреймом, или роутеру спрятать контекст, который прочитает колбэк.

общее для всех вызовов в одной tx фрейм вызывающего TSTORE(k, v) подвызов фрейм вызываемого TLOAD(k) → v ~100 газа · на контракт · очищается в конце tx вызываемый читает то, что спрятал вызывающий — фикс. дешёвый газ, без возвратов
Вызывающий пишет через TSTORE(k, v); подвызов читает то же значение через TLOAD(k). Каждая операция стоит плоские ~100 газа в собственном пространстве имён контракта, без возвратов — и значение автоматически очищается по окончании транзакции.
Формула
gas( TSTORE 1 ) = gas( TLOAD 2 ) = 100 3
  1. 1 записать слово во временное хранилище
  2. 2 прочитать его обратно — между вызовами, в рамках одной транзакции
  3. 3 плоская «тёплая» цена; без возвратов, и диск никогда не затрагивается
Дёшево, потому что ничего не сохраняется постоянно — каждый временный слот стирается по окончании транзакции.

→ Шаг 4: заставляем его следовать правилам revert и конца транзакции.

4 шаг Revert и автоочистка

Он откатывается при revert и сам очищается по окончании транзакции

Два правила очистки делают это безопасным. Во-первых, временное хранилище подчиняется revert точно так же, как постоянное storage: если фрейм вызова реверится, каждый сделанный им TSTORE откатывается, как будто его никогда не было. (Это правило добавили в ходе стандартизации именно для того, чтобы защиты от реентрантности оставались корректными — блокировка, установленная во фрейме, который затем реверится, не должна оставаться застрявшей включённой.) Во-вторых, когда транзакция заканчивается, всё временное хранилище автоматически сбрасывается в ноль. Так что защите от реентрантности больше вообще не нужен завершающий сброс SSTORE(slot, 0) — очистка бесплатна и автоматична.

revert откатывает; конец tx стирает подвызов откатывается TSTORE(k, 9) k = 9 k = 5 ↩ откатывается при revert транзакция завершается каждый слот k = 9 k = 0 сброс SSTORE не нужен записи откатываются при revert и авто-очищаются в конце tx
TSTORE во фрейме, который реверится, отменяется — точно как storage; а по окончании транзакции каждый временный слот сам сбрасывается в ноль — так что защите не нужен явный сброс, и ничего не просачивается в следующую транзакцию.
04 Глубже Куда двигаться дальше