CREATE2: как один и тот же адрес контракта появляется сразу в четырёх сетях
Как работает CREATE2: формула адреса, salt и байткод, и почему контракт эскроу имеет один адрес в Ethereum, Base, Polygon и BNB Chain.
В этой статье
Если открыть контракт Coin-Trust в Ethereum, Base, Polygon и BNB Chain, адрес окажется одним и тем же: 0x6462DDDc0a3D893A7C17f94410e2d27162a390aD. Для пользователя это огромное удобство: не нужно искать «правильный» адрес для каждой сети и опасаться опечатки. Но откуда берётся такое совпадение? Ведь сети не связаны между собой.
Как адрес контракта получается обычно
В Ethereum два способа создать контракт. Классический опкод CREATE вычисляет адрес так:
адрес = последние 20 байт keccak256( отправитель ‖ nonce отправителя )Он зависит от того, кто деплоит, и сколько транзакций этот аккаунт уже отправил. В разных сетях у одного и того же кошелька разные nonce, поэтому адреса получаются разными. Выровнять их можно, только если синхронизировать историю аккаунта во всех сетях - хлопотно и ненадёжно.
CREATE2: адрес, который можно предсказать
В 2019 году (хард-форк Constantinople) появился опкод CREATE2. Его формула не зависит от nonce:
адрес = последние 20 байт keccak256( 0xff ‖ деплоер ‖ salt ‖ keccak256(initcode) )Три ингредиента:
- Деплоер - адрес, который выполняет
CREATE2. Обычно это не кошелёк, а специальный контракт-фабрика. Фабрика на одном и том же адресе существует во многих сетях. - salt - произвольное 32-байтное число, выбранное разработчиком. Меняете salt - получаете другой адрес.
- initcode - код, который выполняется при создании: байткод контракта плюс аргументы конструктора.
Все три значения известны заранее, поэтому адрес можно вычислить до развёртывания. И, главное, если во всех сетях они одинаковы, то и результат одинаков.
Условия, при которых адрес совпадёт
- Одна и та же фабрика на одном и том же адресе в каждой сети.
- Тот же salt.
- Побитово идентичный initcode. Это самое хрупкое условие: байткод и аргументы конструктора должны совпасть до последнего байта.
Третье требует дисциплины при сборке. Достаточно изменить версию компилятора, настройку оптимизатора или даже комментарий в коде, чтобы получился другой байткод и другой адрес. Вот настройки сборки нашего проекта:
solc_version = "0.8.24"
evm_version = "paris" # без PUSH0, MCOPY, TSTORE: исполнимо в любой сети
optimizer = true
optimizer_runs = 200
bytecode_hash = "none" # не вшивать хеш метаданных
cbor_metadata = falseПоследние две строки заслуживают пояснения. По умолчанию компилятор дописывает в конец байткода метаданные - в том числе хеш исходного текста, а значит, и комментариев. Любая правка комментария меняла бы байткод и адрес. Отключив метаданные, мы добились того, что правка комментариев и форматирования адрес не меняет. Это проверено: перевод всех комментариев контракта с русского на английский дал побайтно идентичный байткод.
Заодно версия EVM paris без новых опкодов делает байткод исполнимым даже в сетях, где ещё не включены свежие хард-форки.
Конструкторы и владельцы
Аргументы конструктора входят в initcode. Если в конструктор передаётся адрес владельца и он различается между сетями - адреса тоже разойдутся. Поэтому в детерминированном деплое всё, что зависит от сети, настраивают после развёртывания отдельными транзакциями: белый список токенов, передача владения. У нас так и сделано: в каждой сети включаются свои адреса USDT, USDC и DAI, но сам контракт остаётся тем же.
Как проверить, что контракты одинаковы
Адрес совпал - это уже сильный признак, но надёжнее сравнить байткод. Достаточно получить его из каждой сети и сравнить хеши:
const code = await client.getCode({ address: ESCROW });
console.log(keccak256(code)); // в каждой сети значение должно быть одинаковымМы проверили: во всех четырёх сетях байткод контракта совпадает побайтно. Теперь, если на любой из них кто-то сомневается в подлинности, он может сверить хеш.
Что одинаковый адрес не означает
Важный нюанс: совпадает адрес и код, но не состояние. Каждая сеть - отдельный мир. Сделки, созданные в Base, существуют только в Base; белый список токенов и владелец настраиваются отдельно в каждой сети. Тот же адрес в Polygon - это другой экземпляр, а не «зеркало».
Второй нюанс безопасности: на чужой сети по тому же адресу может оказаться что угодно, если контракт там не развёртывали. Поэтому сервис включает только сети, где контракт проверен, и в остальных не работает.
Зачем это нужно
- Удобство и доверие. Один адрес в документации, на сайте и в обозревателе.
- Простота интеграции. Конфигурация приложений содержит один адрес вместо четырёх.
- Предсказуемость. Можно заранее рассчитать адрес будущей сети и подготовить интерфейс.
- Новые сети. Добавление ещё одной сети - это тот же деплой тем же способом.
Риски и история
Раньше опкод CREATE2 использовали для «метаморфных» контрактов: сначала selfdestruct, затем развёртывание на том же адресе другого кода. Так можно было подменить логику, которую уже проверили. После обновления сети Dencun (EIP-6780) selfdestruct практически перестал уничтожать контракты, и этот трюк в основном утратил силу - но это пример того, почему «адрес» и «код по адресу» нужно проверять отдельно.
Что почитать дальше
Дальше - структуры данных, на которых держатся блокчейны: деревья Меркла, затем - как контракты узнают о внешнем мире: оракулы. Продолжите траекторию статьями про газ, рынок комиссий и Proof of Stake.
Читайте также
- 3 · Под капотом
EVM изнутри: стек, память, storage, calldata и стоимость газа
Как виртуальная машина Ethereum исполняет смарт-контракт: стековая архитектура, области данных, опкоды, цена операций и почему упаковка storage экономит деньги.
4 мин чтения - 3 · Под капотом
Прокси и обновляемые контракты: delegatecall, UUPS и цена гибкости
Как устроены прокси-контракты, что делает delegatecall, чем отличаются Transparent и UUPS, зачем слоты EIP-1967 и почему Coin-Trust выбрал неизменяемый контракт.
4 мин чтения
Упоминается в статьях
- Bitcoin и Ethereum: в чём разница и почему сделки строят на Ethereum
- Словарь терминов: блокчейн, кошельки, смарт-контракты и эскроу от A до Я
- Как выбрать сеть для сделки: Ethereum, Base, Polygon или BNB Chain
- Как проверить смарт-контракт перед использованием: пошаговая инструкция
- Аккаунты и состояние Ethereum: EOA, контракты и мировое состояние
- EVM изнутри: стек, память, storage, calldata и стоимость газа
- Нестандартные токены: USDT без return, fee-on-transfer, блэклисты и decimals
- Прокси и обновляемые контракты: delegatecall, UUPS и цена гибкости
- Роллапы изнутри: батчи, доказательства мошенничества, ZK, блобы и вывод средств
- Абстракция аккаунтов (ERC-4337): умные кошельки и спонсирование газа
- Тестирование смарт-контрактов: unit, fuzz, инварианты и статический анализ
Готовы попробовать?
Создайте первую сделку за пару минут. Деньги блокируются в смарт-контракте и выплачиваются по правилам, которые видны заранее.

