Разработка factory-контрактов с CREATE2 (детерминистический деплой)

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка factory-контрактов с CREATE2 (детерминистический деплой)
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1056
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка factory-контрактов с CREATE2 (детерминистический деплой)

CREATE2 — опкод EVM (EIP-1014), введённый в Constantinople hard fork. Адрес деплоимого контракта вычисляется заранее: он детерминирован от deployer address, salt и keccak256(initcode). Изменишь любой из параметров — получишь другой адрес. Это радикально меняет подход к архитектуре протоколов.

Где CREATE2 решает реальные проблемы

Counterfactual deployment. Пользователь может получить адрес своего Smart Account до деплоя. Передаёт этот адрес для получения средств — а сам контракт деплоится только при первой транзакции (вместе с ней же). EIP-4337 account abstraction полностью построен на этом паттерне: initCode в UserOperation содержит вызов factory, который через CREATE2 деплоит Account контракт на заранее известный адрес.

Uniswap V2/V3 pair addresses. Adres любого пула Uniswap V2 вычисляется off-chain через CREATE2 формулу: keccak256(abi.encodePacked(hex'ff', factory, keccak256(abi.encodePacked(token0, token1)), INIT_CODE_PAIR_HASH)). Router не хранит маппинг пар — он вычисляет адрес на лету. Это экономит тысячи операций SLOAD.

Cross-chain консистентность. Протокол деплоит контракты на 8 чейнах по одному и тому же адресу. Пользователи и интеграторы знают адрес заранее, whitelist настраивается один раз. Достигается через Arachnid's Deterministic Deployment Proxy (0x4e59b44847b379578588920cA78FbF26c0B4956C) — он же Nick's factory, задеплоен на сотнях чейнов с одинаковым адресом.

Реализация Factory с CREATE2

contract ContractFactory {
    event Deployed(address indexed contractAddress, bytes32 indexed salt);

    function deploy(bytes memory bytecode, bytes32 salt)
        external returns (address contractAddress) {
        assembly {
            contractAddress := create2(
                0,                        // value (ETH)
                add(bytecode, 0x20),      // bytecode start (skip length prefix)
                mload(bytecode),          // bytecode length
                salt                      // salt
            )
        }
        require(contractAddress != address(0), "Deploy failed");
        emit Deployed(contractAddress, salt);
    }

    function computeAddress(bytes memory bytecode, bytes32 salt)
        external view returns (address) {
        bytes32 hash = keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            keccak256(bytecode)
        ));
        return address(uint160(uint256(hash)));
    }
}

Инициализация через constructor vs initializer

При CREATE2 деплое контракт с constructor-параметрами включает параметры в initcode — тогда одинаковые параметры дадут один адрес, разные — разные. Это нормально.

Если контракт использует proxy паттерн (деплоится minimal proxy через CREATE2, а реализация — отдельно), constructor не запускается. Инициализация через initialize() функцию обязательна, и она должна быть защищена от двойного вызова (initializer modifier от OpenZeppelin или ручной флаг).

Тонкость: CREATE2 с одним salt можно выполнить только один раз — если контракт по этому адресу уже существует, деплой вернёт address(0). Если контракт был selfdestruct-нут (до Cancun EIP-6780), адрес освобождается и CREATE2 с тем же salt можно повторить. После Cancun EIP-6780 selfdestruct убирает только ETH, код остаётся — адрес не переиспользуется.

Salt design

Salt — это bytes32. Неосторожный salt открывает frontrunning: кто-то видит в mempool вашу транзакцию деплоя, берёт тот же bytecode и salt, деплоит первым на желаемый адрес. Ваша транзакция упадёт.

Защита: включать msg.sender в salt:

bytes32 salt = keccak256(abi.encodePacked(msg.sender, userProvidedSalt));

Теперь adversary с другим msg.sender получит другой адрес — ваш адрес ему недоступен.

Для протоколов, где адрес должен быть одинаковым на всех чейнах независимо от деплоера, используем фиксированный salt без msg.sender — но тогда деплой идёт через доверенный deployer (multisig или deployment script с DEPLOY_KEY).

Minimal Proxy (EIP-1167) + CREATE2

Комбинация часто используется в протоколах с тысячами инстансов (lending positions, yield vaults, game characters). Minimal proxy — 45-байтовый контракт, который делегирует все вызовы к implementation. Деплой стоит ~40K gas вместо 200K-500K для полного контракта.

function deployProxy(address implementation, bytes32 salt)
    external returns (address proxy) {
    bytes memory bytecode = abi.encodePacked(
        hex"3d602d80600a3d3981f3363d3d373d3d3d363d73",
        implementation,
        hex"5af43d82803e903d91602b57fd5bf3"
    );
    assembly {
        proxy := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
    }
    require(proxy != address(0), "Deploy failed");
    IInitializable(proxy).initialize(/* params */);
}

OpenZeppelin предоставляет Clones.cloneDeterministic(implementation, salt) — готовый wrapper над этим паттерном.

Тестирование

Foundry упрощает тестирование CREATE2: vm.computeCreate2Address(salt, keccak256(bytecode), deployer) даёт предсказуемый адрес в тесте. Проверяем:

  • Задеплоированный адрес совпадает с вычисленным computeAddress()
  • Повторный деплой с тем же salt возвращает address(0)
  • Инициализация через initialize() не вызывается дважды
  • Frontrunning salt защищён (если нужно)

Сроки

Базовый factory с CREATE2 и вычислением адресов: 2-3 дня. Factory с minimal proxy паттерном и инициализацией: 3-4 дня. Мультичейн deployment infrastructure с Arachnid proxy: 4-5 дней включая скрипты деплоя и верификацию.

Стоимость рассчитывается после уточнения требований к инфраструктуре деплоя и количеству целевых чейнов.