Разработка системы автоматического деплоя на несколько сетей

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1306 услуг
Разработка системы автоматического деплоя на несколько сетей
Средний
~3-5 дней
Часто задаваемые вопросы

Направления блокчейн-разработки

Этапы блокчейн-разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1285
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    902
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1121
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    858

Разработка системы автоматического деплоя на несколько сетей

Проблема возникает на третьем или четвёртом деплое одного и того же протокола в разные сети: кто-то задеплоил на Arbitrum с другим значением константы, на Base использовалась другая версия OpenZeppelin, адреса прокси не сохранились нормально, и теперь непонятно что где задеплоено. Multichain auto-deploy решает именно это — воспроизводимость и трекинг деплоев.

Детерминированные адреса через CREATE2

Главное требование к мультичейн деплою: одинаковые адреса на всех сетях. Это упрощает пользовательский опыт, документацию и кросс-чейн интеграции.

CREATE2 позволяет вычислить адрес контракта до деплоя:

address = keccak256(0xff ++ deployerAddress ++ salt ++ keccak256(bytecode))[12:]

Если deployer имеет одинаковый адрес на всех EVM-сетях (через Nick's Factory или собственный deployer через deterministicDeploy), байткод одинаков, salt одинаков — адрес будет одинаков везде.

Foundry поддерживает это нативно:

// deploy script
function run() external {
    bytes32 salt = keccak256("MyProtocol_v1.0.0");
    
    vm.broadcast();
    address proxy = factory.deployProxy(
        address(implementation),
        salt,
        abi.encodeCall(MyContract.initialize, (owner, params))
    );
    
    console.log("Deployed at:", proxy);
    console.log("Chain:", block.chainid);
}
# Деплой на несколько сетей
forge script script/Deploy.s.sol --rpc-url arbitrum --broadcast
forge script script/Deploy.s.sol --rpc-url base --broadcast
forge script script/Deploy.s.sol --rpc-url optimism --broadcast

Проблема: если байткод отличается между сетями (например, hardcoded chainId или address), CREATE2-адрес будет другим. Нужно избегать compile-time константных адресов в байткоде.

Структура деплой-системы

Конфигурация сетей

Единый источник истины для всех сетей в deploy.config.ts:

export interface NetworkConfig {
  chainId: number;
  rpcUrl: string;
  deployer: string;         // адрес деплоера
  gasPrice?: bigint;        // override для сетей с нестабильным gas
  confirmations: number;    // сколько блоков ждать
  verifier?: "etherscan" | "blockscout" | "none";
  verifierUrl?: string;
  nativeCurrency: string;
  contracts: {
    // адреса зависимых контрактов в этой сети
    usdc?: string;
    weth?: string;
    uniswapRouter?: string;
  };
}

export const networks: Record<string, NetworkConfig> = {
  arbitrum: {
    chainId: 42161,
    rpcUrl: process.env.ARBITRUM_RPC!,
    deployer: DEPLOYER_ADDRESS,
    confirmations: 1,
    verifier: "etherscan",
    nativeCurrency: "ETH",
    contracts: {
      usdc: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
      weth: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
    },
  },
  base: {
    chainId: 8453,
    rpcUrl: process.env.BASE_RPC!,
    deployer: DEPLOYER_ADDRESS,
    confirmations: 1,
    verifier: "blockscout",
    verifierUrl: "https://base.blockscout.com/api",
    nativeCurrency: "ETH",
    contracts: {
      usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      weth: "0x4200000000000000000000000000000000000006",
    },
  },
};

Artifacts и state management

После каждого деплоя нужно сохранять адреса контрактов. Foundry сохраняет в broadcast/ директорию, но это неудобно для multichain трекинга.

Лучший подход — генерировать deployments.json:

interface DeploymentRecord {
  network: string;
  chainId: number;
  contracts: Record<string, {
    address: string;
    implementationAddress?: string;
    abi: string;            // IPFS хеш или путь
    deployedAt: number;     // block number
    txHash: string;
    version: string;        // semver
  }>;
  deployedBy: string;
  timestamp: string;
}

Этот файл коммитится в репозиторий — является единственным источником правды об адресах. CI/CD обновляет его после каждого деплоя.

Деплой скрипт с retry и verification

import { createPublicClient, createWalletClient, http } from "viem";

async function deployWithRetry(
  network: NetworkConfig,
  contractName: string,
  deployFn: () => Promise<`0x${string}`>,
  maxRetries = 3
): Promise<`0x${string}`> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const address = await deployFn();
      
      // Ждём подтверждений
      const client = createPublicClient({ transport: http(network.rpcUrl) });
      await client.waitForTransactionReceipt({
        hash: address,
        confirmations: network.confirmations,
      });
      
      console.log(`✓ ${contractName} on ${network.chainId}: ${address}`);
      return address;
    } catch (err) {
      if (attempt === maxRetries - 1) throw err;
      console.log(`Retry ${attempt + 1}/${maxRetries}: ${err.message}`);
      await sleep(2000 * (attempt + 1));
    }
  }
  throw new Error("unreachable");
}

Автоматическая верификация контрактов

После деплоя контракты нужно верифицировать на block explorer — это обязательно для протоколов, которые хотят доверие пользователей.

Foundry:

forge verify-contract \
  --chain-id 42161 \
  --num-of-optimizations 200 \
  --compiler-version v0.8.24 \
  $CONTRACT_ADDRESS \
  src/MyContract.sol:MyContract \
  --etherscan-api-key $ARBITRUM_ETHERSCAN_KEY

Для Blockscout (Base, некоторые L2):

forge verify-contract \
  --verifier blockscout \
  --verifier-url https://base.blockscout.com/api \
  $CONTRACT_ADDRESS \
  src/MyContract.sol:MyContract

Автоматизация в скрипте: верифицировать сразу после деплоя, не ждать отдельного шага. Сохранять статус верификации в deployments.json.

CI/CD pipeline

GitHub Actions workflow

name: Deploy Protocol

on:
  push:
    tags:
      - "v*"  # деплой при тегировании релиза

jobs:
  deploy:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        network: [arbitrum, base, optimism]
      max-parallel: 1  # последовательно, чтобы не было race conditions в deployments.json
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1
      
      - name: Run tests
        run: forge test --fork-url ${{ secrets.MAINNET_RPC }}
      
      - name: Deploy to ${{ matrix.network }}
        env:
          DEPLOYER_PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY }}
          RPC_URL: ${{ secrets[format('{0}_RPC', matrix.network)] }}
        run: |
          forge script script/Deploy.s.sol \
            --rpc-url $RPC_URL \
            --private-key $DEPLOYER_PRIVATE_KEY \
            --broadcast \
            --verify
      
      - name: Update deployments.json
        run: node scripts/update-deployments.js ${{ matrix.network }}
      
      - name: Commit deployments
        uses: stefanzweifel/git-auto-commit-action@v5
        with:
          commit_message: "chore: update deployments for ${{ matrix.network }} @ ${{ github.ref_name }}"
          file_pattern: deployments.json

Управление приватными ключами в CI

Никогда не хранить приватный ключ деплоера в CI secrets как hex-строку без дополнительной защиты. Лучше:

  • AWS KMS + custom signer: ключ в KMS, signing через API. Утечка CI secrets не компрометирует ключ.
  • Hardware wallet через Frame + WalletConnect: деплой с ручным подтверждением для mainnet.
  • Отдельный деплой-кошелёк с минимальным балансом (только на gas), не связанный с treasury.

Multichain адресация в протоколе

Если контракты взаимодействуют между сетями (cross-chain вызовы), нужно управлять адресами peer-контрактов:

contract CrossChainRegistry {
    mapping(uint256 => address) public peers; // chainId => peer address
    
    function setPeer(uint256 chainId, address peer) external onlyOwner {
        peers[chainId] = peer;
        emit PeerSet(chainId, peer);
    }
    
    function _validateCrossChainSender(uint256 srcChainId, address sender) internal view {
        require(peers[srcChainId] != address(0), "Unknown chain");
        require(peers[srcChainId] == sender, "Invalid peer");
    }
}

Если используется LayerZero или Wormhole — они предоставляют собственные registry механизмы, но базовая логика та же.

Мониторинг задеплоенных контрактов

После деплоя на 5+ сетей нужно мониторить все инстансы:

// Агрегированный мониторинг через events
const deployments = require("./deployments.json");

for (const [network, data] of Object.entries(deployments)) {
  const client = createPublicClient({ transport: http(data.rpcUrl) });
  
  // Следим за критичными событиями на всех сетях
  client.watchContractEvent({
    address: data.contracts.core.address,
    abi: CORE_ABI,
    eventName: "EmergencyPause",
    onLogs: (logs) => alertOps(`PAUSE on ${network}`, logs),
  });
}

Стек и инструменты

Компонент Инструмент
Compile & test Foundry (forge, cast, anvil)
Deploy scripts TypeScript + viem или Foundry scripts
State tracking deployments.json в git
CI/CD GitHub Actions / GitLab CI
Key management AWS KMS или Ledger для mainnet
Верификация Etherscan API / Blockscout API
Мониторинг Tenderly + собственные алерты

Срок разработки системы деплоя: 1–2 недели для EVM-совместимых сетей. Добавление non-EVM сетей (Solana, TON) требует отдельного toolchain и значительно сложнее.