Files
DLE/backend/scripts/deploy/deploy-multichain.js

450 lines
20 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 */
const hre = require('hardhat');
const path = require('path');
const fs = require('fs');
const { getRpcUrlByChainId } = require('../../services/rpcProviderService');
// Подбираем безопасные gas/fee для разных сетей (включая L2)
async function getFeeOverrides(provider, { minPriorityGwei = 1n, minFeeGwei = 20n } = {}) {
const fee = await provider.getFeeData();
const overrides = {};
const minPriority = (await (async () => hre.ethers.parseUnits(minPriorityGwei.toString(), 'gwei'))());
const minFee = (await (async () => hre.ethers.parseUnits(minFeeGwei.toString(), 'gwei'))());
if (fee.maxFeePerGas) {
overrides.maxFeePerGas = fee.maxFeePerGas < minFee ? minFee : fee.maxFeePerGas;
overrides.maxPriorityFeePerGas = (fee.maxPriorityFeePerGas && fee.maxPriorityFeePerGas > 0n)
? fee.maxPriorityFeePerGas
: minPriority;
} else if (fee.gasPrice) {
overrides.gasPrice = fee.gasPrice < minFee ? minFee : fee.gasPrice;
}
return overrides;
}
async function deployInNetwork(rpcUrl, pk, salt, initCodeHash, targetDLENonce, dleInit, params) {
const { ethers } = hre;
const provider = new ethers.JsonRpcProvider(rpcUrl);
const wallet = new ethers.Wallet(pk, provider);
const net = await provider.getNetwork();
// DEBUG: базовая информация по сети
try {
const calcInitHash = ethers.keccak256(dleInit);
const saltLen = ethers.getBytes(salt).length;
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} rpc=${rpcUrl}`);
console.log(`[MULTI_DBG] wallet=${wallet.address} targetDLENonce=${targetDLENonce}`);
console.log(`[MULTI_DBG] saltLenBytes=${saltLen} salt=${salt}`);
console.log(`[MULTI_DBG] initCodeHash(provided)=${initCodeHash}`);
console.log(`[MULTI_DBG] initCodeHash(calculated)=${calcInitHash}`);
console.log(`[MULTI_DBG] dleInit.lenBytes=${ethers.getBytes(dleInit).length} head16=${dleInit.slice(0, 34)}...`);
} catch (e) {
console.log('[MULTI_DBG] precheck error', e?.message || e);
}
// 1) Выравнивание nonce до targetDLENonce нулевыми транзакциями (если нужно)
let current = await provider.getTransactionCount(wallet.address, 'pending');
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} current nonce=${current} target=${targetDLENonce}`);
if (current > targetDLENonce) {
throw new Error(`Current nonce ${current} > targetDLENonce ${targetDLENonce} on chainId=${Number(net.chainId)}`);
}
if (current < targetDLENonce) {
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} starting nonce alignment: ${current} -> ${targetDLENonce} (${targetDLENonce - current} transactions needed)`);
} else {
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} nonce already aligned: ${current} = ${targetDLENonce}`);
}
if (current < targetDLENonce) {
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} aligning nonce from ${current} to ${targetDLENonce} (${targetDLENonce - current} transactions needed)`);
// Используем burn address для более надежных транзакций
const burnAddress = "0x000000000000000000000000000000000000dEaD";
while (current < targetDLENonce) {
const overrides = await getFeeOverrides(provider);
let gasLimit = 21000; // минимальный gas для обычной транзакции
let sent = false;
let lastErr = null;
for (let attempt = 0; attempt < 3 && !sent; attempt++) {
try {
const txReq = {
to: burnAddress, // отправляем на burn address вместо своего адреса
value: 0n,
nonce: current,
gasLimit,
...overrides
};
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} sending filler tx nonce=${current} attempt=${attempt + 1}`);
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} tx details: to=${burnAddress}, value=0, gasLimit=${gasLimit}`);
const txFill = await wallet.sendTransaction(txReq);
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} filler tx sent, hash=${txFill.hash}, waiting for confirmation...`);
await txFill.wait();
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} filler tx nonce=${current} confirmed, hash=${txFill.hash}`);
sent = true;
} catch (e) {
lastErr = e;
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} filler tx nonce=${current} attempt=${attempt + 1} failed: ${e?.message || e}`);
if (String(e?.message || '').toLowerCase().includes('intrinsic gas too low') && attempt < 2) {
gasLimit = 50000; // увеличиваем gas limit
continue;
}
if (String(e?.message || '').toLowerCase().includes('nonce too low') && attempt < 2) {
// Обновляем nonce и пробуем снова
current = await provider.getTransactionCount(wallet.address, 'pending');
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} updated nonce to ${current}`);
continue;
}
throw e;
}
}
if (!sent) {
console.error(`[MULTI_DBG] chainId=${Number(net.chainId)} failed to send filler tx for nonce=${current}`);
throw lastErr || new Error('filler tx failed');
}
current++;
}
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} nonce alignment completed, current nonce=${current}`);
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} ready for DLE deployment with nonce=${current}`);
} else {
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} nonce already aligned at ${current}`);
}
// 2) Деплой DLE напрямую на согласованном nonce
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} deploying DLE directly with nonce=${targetDLENonce}`);
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;
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} estGas=${est?.toString?.()||'null'} effGasPrice=${effPrice?.toString?.()||'0'} maxByBalance=${maxByBalance.toString()} chosenGasLimit=${gasLimit.toString()}`);
} catch (_) {
gasLimit = 3_000_000n;
}
// Вычисляем предсказанный адрес DLE
const predictedAddress = ethers.getCreateAddress({
from: wallet.address,
nonce: targetDLENonce
});
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} predicted DLE address=${predictedAddress}`);
// Проверяем, не развернут ли уже контракт
const existingCode = await provider.getCode(predictedAddress);
if (existingCode && existingCode !== '0x') {
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} DLE already exists at predictedAddress, skip deploy`);
// Проверяем и инициализируем логотип для существующего контракта
if (params.logoURI && params.logoURI !== '') {
try {
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} checking logoURI for existing contract`);
const DLE = await hre.ethers.getContractFactory('DLE');
const dleContract = DLE.attach(predictedAddress);
const currentLogo = await dleContract.logoURI();
if (currentLogo === '' || currentLogo === '0x') {
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} initializing logoURI for existing contract: ${params.logoURI}`);
const logoTx = await dleContract.connect(wallet).initializeLogoURI(params.logoURI, feeOverrides);
await logoTx.wait();
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialized for existing contract`);
} else {
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI already set: ${currentLogo}`);
}
} catch (error) {
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialization failed for existing contract: ${error.message}`);
}
}
return { address: predictedAddress, chainId: Number(net.chainId) };
}
// Деплоим DLE
let tx;
try {
tx = await wallet.sendTransaction({
data: dleInit,
nonce: targetDLENonce,
gasLimit,
...feeOverrides
});
} catch (e) {
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} deploy error(first): ${e?.message || e}`);
// Повторная попытка с обновленным nonce
const updatedNonce = await provider.getTransactionCount(wallet.address, 'pending');
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} retry deploy with nonce=${updatedNonce}`);
tx = await wallet.sendTransaction({
data: dleInit,
nonce: updatedNonce,
gasLimit,
...feeOverrides
});
}
const rc = await tx.wait();
const deployedAddress = rc.contractAddress || predictedAddress;
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} DLE deployed at=${deployedAddress}`);
// Инициализация логотипа если он указан
if (params.logoURI && params.logoURI !== '') {
try {
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} initializing logoURI: ${params.logoURI}`);
const DLE = await hre.ethers.getContractFactory('DLE');
const dleContract = DLE.attach(deployedAddress);
const logoTx = await dleContract.connect(wallet).initializeLogoURI(params.logoURI, feeOverrides);
await logoTx.wait();
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialized successfully`);
} catch (error) {
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialization failed: ${error.message}`);
// Не прерываем деплой из-за ошибки логотипа
}
} else {
console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} no logoURI specified, skipping initialization`);
}
return { address: deployedAddress, chainId: Number(net.chainId) };
}
async function main() {
const { ethers } = hre;
// Загружаем параметры из базы данных или файла
let params;
try {
// Пытаемся загрузить из базы данных
const DeployParamsService = require('../../services/deployParamsService');
const deployParamsService = new DeployParamsService();
// Проверяем, передан ли конкретный deploymentId
const deploymentId = process.env.DEPLOYMENT_ID;
if (deploymentId) {
console.log(`🔍 Ищем параметры для deploymentId: ${deploymentId}`);
params = await deployParamsService.getDeployParams(deploymentId);
if (params) {
console.log('✅ Параметры загружены из базы данных по deploymentId');
} else {
throw new Error(`Параметры деплоя не найдены для deploymentId: ${deploymentId}`);
}
} else {
// Получаем последние параметры деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
params = latestParams[0];
console.log('✅ Параметры загружены из базы данных (последние)');
} else {
throw new Error('Параметры деплоя не найдены в базе данных');
}
}
await deployParamsService.close();
} catch (dbError) {
console.log('⚠️ Не удалось загрузить параметры из БД, пытаемся загрузить из файла:', dbError.message);
// Fallback к файлу
const paramsPath = path.join(__dirname, './current-params.json');
if (!fs.existsSync(paramsPath)) {
throw new Error('Файл параметров не найден: ' + paramsPath);
}
params = JSON.parse(fs.readFileSync(paramsPath, 'utf8'));
console.log('✅ Параметры загружены из файла');
}
console.log('[MULTI_DBG] Загружены параметры:', {
name: params.name,
symbol: params.symbol,
supportedChainIds: params.supportedChainIds,
CREATE2_SALT: params.CREATE2_SALT
});
const pk = params.private_key || process.env.PRIVATE_KEY;
const salt = params.CREATE2_SALT || params.create2_salt;
const networks = params.rpcUrls || params.rpc_urls || [];
if (!pk) throw new Error('Env: PRIVATE_KEY');
if (!salt) throw new Error('CREATE2_SALT not found in params');
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 dleConfig = {
name: params.name || '',
symbol: params.symbol || '',
location: params.location || '',
coordinates: params.coordinates || '',
jurisdiction: params.jurisdiction || 0,
oktmo: params.oktmo || '',
okvedCodes: params.okvedCodes || [],
kpp: params.kpp ? BigInt(params.kpp) : 0n,
quorumPercentage: params.quorumPercentage || 51,
initialPartners: params.initialPartners || [],
initialAmounts: (params.initialAmounts || []).map(amount => BigInt(amount) * BigInt(10**18)),
supportedChainIds: (params.supportedChainIds || []).map(id => BigInt(id))
};
const deployTx = await DLE.getDeployTransaction(dleConfig, BigInt(params.currentChainId || params.supportedChainIds?.[0] || 1), params.initializer || params.initialPartners?.[0] || "0x0000000000000000000000000000000000000000");
const dleInit = deployTx.data;
const initCodeHash = ethers.keccak256(dleInit);
// DEBUG: глобальные значения
try {
const saltLen = ethers.getBytes(salt).length;
console.log(`[MULTI_DBG] GLOBAL saltLenBytes=${saltLen} salt=${salt}`);
console.log(`[MULTI_DBG] GLOBAL initCodeHash(calculated)=${initCodeHash}`);
console.log(`[MULTI_DBG] GLOBAL dleInit.lenBytes=${ethers.getBytes(dleInit).length} head16=${dleInit.slice(0, 34)}...`);
} catch (e) {
console.log('[MULTI_DBG] GLOBAL precheck error', e?.message || e);
}
// Подготовим провайдеры и вычислим общий nonce для DLE
const providers = networks.map(u => new hre.ethers.JsonRpcProvider(u));
const wallets = providers.map(p => new hre.ethers.Wallet(pk, p));
const nonces = [];
for (let i = 0; i < providers.length; i++) {
const n = await providers[i].getTransactionCount(wallets[i].address, 'pending');
nonces.push(n);
}
const targetDLENonce = Math.max(...nonces);
console.log(`[MULTI_DBG] nonces=${JSON.stringify(nonces)} targetDLENonce=${targetDLENonce}`);
console.log(`[MULTI_DBG] Starting deployment to ${networks.length} networks:`, networks);
// ПАРАЛЛЕЛЬНЫЙ деплой во всех сетях одновременно
console.log(`[MULTI_DBG] Starting PARALLEL deployment to ${networks.length} networks`);
const deploymentPromises = networks.map(async (rpcUrl, i) => {
console.log(`[MULTI_DBG] 🚀 Starting deployment to network ${i + 1}/${networks.length}: ${rpcUrl}`);
try {
// Получаем chainId динамически из сети
const provider = new hre.ethers.JsonRpcProvider(rpcUrl);
const network = await provider.getNetwork();
const chainId = Number(network.chainId);
console.log(`[MULTI_DBG] 📡 Network ${i + 1} chainId: ${chainId}`);
const r = await deployInNetwork(rpcUrl, pk, salt, initCodeHash, targetDLENonce, dleInit, params);
console.log(`[MULTI_DBG] ✅ Network ${i + 1} (chainId: ${chainId}) deployment SUCCESS: ${r.address}`);
return { rpcUrl, chainId, ...r };
} catch (error) {
console.error(`[MULTI_DBG] ❌ Network ${i + 1} deployment FAILED:`, error.message);
return { rpcUrl, error: error.message };
}
});
// Ждем завершения всех деплоев
const results = await Promise.all(deploymentPromises);
console.log(`[MULTI_DBG] All ${networks.length} deployments completed`);
// Логируем результаты для каждой сети
results.forEach((result, index) => {
if (result.address) {
console.log(`[MULTI_DBG] ✅ Network ${index + 1} (chainId: ${result.chainId}) SUCCESS: ${result.address}`);
} else {
console.log(`[MULTI_DBG] ❌ Network ${index + 1} (chainId: ${result.chainId}) FAILED: ${result.error}`);
}
});
// Проверяем, что все адреса одинаковые
const addresses = results.map(r => r.address).filter(addr => addr);
const uniqueAddresses = [...new Set(addresses)];
console.log('[MULTI_DBG] All addresses:', addresses);
console.log('[MULTI_DBG] Unique addresses:', uniqueAddresses);
console.log('[MULTI_DBG] Results count:', results.length);
console.log('[MULTI_DBG] Networks count:', networks.length);
if (uniqueAddresses.length > 1) {
console.error('[MULTI_DBG] ERROR: DLE addresses are different across networks!');
console.error('[MULTI_DBG] addresses:', uniqueAddresses);
throw new Error('Nonce alignment failed - addresses are different');
}
if (uniqueAddresses.length === 0) {
console.error('[MULTI_DBG] ERROR: No successful deployments!');
throw new Error('No successful deployments');
}
console.log('[MULTI_DBG] SUCCESS: All DLE addresses are identical:', uniqueAddresses[0]);
// Автоматическая верификация контрактов
let verificationResults = [];
console.log(`[MULTI_DBG] autoVerifyAfterDeploy: ${params.autoVerifyAfterDeploy}`);
if (params.autoVerifyAfterDeploy) {
console.log('[MULTI_DBG] Starting automatic contract verification...');
try {
// Импортируем функцию верификации
const { verifyWithHardhatV2 } = require('../verify-with-hardhat-v2');
// Подготавливаем данные о развернутых сетях
const deployedNetworks = results
.filter(result => result.address && !result.error)
.map(result => ({
chainId: result.chainId,
address: result.address
}));
// Запускаем верификацию с данными о сетях
await verifyWithHardhatV2(params, deployedNetworks);
// Если верификация прошла успешно, отмечаем все как верифицированные
verificationResults = networks.map(() => 'verified');
console.log('[MULTI_DBG] ✅ Automatic verification completed successfully');
} catch (verificationError) {
console.error('[MULTI_DBG] ❌ Automatic verification failed:', verificationError.message);
verificationResults = networks.map(() => 'verification_failed');
}
} else {
console.log('[MULTI_DBG] Contract verification disabled (autoVerifyAfterDeploy: false)');
verificationResults = networks.map(() => 'disabled');
}
// Объединяем результаты
const finalResults = results.map((result, index) => ({
...result,
verification: verificationResults[index] || 'failed'
}));
console.log('MULTICHAIN_DEPLOY_RESULT', JSON.stringify(finalResults));
console.log('[MULTI_DBG] DLE deployment completed successfully!');
}
main().catch((e) => { console.error(e); process.exit(1); });