/** * Copyright (c) 2024-2025 Тарабанов Александр Викторович * All rights reserved. * * This software is proprietary and confidential. * Unauthorized copying, modification, or distribution is prohibited. * * For licensing inquiries: info@hb3-accelerator.com * Website: https://hb3-accelerator.com * GitHub: https://github.com/HB3-ACCELERATOR */ /* eslint-disable no-console */ // КРИТИЧЕСКИЙ ЛОГ - СКРИПТ ЗАПУЩЕН! console.log('[MULTI_DBG] 🚀 СКРИПТ DEPLOY-MULTICHAIN.JS ЗАПУЩЕН!'); console.log('[MULTI_DBG] 📦 Импортируем hardhat...'); const hre = require('hardhat'); console.log('[MULTI_DBG] ✅ hardhat импортирован'); console.log('[MULTI_DBG] 📦 Импортируем path...'); const path = require('path'); console.log('[MULTI_DBG] ✅ path импортирован'); console.log('[MULTI_DBG] 📦 Импортируем fs...'); const fs = require('fs'); console.log('[MULTI_DBG] ✅ fs импортирован'); console.log('[MULTI_DBG] 📦 Импортируем rpcProviderService...'); const { getRpcUrlByChainId } = require('../../services/rpcProviderService'); console.log('[MULTI_DBG] ✅ rpcProviderService импортирован'); console.log('[MULTI_DBG] 📦 Импортируем logger...'); const logger = require('../../utils/logger'); console.log('[MULTI_DBG] ✅ logger импортирован'); console.log('[MULTI_DBG] 📦 Импортируем deploymentUtils...'); const { getFeeOverrides, createProviderAndWallet, getNetworkInfo, createMultipleRPCConnections, createRPCConnection } = require('../../utils/deploymentUtils'); const RPCConnectionManager = require('../../utils/rpcConnectionManager'); console.log('[MULTI_DBG] ✅ deploymentUtils импортирован'); console.log('[MULTI_DBG] 📦 Импортируем nonceManager...'); const { nonceManager } = require('../../utils/nonceManager'); console.log('[MULTI_DBG] ✅ nonceManager импортирован'); // ContractVerificationService удален - используем Hardhat verify console.log('[MULTI_DBG] 🎯 ВСЕ ИМПОРТЫ УСПЕШНЫ!'); console.log('[MULTI_DBG] 🔍 ПРОВЕРЯЕМ ФУНКЦИИ...'); console.log('[MULTI_DBG] deployInNetwork:', typeof deployInNetwork); console.log('[MULTI_DBG] main:', typeof main); // Функция для получения имени сети для Hardhat из deploy_params function getNetworkNameForHardhat(chainId, params) { // Создаем маппинг chainId -> Hardhat network name const networkMapping = { 11155111: 'sepolia', 17000: 'holesky', 421614: 'arbitrumSepolia', 84532: 'baseSepolia', 1: 'mainnet', 42161: 'arbitrumOne', 8453: 'base', 137: 'polygon', 56: 'bsc' }; // Проверяем, поддерживается ли сеть в Hardhat const hardhatNetworkName = networkMapping[chainId]; if (!hardhatNetworkName) { logger.warn(`⚠️ Сеть ${chainId} не поддерживается в Hardhat`); return null; } // Проверяем, есть ли эта сеть в supported_chain_ids из deploy_params const supportedChainIds = params.supported_chain_ids || params.supportedChainIds || []; if (supportedChainIds.length > 0) { // Преобразуем supportedChainIds в числа для сравнения const supportedChainIdsNumbers = supportedChainIds.map(id => Number(id)); if (!supportedChainIdsNumbers.includes(chainId)) { logger.warn(`⚠️ Сеть ${chainId} не входит в список поддерживаемых сетей: ${supportedChainIdsNumbers.join(', ')}`); return null; } logger.info(`✅ Сеть ${chainId} найдена в списке поддерживаемых сетей`); } else { logger.info(`ℹ️ Список поддерживаемых сетей пуст, разрешаем верификацию для ${chainId}`); } logger.info(`✅ Сеть ${chainId} поддерживается: ${hardhatNetworkName}`); logger.info(`🔍 Детали сети: chainId=${chainId}, hardhatName=${hardhatNetworkName}, supportedChains=[${supportedChainIds.join(', ')}]`); return hardhatNetworkName; } // Функция для автоматической верификации DLE контракта async function verifyDLEAfterDeploy(chainId, contractAddress, constructorArgs, apiKey, params) { try { if (!apiKey) { logger.warn(`⚠️ API ключ Etherscan не предоставлен, пропускаем верификацию DLE`); return { success: false, error: 'API ключ не предоставлен' }; } if (!params.autoVerifyAfterDeploy) { logger.info(`ℹ️ Автоматическая верификация отключена, пропускаем верификацию DLE`); return { success: false, error: 'Автоматическая верификация отключена' }; } logger.info(`🔍 Начинаем верификацию DLE контракта по адресу ${contractAddress} в сети ${chainId}`); // Retry логика для верификации (до 3 попыток) const maxVerifyAttempts = 3; let verifyAttempts = 0; while (verifyAttempts < maxVerifyAttempts) { verifyAttempts++; logger.info(`🔄 Попытка верификации ${verifyAttempts}/${maxVerifyAttempts}`); try { // Используем Hardhat verify вместо старого сервиса const { exec } = require('child_process'); const { promisify } = require('util'); const execAsync = promisify(exec); // Определяем имя сети для Hardhat из deploy_params const networkName = getNetworkNameForHardhat(chainId, params); if (!networkName) { logger.warn(`⚠️ Неизвестная сеть ${chainId}, пропускаем верификацию`); return { success: false, error: `Неизвестная сеть ${chainId}` }; } logger.info(`🔧 Используем Hardhat verify для сети ${networkName}`); // Создаем временный файл с аргументами конструктора const fs = require('fs'); const path = require('path'); const tempArgsFile = path.join(__dirname, '..', '..', 'temp-constructor-args.js'); // Формируем аргументы в правильном формате для Hardhat // constructorArgs - это hex строка, нам нужны исходные аргументы // Получаем dleConfig и initializer из параметров const { generateDeploymentArgs } = require('../../utils/constructorArgsGenerator'); const { dleConfig, initializer } = generateDeploymentArgs(params); // Конвертируем BigInt значения в строки для JSON сериализации const serializableDleConfig = { name: dleConfig.name, symbol: dleConfig.symbol, location: dleConfig.location, coordinates: dleConfig.coordinates, jurisdiction: dleConfig.jurisdiction.toString(), okvedCodes: dleConfig.okvedCodes, kpp: dleConfig.kpp.toString(), quorumPercentage: dleConfig.quorumPercentage.toString(), initialPartners: dleConfig.initialPartners, initialAmounts: dleConfig.initialAmounts.map(amount => amount.toString()), supportedChainIds: dleConfig.supportedChainIds.map(id => id.toString()) }; const argsContent = `module.exports = ${JSON.stringify([serializableDleConfig, initializer], null, 2)};`; fs.writeFileSync(tempArgsFile, argsContent); try { // Выполняем Hardhat verify const command = `npx hardhat verify --network ${networkName} --constructor-args ${tempArgsFile} ${contractAddress}`; logger.info(`🔧 Выполняем команду: ${command}`); // Устанавливаем переменные окружения для Hardhat const envVars = { ...process.env, ETHERSCAN_API_KEY: apiKey, SUPPORTED_CHAIN_IDS: JSON.stringify(params.supported_chain_ids || params.supportedChainIds || []), RPC_URLS: JSON.stringify(params.rpc_urls || params.rpcUrls || {}) }; const { stdout, stderr } = await execAsync(command, { cwd: path.join(__dirname, '..', '..'), env: envVars }); if (stdout.includes('Successfully verified')) { logger.info(`✅ DLE контракт успешно верифицирован через Hardhat!`); logger.info(`📄 Вывод: ${stdout}`); return { success: true, message: 'Верификация успешна' }; } else { // Проверяем, нужно ли повторить попытку if (stderr.includes('does not have bytecode') && verifyAttempts < maxVerifyAttempts) { logger.warn(`⚠️ Контракт еще не проиндексирован, ждем 5 секунд...`); await new Promise(resolve => setTimeout(resolve, 5000)); continue; } logger.error(`❌ Ошибка верификации: ${stderr || stdout}`); return { success: false, error: stderr || stdout }; } } finally { // Удаляем временный файл if (fs.existsSync(tempArgsFile)) { fs.unlinkSync(tempArgsFile); } } } catch (error) { // Проверяем, нужно ли повторить попытку if (error.message.includes('does not have bytecode') && verifyAttempts < maxVerifyAttempts) { logger.warn(`⚠️ Контракт еще не проиндексирован, ждем 5 секунд...`); await new Promise(resolve => setTimeout(resolve, 5000)); continue; } logger.error(`❌ Ошибка при верификации DLE контракта: ${error.message}`); return { success: false, error: error.message }; } } // Если все попытки исчерпаны logger.error(`❌ Верификация не удалась после ${maxVerifyAttempts} попыток`); return { success: false, error: 'Верификация не удалась после всех попыток' }; } catch (error) { logger.error(`❌ Критическая ошибка при верификации DLE контракта: ${error.message}`); return { success: false, error: error.message }; } } async function deployInNetwork(chainId, pk, initCodeHash, targetDLENonce, dleInit, params, dleConfig, initializer, etherscanKey) { try { const { ethers } = hre; // Получаем RPC URL для данной сети const rpcService = require('../../services/rpcProviderService'); const rpcUrl = await rpcService.getRpcUrlByChainId(chainId); if (!rpcUrl) { throw new Error(`RPC URL не найден для chainId ${chainId}`); } // Используем новый менеджер RPC с retry логикой const { provider, wallet, network } = await createRPCConnection(chainId, pk, { maxRetries: 3, timeout: 30000 }); const net = network; // DEBUG: базовая информация по сети try { const calcInitHash = ethers.keccak256(dleInit); logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} rpc=${rpcUrl}`); logger.info(`[MULTI_DBG] wallet=${wallet.address} targetDLENonce=${targetDLENonce}`); logger.info(`[MULTI_DBG] initCodeHash(provided)=${initCodeHash}`); logger.info(`[MULTI_DBG] initCodeHash(calculated)=${calcInitHash}`); logger.info(`[MULTI_DBG] dleInit.lenBytes=${ethers.getBytes(dleInit).length} head16=${dleInit.slice(0, 34)}...`); } catch (e) { logger.error('[MULTI_DBG] precheck error', e?.message || e); } // 1) Используем NonceManager для получения актуального nonce let current = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 }); logger.info(`[MULTI_DBG] chainId=${chainId} current nonce=${current} (target was ${targetDLENonce})`); // Если текущий nonce больше целевого, обновляем targetDLENonce if (current > targetDLENonce) { logger.info(`[MULTI_DBG] chainId=${chainId} current nonce ${current} > targetDLENonce ${targetDLENonce}, updating target`); targetDLENonce = current; logger.info(`[MULTI_DBG] chainId=${chainId} updated targetDLENonce to: ${targetDLENonce}`); } // Если текущий nonce меньше целевого, выравниваем его if (current < targetDLENonce) { logger.info(`[MULTI_DBG] chainId=${chainId} starting nonce alignment: ${current} -> ${targetDLENonce} (${targetDLENonce - current} transactions needed)`); } else { logger.info(`[MULTI_DBG] chainId=${chainId} nonce already aligned: ${current} = ${targetDLENonce}`); } // 2) Выравниваем nonce если нужно (используем NonceManager) if (current < targetDLENonce) { logger.info(`[MULTI_DBG] chainId=${chainId} aligning nonce from ${current} to ${targetDLENonce}`); try { current = await nonceManager.alignNonceToTarget( wallet.address, rpcUrl, chainId, targetDLENonce, wallet, { gasLimit: 21000, maxRetries: 5 } ); logger.info(`[MULTI_DBG] chainId=${chainId} nonce alignment completed, current nonce=${current}`); // Зарезервируем nonce в NonceManager nonceManager.reserveNonce(wallet.address, chainId, targetDLENonce); logger.info(`[MULTI_DBG] chainId=${chainId} ready for DLE deployment with nonce=${current}`); } catch (error) { logger.error(`[MULTI_DBG] chainId=${chainId} nonce alignment failed: ${error.message}`); throw error; } } else { logger.info(`[MULTI_DBG] chainId=${chainId} nonce already aligned at ${current}`); } // 2) Проверяем баланс перед деплоем const balance = await provider.getBalance(wallet.address, 'latest'); const balanceEth = ethers.formatEther(balance); logger.info(`[MULTI_DBG] chainId=${chainId} wallet balance: ${balanceEth} ETH`); if (balance < ethers.parseEther('0.01')) { throw new Error(`Insufficient balance for deployment on chainId=${chainId}. Current: ${balanceEth} ETH, required: 0.01 ETH minimum`); } // 3) Деплой DLE с актуальным nonce logger.info(`[MULTI_DBG] chainId=${chainId} deploying DLE with current nonce`); const feeOverrides = await getFeeOverrides(provider); let gasLimit; try { // Оцениваем газ для деплоя DLE const est = await wallet.estimateGas({ data: dleInit, ...feeOverrides }).catch(() => null); // Рассчитываем доступный gasLimit из баланса const balance = await provider.getBalance(wallet.address, 'latest'); const effPrice = feeOverrides.maxFeePerGas || feeOverrides.gasPrice || 0n; const reserve = hre.ethers.parseEther('0.005'); const maxByBalance = effPrice > 0n && balance > reserve ? (balance - reserve) / effPrice : 3_000_000n; const fallbackGas = maxByBalance > 5_000_000n ? 5_000_000n : (maxByBalance < 2_500_000n ? 2_500_000n : maxByBalance); gasLimit = est ? (est + est / 5n) : fallbackGas; logger.info(`[MULTI_DBG] chainId=${chainId} estGas=${est?.toString?.()||'null'} effGasPrice=${effPrice?.toString?.()||'0'} maxByBalance=${maxByBalance.toString()} chosenGasLimit=${gasLimit.toString()}`); } catch (_) { gasLimit = 3_000_000n; } // Вычисляем предсказанный адрес DLE с целевым nonce (детерминированный деплой) let predictedAddress = ethers.getCreateAddress({ from: wallet.address, nonce: targetDLENonce }); logger.info(`[MULTI_DBG] chainId=${chainId} predicted DLE address=${predictedAddress} (nonce=${targetDLENonce})`); // Проверяем, не развернут ли уже контракт const existingCode = await provider.getCode(predictedAddress); if (existingCode && existingCode !== '0x') { logger.info(`[MULTI_DBG] chainId=${chainId} DLE already exists at predictedAddress, skip deploy`); // Проверяем и инициализируем логотип для существующего контракта if (params.logoURI && params.logoURI !== '') { try { logger.info(`[MULTI_DBG] chainId=${chainId} checking logoURI for existing contract`); // Ждем 2 секунды для стабильности соединения await new Promise(resolve => setTimeout(resolve, 2000)); const DLE = await hre.ethers.getContractFactory('contracts/DLE.sol:DLE'); const dleContract = DLE.attach(predictedAddress); const currentLogo = await dleContract.logoURI(); if (currentLogo === '' || currentLogo === '0x') { logger.info(`[MULTI_DBG] chainId=${chainId} initializing logoURI for existing contract: ${params.logoURI}`); const logoTx = await dleContract.connect(wallet).initializeLogoURI(params.logoURI, feeOverrides); await logoTx.wait(); logger.info(`[MULTI_DBG] chainId=${chainId} logoURI initialized for existing contract`); } else { logger.info(`[MULTI_DBG] chainId=${chainId} logoURI already set: ${currentLogo}`); } } catch (error) { logger.info(`[MULTI_DBG] chainId=${chainId} logoURI initialization failed for existing contract: ${error.message}`); } } return { address: predictedAddress, chainId: chainId }; } // Деплоим DLE с retry логикой для обработки race conditions let tx; let deployAttempts = 0; const maxDeployAttempts = 5; while (deployAttempts < maxDeployAttempts) { try { deployAttempts++; // Получаем актуальный nonce прямо перед отправкой транзакции const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 }); logger.info(`[MULTI_DBG] chainId=${chainId} deploy attempt ${deployAttempts}/${maxDeployAttempts} with current nonce=${currentNonce} (target was ${targetDLENonce})`); // Если текущий nonce больше целевого, обновляем targetDLENonce if (currentNonce > targetDLENonce) { logger.info(`[MULTI_DBG] chainId=${chainId} current nonce ${currentNonce} > target nonce ${targetDLENonce}, updating target`); targetDLENonce = currentNonce; logger.info(`[MULTI_DBG] chainId=${chainId} updated targetDLENonce to: ${targetDLENonce}`); } const txData = { data: dleInit, nonce: currentNonce, gasLimit, ...feeOverrides }; const rpcManager = new RPCConnectionManager(); const result = await rpcManager.sendTransactionWithRetry(wallet, txData, { maxRetries: 3 }); tx = result.tx; // Отмечаем транзакцию как pending в NonceManager nonceManager.markTransactionPending(wallet.address, chainId, currentNonce, tx.hash); logger.info(`[MULTI_DBG] chainId=${chainId} deploy successful on attempt ${deployAttempts}`); break; // Успешно отправили, выходим из цикла } catch (e) { const errorMsg = e?.message || e; logger.warn(`[MULTI_DBG] chainId=${chainId} deploy attempt ${deployAttempts} failed: ${errorMsg}`); // Проверяем, является ли это ошибкой nonce if (String(errorMsg).toLowerCase().includes('nonce too low') && deployAttempts < maxDeployAttempts) { logger.info(`[MULTI_DBG] chainId=${chainId} nonce race condition detected, retrying...`); // Используем NonceManager для обновления nonce nonceManager.resetNonce(wallet.address, chainId); const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 }); logger.info(`[MULTI_DBG] chainId=${chainId} current nonce: ${currentNonce}, target was: ${targetDLENonce}`); // Обновляем targetDLENonce на актуальный nonce targetDLENonce = currentNonce; logger.info(`[MULTI_DBG] chainId=${chainId} updated targetDLENonce to: ${targetDLENonce}`); // Короткая задержка перед следующей попыткой await new Promise(resolve => setTimeout(resolve, 1000)); continue; } // Если это не ошибка nonce или исчерпаны попытки, выбрасываем ошибку if (deployAttempts >= maxDeployAttempts) { throw new Error(`Deployment failed after ${maxDeployAttempts} attempts: ${errorMsg}`); } // Для других ошибок делаем короткую задержку и пробуем снова await new Promise(resolve => setTimeout(resolve, 2000)); } } const rc = await tx.wait(2); // Ждем 2 подтверждения с таймаутом // Отмечаем транзакцию как подтвержденную в NonceManager nonceManager.markTransactionConfirmed(wallet.address, chainId, tx.hash); const deployedAddress = rc.contractAddress || predictedAddress; // Проверяем, что адрес соответствует предсказанному if (deployedAddress !== predictedAddress) { logger.error(`[MULTI_DBG] chainId=${chainId} ADDRESS MISMATCH! predicted=${predictedAddress} actual=${deployedAddress}`); throw new Error(`Address mismatch: predicted ${predictedAddress} != actual ${deployedAddress}`); } logger.info(`[MULTI_DBG] chainId=${chainId} DLE deployed at=${deployedAddress} ✅`); // Инициализация логотипа если он указан if (params.logoURI && params.logoURI !== '') { try { logger.info(`[MULTI_DBG] chainId=${chainId} initializing logoURI: ${params.logoURI}`); // Ждем 5 секунд, чтобы контракт получил подтверждения logger.info(`[MULTI_DBG] chainId=${chainId} waiting 5 seconds for contract confirmations...`); await new Promise(resolve => setTimeout(resolve, 5000)); const DLE = await hre.ethers.getContractFactory('contracts/DLE.sol:DLE'); const dleContract = DLE.attach(deployedAddress); // Проверяем текущий логотип перед инициализацией const currentLogo = await dleContract.logoURI(); logger.info(`[MULTI_DBG] chainId=${chainId} current logoURI: ${currentLogo}`); if (currentLogo === '' || currentLogo === '0x') { logger.info(`[MULTI_DBG] chainId=${chainId} logoURI is empty, initializing...`); const logoTx = await dleContract.connect(wallet).initializeLogoURI(params.logoURI, feeOverrides); logger.info(`[MULTI_DBG] chainId=${chainId} logoURI transaction sent: ${logoTx.hash}`); await logoTx.wait(2); // Ждем 2 подтверждения с таймаутом logger.info(`[MULTI_DBG] chainId=${chainId} logoURI initialized successfully`); } else { logger.info(`[MULTI_DBG] chainId=${chainId} logoURI already set: ${currentLogo}, skipping initialization`); } } catch (error) { logger.error(`[MULTI_DBG] chainId=${chainId} logoURI initialization failed: ${error.message}`); logger.error(`[MULTI_DBG] chainId=${chainId} error stack: ${error.stack}`); // Не прерываем деплой из-за ошибки логотипа } } else { logger.info(`[MULTI_DBG] chainId=${chainId} no logoURI specified, skipping initialization`); } // Автоматическая верификация DLE контракта после успешного деплоя let verificationResult = { success: false, error: 'skipped' }; if ((etherscanKey || params.etherscanApiKey) && params.autoVerifyAfterDeploy) { try { logger.info(`🔍 Начинаем автоматическую верификацию DLE контракта...`); logger.info(`[VERIFY_DBG] dleConfig available: ${!!dleConfig}`); logger.info(`[VERIFY_DBG] initializer: ${initializer}`); // Кодируем аргументы конструктора в hex // Конструктор DLE: constructor(DLEConfig memory config, address _initializer) const abiCoder = ethers.AbiCoder.defaultAbiCoder(); // Структура DLEConfig const dleConfigType = 'tuple(string,string,string,string,uint256,string[],uint256,uint256,address[],uint256[],uint256[])'; // Подготавливаем DLEConfig tuple (все значения уже BigInt из constructorArgsGenerator) const dleConfigTuple = [ dleConfig.name, dleConfig.symbol, dleConfig.location, dleConfig.coordinates, dleConfig.jurisdiction, // уже BigInt dleConfig.okvedCodes, // уже массив строк dleConfig.kpp, // уже BigInt dleConfig.quorumPercentage, // уже BigInt dleConfig.initialPartners, dleConfig.initialAmounts, // уже BigInt массив dleConfig.supportedChainIds // уже BigInt массив ]; // Кодируем конструктор: (DLEConfig, address) const constructorArgsHex = abiCoder.encode( [dleConfigType, 'address'], [dleConfigTuple, initializer] ).slice(2); // Убираем префикс 0x logger.info(`[VERIFY_DBG] Constructor args encoded: ${constructorArgsHex.slice(0, 100)}...`); // Ждем 5 секунд перед верификацией для индексации контракта logger.info(`[VERIFY_DBG] Ожидаем 5 секунд для индексации контракта...`); await new Promise(resolve => setTimeout(resolve, 5000)); logger.info(`[VERIFY_DBG] Calling verifyDLEAfterDeploy...`); verificationResult = await verifyDLEAfterDeploy( Number(network.chainId), deployedAddress, constructorArgsHex, etherscanKey || params.etherscanApiKey, params ); logger.info(`[VERIFY_DBG] verifyDLEAfterDeploy completed`); if (verificationResult.success) { logger.info(`✅ DLE контракт верифицирован: ${deployedAddress}`); } else { logger.warn(`⚠️ Верификация DLE не удалась: ${verificationResult.error || verificationResult.message}`); } } catch (verificationError) { const errorMsg = verificationError.message || String(verificationError); const errorStack = verificationError.stack || 'No stack trace'; logger.error(`❌ Ошибка при верификации DLE: ${errorMsg}`); logger.error(`❌ Стек ошибки верификации: ${errorStack}`); verificationResult = { success: false, error: errorMsg }; } } else { if (!(etherscanKey || params.etherscanApiKey)) { logger.info(`ℹ️ API ключ Etherscan не предоставлен, пропускаем верификацию DLE`); } else if (!params.autoVerifyAfterDeploy) { logger.info(`ℹ️ Автоматическая верификация отключена, пропускаем верификацию DLE`); } } const finalChainId = Number(network.chainId); logger.info(`[MULTI_DBG] chainId=${finalChainId} Returning deployment result: address=${deployedAddress}`); return { address: deployedAddress, chainId: finalChainId, verification: verificationResult }; } catch (error) { const errorMsg = error.message || String(error); const errorStack = error.stack || 'No stack trace'; const chainIdStr = network?.chainId ? Number(network.chainId) : 'unknown'; logger.error(`[MULTI_DBG] chainId=${chainIdStr} deployment failed: ${errorMsg}`); logger.error(`[MULTI_DBG] chainId=${chainIdStr} error stack: ${errorStack}`); throw error; // Перебрасываем ошибку для обработки в main() } } async function main() { console.log('[MULTI_DBG] 🚀 ВХОДИМ В ФУНКЦИЮ MAIN!'); // 🔧 BEST PRACTICE: Настраиваем NO_PROXY перед деплоем try { const proxyManager = require('../../utils/proxyManager'); await proxyManager.initialize(); console.log('[MULTI_DBG] ✅ ProxyManager инициализирован'); } catch (error) { console.warn('[MULTI_DBG] ⚠️ Не удалось инициализировать ProxyManager:', error.message); } const { ethers } = hre; console.log('[MULTI_DBG] ✅ ethers получен'); logger.info('[MULTI_DBG] 🚀 НАЧИНАЕМ ДЕПЛОЙ DLE КОНТРАКТА'); console.log('[MULTI_DBG] ✅ logger.info выполнен'); // Автоматически генерируем ABI и flattened контракт перед деплоем logger.info('🔨 Генерация ABI файла...'); try { const { generateABIFile } = require('../generate-abi'); generateABIFile(); logger.info('✅ ABI файл обновлен перед деплоем'); } catch (abiError) { logger.warn('⚠️ Ошибка генерации ABI:', abiError.message); } logger.info('🔨 Генерация flattened контракта...'); try { const { generateFlattened } = require('../generate-flattened'); await generateFlattened(); logger.info('✅ Flattened контракт обновлен перед деплоем'); } catch (flattenError) { logger.warn('⚠️ Ошибка генерации flattened контракта:', flattenError.message); } // Загружаем параметры из базы данных или файла console.log('[MULTI_DBG] 🔍 НАЧИНАЕМ ЗАГРУЗКУ ПАРАМЕТРОВ...'); let params; try { // Пытаемся загрузить из базы данных const DeployParamsService = require('../../services/deployParamsService'); const deployParamsService = new DeployParamsService(); // Проверяем, передан ли конкретный deploymentId const deploymentId = process.env.DEPLOYMENT_ID; if (deploymentId) { logger.info(`🔍 Ищем параметры для deploymentId: ${deploymentId}`); params = await deployParamsService.getDeployParams(deploymentId); if (params) { logger.info('✅ Параметры загружены из базы данных по deploymentId'); } else { throw new Error(`Параметры деплоя не найдены для deploymentId: ${deploymentId}`); } } else { // Получаем последние параметры деплоя const latestParams = await deployParamsService.getLatestDeployParams(1); if (latestParams.length > 0) { params = latestParams[0]; logger.info('✅ Параметры загружены из базы данных (последние)'); } else { throw new Error('Параметры деплоя не найдены в базе данных'); } } await deployParamsService.close(); } catch (dbError) { logger.error('❌ Критическая ошибка: не удалось загрузить параметры из БД:', dbError.message); throw new Error(`Деплой невозможен без параметров из БД: ${dbError.message}`); } logger.info('[MULTI_DBG] Загружены параметры:', { name: params.name, symbol: params.symbol, supportedChainIds: params.supportedChainIds, rpcUrls: params.rpcUrls || params.rpc_urls, etherscanApiKey: params.etherscanApiKey || params.etherscan_api_key }); const pk = params.private_key || process.env.PRIVATE_KEY; // ИСПРАВЛЕНИЕ: Используем RPC URLs из deployParams, а не из rpcProviderService const networks = params.rpcUrls || params.rpc_urls || []; logger.info(`[MULTI_DBG] 📊 RPC URLs из deployParams: ${networks.length} сетей`); networks.forEach((url, i) => { logger.info(`[MULTI_DBG] ${i + 1}. ${url}`); }); // Устанавливаем API ключи Etherscan для верификации const ApiKeyManager = require('../../utils/apiKeyManager'); const etherscanKey = ApiKeyManager.getAndSetEtherscanApiKey(params); if (!etherscanKey) { logger.warn('[MULTI_DBG] ⚠️ Etherscan API ключ не найден - верификация будет пропущена'); logger.warn(`[MULTI_DBG] Доступные поля: ${Object.keys(params).join(', ')}`); } if (!pk) throw new Error('Env: PRIVATE_KEY'); if (networks.length === 0) throw new Error('RPC URLs not found in params'); // Prepare init code once const DLE = await hre.ethers.getContractFactory('contracts/DLE.sol:DLE'); // Используем централизованный генератор параметров конструктора const { generateDeploymentArgs } = require('../../utils/constructorArgsGenerator'); const { dleConfig, initializer } = generateDeploymentArgs(params); // Проверяем наличие поддерживаемых сетей const supportedChainIds = params.supportedChainIds || []; if (supportedChainIds.length === 0) { throw new Error('Не указаны поддерживаемые сети (supportedChainIds)'); } // Создаем initCode для каждой сети отдельно const initCodes = {}; for (const chainId of supportedChainIds) { const deployTx = await DLE.getDeployTransaction(dleConfig, initializer); initCodes[chainId] = deployTx.data; } // Получаем initCodeHash из первого initCode (все должны быть одинаковые по структуре) const firstChainId = supportedChainIds[0]; const firstInitCode = initCodes[firstChainId]; if (!firstInitCode) { throw new Error(`InitCode не создан для первой сети: ${firstChainId}`); } const initCodeHash = ethers.keccak256(firstInitCode); // DEBUG: глобальные значения try { logger.info(`[MULTI_DBG] GLOBAL initCodeHash(calculated)=${initCodeHash}`); logger.info(`[MULTI_DBG] GLOBAL firstInitCode.lenBytes=${ethers.getBytes(firstInitCode).length} head16=${firstInitCode.slice(0, 34)}...`); } catch (e) { logger.info('[MULTI_DBG] GLOBAL precheck error', e?.message || e); } // Подготовим провайдеры и вычислим общий nonce для DLE с retry логикой logger.info(`[MULTI_DBG] Создаем RPC соединения для ${supportedChainIds.length} сетей...`); const connections = await createMultipleRPCConnections(supportedChainIds, pk, { maxRetries: 3, timeout: 30000 }); if (connections.length === 0) { throw new Error('Не удалось установить ни одного RPC соединения'); } logger.info(`[MULTI_DBG] ✅ Успешно подключились к ${connections.length}/${supportedChainIds.length} сетям`); // Очищаем старые pending транзакции для всех сетей for (const connection of connections) { const chainId = Number(connection.network.chainId); nonceManager.clearOldPendingTransactions(connection.wallet.address, chainId); } const nonces = []; for (const connection of connections) { logger.info(`[MULTI_DBG] Получаем nonce для connection: address=${connection.wallet.address}, rpcUrl=${connection.rpcUrl}, chainId=${Number(connection.network.chainId)}`); const n = await nonceManager.getNonce(connection.wallet.address, connection.rpcUrl, Number(connection.network.chainId)); nonces.push(n); } const targetDLENonce = Math.max(...nonces); logger.info(`[MULTI_DBG] nonces=${JSON.stringify(nonces)} targetDLENonce=${targetDLENonce}`); logger.info(`[MULTI_DBG] Starting deployment to ${connections.length} networks`); // ПАРАЛЛЕЛЬНЫЙ деплой во всех успешных сетях одновременно console.log(`[MULTI_DBG] 🚀 ДОШЛИ ДО ПАРАЛЛЕЛЬНОГО ДЕПЛОЯ!`); logger.info(`[MULTI_DBG] Starting PARALLEL deployment to ${connections.length} successful networks`); logger.info(`[MULTI_DBG] 🚀 ЗАПУСКАЕМ ЦИКЛ ДЕПЛОЯ!`); const deploymentPromises = connections.map(async (connection, i) => { const rpcUrl = connection.rpcUrl; const chainId = Number(connection.network.chainId); logger.info(`[MULTI_DBG] 🚀 Starting deployment to network ${i + 1}/${connections.length}: ${rpcUrl} (chainId: ${chainId})`); try { // Получаем правильный initCode для этой сети const networkInitCode = initCodes[chainId]; if (!networkInitCode) { throw new Error(`InitCode не найден для chainId: ${chainId}`); } const r = await deployInNetwork(chainId, pk, initCodeHash, targetDLENonce, networkInitCode, params, dleConfig, initializer, etherscanKey); logger.info(`[MULTI_DBG] ✅ Network ${i + 1} (chainId: ${chainId}) deployment SUCCESS: ${r.address}`); return { rpcUrl, chainId, address: r.address, success: true, verification: r.verification || { success: false, error: 'unknown' } }; } catch (error) { const errorMsg = error.message || String(error); const errorStack = error.stack || 'No stack trace'; logger.error(`[MULTI_DBG] ❌ Network ${i + 1} (chainId: ${chainId}) deployment FAILED: ${errorMsg}`); logger.error(`[MULTI_DBG] ❌ Network ${i + 1} (chainId: ${chainId}) error stack: ${errorStack}`); return { rpcUrl, chainId, error: errorMsg, success: false }; } }); // Ждем завершения всех деплоев const results = await Promise.all(deploymentPromises); logger.info(`[MULTI_DBG] All ${networks.length} deployments completed`); // Логируем результаты для каждой сети results.forEach((result, index) => { if (result.address) { logger.info(`[MULTI_DBG] ✅ Network ${index + 1} (chainId: ${result.chainId}) SUCCESS: ${result.address}`); } else { logger.info(`[MULTI_DBG] ❌ Network ${index + 1} (chainId: ${result.chainId}) FAILED: ${result.error}`); } }); // Логируем все результаты для отладки logger.info('[MULTI_DBG] Raw results:', JSON.stringify(results, null, 2)); // Проверяем, что все адреса одинаковые (критично для детерминированного деплоя) const successfulResults = results.filter(r => r.success === true); const addresses = successfulResults.map(r => r.address).filter(addr => addr); const uniqueAddresses = [...new Set(addresses)]; logger.info('[MULTI_DBG] All addresses:', addresses); logger.info('[MULTI_DBG] Unique addresses:', uniqueAddresses); logger.info('[MULTI_DBG] Results count:', results.length); logger.info('[MULTI_DBG] Networks count:', networks.length); if (uniqueAddresses.length > 1) { logger.error('[MULTI_DBG] ERROR: DLE addresses are different across networks!'); logger.error('[MULTI_DBG] addresses:', uniqueAddresses); throw new Error('Nonce alignment failed - addresses are different'); } if (uniqueAddresses.length === 0) { logger.error('[MULTI_DBG] ERROR: No successful deployments!'); throw new Error('No successful deployments'); } logger.info('[MULTI_DBG] SUCCESS: All DLE addresses are identical:', uniqueAddresses[0]); // Верификация уже выполнена в процессе деплоя const finalResults = results.map((result) => ({ ...result, verification: result.verification || { success: false, error: 'not_attempted' } })); // ВЫВОДИМ РЕЗУЛЬТАТ С ИНТЕГРИРОВАННОЙ ВЕРИФИКАЦИЕЙ! console.log('[MULTI_DBG] 🎯 ДОШЛИ ДО ВЫВОДА РЕЗУЛЬТАТА!'); console.log('[MULTI_DBG] 📊 finalResults:', JSON.stringify(finalResults, null, 2)); console.log('[MULTI_DBG] 🎯 ВЫВОДИМ MULTICHAIN_DEPLOY_RESULT!'); console.log('MULTICHAIN_DEPLOY_RESULT', JSON.stringify(finalResults)); console.log('[MULTI_DBG] ✅ MULTICHAIN_DEPLOY_RESULT ВЫВЕДЕН!'); logger.info('[MULTI_DBG] DLE deployment completed successfully with integrated verification!'); } console.log('[MULTI_DBG] 🚀 ВЫЗЫВАЕМ MAIN()...'); main().catch((e) => { console.log('[MULTI_DBG] ❌ ОШИБКА В MAIN:', e); logger.error('[MULTI_DBG] ❌ Deployment failed:', e); // Даже при ошибке выводим результат для анализа const errorResult = { error: e.message, success: false, timestamp: new Date().toISOString(), stack: e.stack }; console.log('MULTICHAIN_DEPLOY_RESULT', JSON.stringify([errorResult])); process.exit(1); });