К основному содержимому

Введите слово из вопроса или названия статьи.

3 · Под капотом

CREATE2: как один и тот же адрес контракта появляется сразу в четырёх сетях

Как работает CREATE2: формула адреса, salt и байткод, и почему контракт эскроу имеет один адрес в Ethereum, Base, Polygon и BNB Chain.

4 мин чтенияОбновлено:
В этой статье

Если открыть контракт Coin-Trust в Ethereum, Base, Polygon и BNB Chain, адрес окажется одним и тем же: 0x6462DDDc0a3D893A7C17f94410e2d27162a390aD. Для пользователя это огромное удобство: не нужно искать «правильный» адрес для каждой сети и опасаться опечатки. Но откуда берётся такое совпадение? Ведь сети не связаны между собой.

Как адрес контракта получается обычно

В Ethereum два способа создать контракт. Классический опкод CREATE вычисляет адрес так:

text
адрес = последние 20 байт keccak256( отправитель ‖ nonce отправителя )

Он зависит от того, кто деплоит, и сколько транзакций этот аккаунт уже отправил. В разных сетях у одного и того же кошелька разные nonce, поэтому адреса получаются разными. Выровнять их можно, только если синхронизировать историю аккаунта во всех сетях - хлопотно и ненадёжно.

CREATE2: адрес, который можно предсказать

В 2019 году (хард-форк Constantinople) появился опкод CREATE2. Его формула не зависит от nonce:

text
адрес = последние 20 байт keccak256( 0xff ‖ деплоер ‖ salt ‖ keccak256(initcode) )

Три ингредиента:

  • Деплоер - адрес, который выполняет CREATE2. Обычно это не кошелёк, а специальный контракт-фабрика. Фабрика на одном и том же адресе существует во многих сетях.
  • salt - произвольное 32-байтное число, выбранное разработчиком. Меняете salt - получаете другой адрес.
  • initcode - код, который выполняется при создании: байткод контракта плюс аргументы конструктора.

Все три значения известны заранее, поэтому адрес можно вычислить до развёртывания. И, главное, если во всех сетях они одинаковы, то и результат одинаков.

Условия, при которых адрес совпадёт

  1. Одна и та же фабрика на одном и том же адресе в каждой сети.
  2. Тот же salt.
  3. Побитово идентичный initcode. Это самое хрупкое условие: байткод и аргументы конструктора должны совпасть до последнего байта.

Третье требует дисциплины при сборке. Достаточно изменить версию компилятора, настройку оптимизатора или даже комментарий в коде, чтобы получился другой байткод и другой адрес. Вот настройки сборки нашего проекта:

toml
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, но сам контракт остаётся тем же.

Как проверить, что контракты одинаковы

Адрес совпал - это уже сильный признак, но надёжнее сравнить байткод. Достаточно получить его из каждой сети и сравнить хеши:

ts
const code = await client.getCode({ address: ESCROW });
console.log(keccak256(code));    // в каждой сети значение должно быть одинаковым

Мы проверили: во всех четырёх сетях байткод контракта совпадает побайтно. Теперь, если на любой из них кто-то сомневается в подлинности, он может сверить хеш.

Что одинаковый адрес не означает

Важный нюанс: совпадает адрес и код, но не состояние. Каждая сеть - отдельный мир. Сделки, созданные в Base, существуют только в Base; белый список токенов и владелец настраиваются отдельно в каждой сети. Тот же адрес в Polygon - это другой экземпляр, а не «зеркало».

Второй нюанс безопасности: на чужой сети по тому же адресу может оказаться что угодно, если контракт там не развёртывали. Поэтому сервис включает только сети, где контракт проверен, и в остальных не работает.

Зачем это нужно

  • Удобство и доверие. Один адрес в документации, на сайте и в обозревателе.
  • Простота интеграции. Конфигурация приложений содержит один адрес вместо четырёх.
  • Предсказуемость. Можно заранее рассчитать адрес будущей сети и подготовить интерфейс.
  • Новые сети. Добавление ещё одной сети - это тот же деплой тем же способом.

Риски и история

Раньше опкод CREATE2 использовали для «метаморфных» контрактов: сначала selfdestruct, затем развёртывание на том же адресе другого кода. Так можно было подменить логику, которую уже проверили. После обновления сети Dencun (EIP-6780) selfdestruct практически перестал уничтожать контракты, и этот трюк в основном утратил силу - но это пример того, почему «адрес» и «код по адресу» нужно проверять отдельно.

Что почитать дальше

Дальше - структуры данных, на которых держатся блокчейны: деревья Меркла, затем - как контракты узнают о внешнем мире: оракулы. Продолжите траекторию статьями про газ, рынок комиссий и Proof of Stake.

Упоминается в статьях

Готовы попробовать?

Создайте первую сделку за пару минут. Деньги блокируются в смарт-контракте и выплачиваются по правилам, которые видны заранее.