CREATE2 — это вариант операции создания контракта в EVM, при котором адрес нового контракта заранее известен и детерминирован. В отличие от обычного CREATE, где адрес зависит от nonce создателя, при CREATE2 он вычисляется из фиксированного набора параметров: адреса деплоера, salt и хеша init-кода.
CREATE2 введён для того, чтобы:
- заранее знать адрес контракта, который будет создан в будущем;
- строить схемы «контракт по требованию» (counterfactual deployment), когда адрес используется до фактического деплоя;
- надёжно связывать адрес контракта с конкретным init-кодом.
CREATE vs CREATE2: как считаются адреса
При обычном создании контракта (опкод CREATE) адрес вычисляется так:
- берутся address создателя и его nonce;
- вычисляется RLP-структура RLP(sender, nonce);
- делается keccak256 от этого значения;
- адрес контракта — последние 20 байт результата.
Следствия:
- адрес зависит от истории транзакций создателя (nonce);
- заранее предсказать адрес можно, только зная точное значение nonce в момент деплоя.
Для CREATE2 формула другая:
- адрес контракта = последние 20 байт от
keccak256( 0xff, deployer, salt, keccak256(init_code) )
где:
- 0xff — фиксированный префикс-байт;
- deployer — адрес контракта/EOA, выполняющего CREATE2;
- salt — 32-байтовое значение, задаваемое при вызове;
- keccak256(init_code) — хеш кода инициализации контракта (байткод, который выполняется при создании).
Важные свойства:
- адрес не зависит от nonce;
- если не меняются deployer, salt и init_code, адрес будет всегда одинаковым;
- для любого данного набора параметров существует ровно один возможный адрес контракта.
Компоненты формулы CREATE2
- Адрес деплоера (deployer)
Это аккаунт или контракт, из которого делается создание. Он жёстко зашит в формулу, поэтому смена фабрики меняет и адрес создаваемых контрактов.
- Значение salt
32-байтовый идентификатор, который задаётся вызывающим кодом. Его можно:
- использовать как счётчик (0, 1, 2, …) для генерации серии адресов;
- привязать к какому-то внешнему идентификатору (ID пользователя, хеш данных и т.п.).
- init_code и его хеш
Это код инициализации, который:
- выполняется один раз при создании контракта;
- в конце возвращает итоговый байткод, который будет записан в хранилище кода по адресу контракта.
Любое изменение init-кода (даже один байт) меняет keccak256(init_code), а значит — и адрес результата.
Так обеспечивается важное свойство: адрес однозначно связан с ожидаемым кодом. Если init-код другой, то и адрес уже другой.
Практические сценарии использования
CREATE2 применяется в нескольких типичных паттернах:
- Фабрики контрактов (factory pattern)
Фабрика может:
- заранее вычислить адрес нового контракта по salt и init-коду;
- показать пользователю этот адрес до фактического деплоя;
- использовать его в других контрактах, ссылках, подписи оффчейн-соглашений и т.д.
- Counterfactual deployment
Контракт «существует» логически (его адрес уже известен и используется), но фактически ещё не задеплоен:
- пользователи могут подписывать сообщения, где указано «будущий адрес»;
- позже фабрика создаёт контракт по CREATE2, и все ранее собранные данные начинают работать с уже существующим адресом.
- Смарт-кошельки и account abstraction
В системах абстракции аккаунтов часто:
- адрес кошелька пользователя заранее вычисляется через CREATE2;
- пользователь может получать средства на этот адрес до того, как кошелёк развёрнут;
- при первом использовании кошелька контракт создаётся и «подхватывает» уже лежащий на адресе баланс.
- Детерминированные прокси
Шаблоны минимальных прокси (minimal proxy / clone) используют CREATE2, чтобы:
- получать повторяемые, предсказуемые адреса клонов;
- строить фронтенд- и ончейн-логику вокруг заранее известных адресов.
CREATE2 в Solidity
В Solidity поддержка CREATE2 есть на уровне языка:
- синтаксис вида
new MyContract{salt: _salt}(args)
использует именно CREATE2, а не обычный CREATE;
- компилятор сам:
- рассчитывает init-код для MyContract;
- подставляет salt и вызывает соответствующий опкод EVM.
При использовании inline-assembly разработчик может напрямую вызвать опкод CREATE2, передав:
- количество wei для деплоя;
- указатель на init-код в памяти и его длину;
- 32-байтовый salt.
Важно:
- адрес можно заранее посчитать в off-chain-коде (скриптах, фронтенде), используя ту же формулу с Keccak-256;
- нужно гарантировать, что init-код, с которым считается адрес, действительно совпадает с тем, который будет использован при деплое.
Связь с безопасностью и риски
CREATE2 открывает и специфические риски, подробно рассматриваемые в рисках CREATE2:
- возможность переиспользования адресов, если контракт был уничтожен и затем создан заново с тем же набором параметров;
- атаки, связанные с тем, что «ожидаемый код» по адресу не совпадает с фактически деплоенным (при неверных предположениях в логике);
- сложность анализа «истории адреса», когда один и тот же адрес мог принадлежать разным контрактам в разные моменты времени.
Поэтому при проектировании протоколов и фабрик контрактов важно:
- явно учитывать возможность SELFDESTRUCT и повторного деплоя;
- не полагаться только на адрес как на «вечный» идентификатор логики контракта.
