feat: Добавлены формы деплоя модулей DLE с полными настройками
- Создана форма деплоя TreasuryModule с детальными настройками казны - Создана форма деплоя TimelockModule с настройками временных задержек - Создана форма деплоя DLEReader с простой конфигурацией - Добавлены маршруты и индексы для всех модулей - Исправлены пути импорта BaseLayout - Добавлены авторские права во все файлы - Улучшена архитектура деплоя модулей отдельно от основного DLE
This commit is contained in:
@@ -16,6 +16,7 @@ const fs = require('fs');
|
||||
const { ethers } = require('ethers');
|
||||
const logger = require('../utils/logger');
|
||||
const { getRpcUrlByChainId } = require('./rpcProviderService');
|
||||
const deploymentTracker = require('../utils/deploymentTracker');
|
||||
const etherscanV2 = require('./etherscanV2VerificationService');
|
||||
const verificationStore = require('./verificationStore');
|
||||
|
||||
@@ -29,11 +30,18 @@ class DLEV2Service {
|
||||
* @param {Object} dleParams - Параметры DLE
|
||||
* @returns {Promise<Object>} - Результат создания DLE
|
||||
*/
|
||||
async createDLE(dleParams) {
|
||||
async createDLE(dleParams, deploymentId = null) {
|
||||
console.log("🔥 [DLEV2-SERVICE] ФУНКЦИЯ createDLE ВЫЗВАНА!");
|
||||
logger.info("🚀 DEBUG: ВХОДИМ В createDLE ФУНКЦИЮ");
|
||||
let paramsFile = null;
|
||||
let tempParamsFile = null;
|
||||
try {
|
||||
logger.info('Начало создания DLE v2 с параметрами:', dleParams);
|
||||
|
||||
// WebSocket обновление: начало процесса
|
||||
if (deploymentId) {
|
||||
deploymentTracker.updateProgress(deploymentId, 'Валидация параметров', 5, 'Проверяем входные данные');
|
||||
}
|
||||
|
||||
// Валидация входных данных
|
||||
this.validateDLEParams(dleParams);
|
||||
@@ -50,6 +58,11 @@ class DLEV2Service {
|
||||
logger.warn('Не удалось вычислить initializerAddress из приватного ключа:', e.message);
|
||||
}
|
||||
|
||||
// WebSocket обновление: генерация CREATE2_SALT
|
||||
if (deploymentId) {
|
||||
deploymentTracker.updateProgress(deploymentId, 'Генерация CREATE2 SALT', 10, 'Создаем уникальный идентификатор для детерминированного адреса');
|
||||
}
|
||||
|
||||
// Генерируем одноразовый CREATE2_SALT и сохраняем его с уникальным ключом в secrets
|
||||
const { createAndStoreNewCreate2Salt } = require('./secretStore');
|
||||
const { salt: create2Salt, key: saltKey } = await createAndStoreNewCreate2Salt({ label: deployParams.name || 'DLEv2' });
|
||||
@@ -66,6 +79,11 @@ class DLEV2Service {
|
||||
}
|
||||
fs.copyFileSync(paramsFile, tempParamsFile);
|
||||
|
||||
// WebSocket обновление: поиск RPC URLs
|
||||
if (deploymentId) {
|
||||
deploymentTracker.updateProgress(deploymentId, 'Поиск RPC endpoints', 15, 'Подключаемся к блокчейн сетям');
|
||||
}
|
||||
|
||||
// Готовим RPC для всех выбранных сетей
|
||||
const rpcUrls = [];
|
||||
for (const cid of deployParams.supportedChainIds) {
|
||||
@@ -99,14 +117,7 @@ class DLEV2Service {
|
||||
const walletAddress = new ethers.Wallet(pk, provider).address;
|
||||
const balance = await provider.getBalance(walletAddress);
|
||||
|
||||
if (typeof ethers.parseEther !== 'function') {
|
||||
throw new Error('Метод ethers.parseEther не найден');
|
||||
}
|
||||
const minBalance = ethers.parseEther("0.00001");
|
||||
|
||||
if (typeof ethers.formatEther !== 'function') {
|
||||
throw new Error('Метод ethers.formatEther не найден');
|
||||
}
|
||||
logger.info(`Баланс кошелька ${walletAddress}: ${ethers.formatEther(balance)} ETH`);
|
||||
if (balance < minBalance) {
|
||||
throw new Error(`Недостаточно ETH для деплоя в ${deployParams.supportedChainIds[0]}. Баланс: ${ethers.formatEther(balance)} ETH`);
|
||||
@@ -117,27 +128,87 @@ class DLEV2Service {
|
||||
throw new Error('Приватный ключ для деплоя не передан');
|
||||
}
|
||||
|
||||
// Рассчитываем INIT_CODE_HASH автоматически из актуального initCode
|
||||
const initCodeHash = await this.computeInitCodeHash({
|
||||
...deployParams,
|
||||
currentChainId: deployParams.currentChainId || deployParams.supportedChainIds[0]
|
||||
});
|
||||
// Сохраняем ключ Etherscan V2 ПЕРЕД деплоем
|
||||
logger.info(`🔑 Etherscan API Key получен: ${dleParams.etherscanApiKey ? '[ЕСТЬ]' : '[НЕТ]'}`);
|
||||
try {
|
||||
if (dleParams.etherscanApiKey) {
|
||||
logger.info('🔑 Сохраняем Etherscan API Key в secretStore...');
|
||||
const { setSecret } = require('./secretStore');
|
||||
await setSecret('ETHERSCAN_V2_API_KEY', dleParams.etherscanApiKey);
|
||||
logger.info('🔑 Etherscan API Key успешно сохранен в базу данных');
|
||||
} else {
|
||||
logger.warn('🔑 Etherscan API Key не передан, пропускаем сохранение');
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('🔑 Ошибка при сохранении Etherscan API Key:', e.message);
|
||||
}
|
||||
|
||||
// WebSocket обновление: компиляция произойдет автоматически в deploy-multichain.js
|
||||
if (deploymentId) {
|
||||
deploymentTracker.updateProgress(deploymentId, 'Подготовка к деплою', 25, 'Подготавливаем параметры для деплоя');
|
||||
}
|
||||
|
||||
// INIT_CODE_HASH будет вычислен в deploy-multichain.js
|
||||
|
||||
// Factory больше не используется - деплой DLE напрямую
|
||||
logger.info(`Подготовка к прямому деплою DLE в сетях: ${deployParams.supportedChainIds.join(', ')}`);
|
||||
|
||||
// WebSocket обновление: начало мульти-чейн деплоя
|
||||
if (deploymentId) {
|
||||
deploymentTracker.updateProgress(deploymentId, 'Мульти-чейн деплой', 40);
|
||||
deploymentTracker.addLog(deploymentId, `🌐 Деплой в ${deployParams.supportedChainIds.length} сетях: ${deployParams.supportedChainIds.join(', ')}`, 'info');
|
||||
deploymentTracker.addLog(deploymentId, `📋 Этапы: 1) DLE контракт → 2) Модули → 3) Инициализация → 4) Верификация`, 'info');
|
||||
}
|
||||
|
||||
// Мультисетевой деплой одним вызовом
|
||||
logger.info('Запуск мульти-чейн деплоя...');
|
||||
logger.info("🔍 DEBUG: Подготовка к прямому деплою...");
|
||||
|
||||
const result = await this.runDeployMultichain(paramsFile, {
|
||||
rpcUrls: rpcUrls,
|
||||
chainIds: deployParams.supportedChainIds,
|
||||
privateKey: dleParams.privateKey?.startsWith('0x') ? dleParams.privateKey : `0x${dleParams.privateKey}`,
|
||||
salt: create2Salt,
|
||||
initCodeHash
|
||||
etherscanApiKey: dleParams.etherscanApiKey
|
||||
});
|
||||
|
||||
logger.info('Деплой завершен, результат:', JSON.stringify(result, null, 2));
|
||||
logger.info("🔍 DEBUG: Запуск мультисетевого деплоя...");
|
||||
|
||||
// WebSocket обновление: деплой завершен, начинаем обработку результатов
|
||||
if (deploymentId) {
|
||||
deploymentTracker.updateProgress(deploymentId, 'Обработка результатов', 85, 'Деплой завершен, сохраняем результаты');
|
||||
deploymentTracker.addLog(deploymentId, `✅ DLE контракт задеплоен в ${result.networks?.length || 0} сетях`, 'success');
|
||||
if (result.networks) {
|
||||
result.networks.forEach(network => {
|
||||
deploymentTracker.addLog(deploymentId, `📍 ${network.networkName || `Chain ${network.chainId}`}: ${network.address}`, 'info');
|
||||
});
|
||||
}
|
||||
|
||||
// Логируем информацию о модулях
|
||||
if (result.modules) {
|
||||
deploymentTracker.addLog(deploymentId, `🔧 Модули задеплоены в ${result.modules.length} сетях`, 'info');
|
||||
result.modules.forEach((moduleSet, index) => {
|
||||
if (moduleSet && !moduleSet.error) {
|
||||
deploymentTracker.addLog(deploymentId, `📦 Сеть ${index + 1}: Treasury=${moduleSet.treasuryModule?.substring(0, 10)}..., Timelock=${moduleSet.timelockModule?.substring(0, 10)}..., Reader=${moduleSet.dleReader?.substring(0, 10)}...`, 'info');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Логируем информацию о верификации
|
||||
if (result.verification) {
|
||||
deploymentTracker.addLog(deploymentId, `🔍 Верификация выполнена в ${result.verification.length} сетях`, 'info');
|
||||
result.verification.forEach((verification, index) => {
|
||||
if (verification && !verification.error) {
|
||||
const dleStatus = verification.dle === 'success' ? '✅' : '❌';
|
||||
const treasuryStatus = verification.treasuryModule === 'success' ? '✅' : '❌';
|
||||
const timelockStatus = verification.timelockModule === 'success' ? '✅' : '❌';
|
||||
const readerStatus = verification.dleReader === 'success' ? '✅' : '❌';
|
||||
deploymentTracker.addLog(deploymentId, `🔍 Сеть ${index + 1}: DLE${dleStatus} Treasury${treasuryStatus} Timelock${timelockStatus} Reader${readerStatus}`, 'info');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Сохраняем информацию о созданном DLE для отображения на странице управления
|
||||
try {
|
||||
@@ -148,6 +219,7 @@ class DLEV2Service {
|
||||
logger.error('Неверная структура результата деплоя:', result);
|
||||
throw new Error('Неверная структура результата деплоя');
|
||||
}
|
||||
logger.info("🔍 DEBUG: Вызываем runDeployMultichain...");
|
||||
|
||||
// Если результат - массив (прямой результат из скрипта), преобразуем его
|
||||
let deployResult = result;
|
||||
@@ -209,6 +281,14 @@ class DLEV2Service {
|
||||
fs.writeFileSync(savedPath, JSON.stringify(dleData, null, 2));
|
||||
// logger.info(`DLE данные сохранены в: ${savedPath}`); // Убрано избыточное логирование
|
||||
|
||||
// WebSocket обновление: финализация
|
||||
if (deploymentId) {
|
||||
deploymentTracker.updateProgress(deploymentId, 'Завершение', 100, 'Деплой успешно завершен!');
|
||||
deploymentTracker.addLog(deploymentId, `🎉 DLE ${result.data.name} (${result.data.symbol}) успешно создан!`, 'success');
|
||||
deploymentTracker.addLog(deploymentId, `📊 Партнеров: ${result.data.partnerBalances?.length || 0}`, 'info');
|
||||
deploymentTracker.addLog(deploymentId, `💰 Общий supply: ${result.data.totalSupply || 'N/A'}`, 'info');
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: dleData
|
||||
@@ -220,31 +300,25 @@ class DLEV2Service {
|
||||
logger.warn('Не удалось сохранить локальную карточку DLE:', e.message);
|
||||
}
|
||||
|
||||
// Сохраняем ключ Etherscan V2 для последующих авто‑обновлений статуса, если он передан
|
||||
try {
|
||||
if (dleParams.etherscanApiKey) {
|
||||
const { setSecret } = require('./secretStore');
|
||||
await setSecret('ETHERSCAN_V2_API_KEY', dleParams.etherscanApiKey);
|
||||
}
|
||||
} catch (_) {}
|
||||
// Etherscan API Key уже сохранен в начале функции
|
||||
|
||||
// Авто-верификация через Etherscan V2 (опционально)
|
||||
if (dleParams.autoVerifyAfterDeploy) {
|
||||
try {
|
||||
await this.autoVerifyAcrossChains({
|
||||
deployParams,
|
||||
deployResult: result,
|
||||
apiKey: dleParams.etherscanApiKey
|
||||
});
|
||||
} catch (e) {
|
||||
logger.warn('Авто-верификация завершилась с ошибкой:', e.message);
|
||||
}
|
||||
// Верификация выполняется в deploy-multichain.js
|
||||
|
||||
// WebSocket обновление: деплой успешно завершен
|
||||
if (deploymentId) {
|
||||
deploymentTracker.completeDeployment(deploymentId, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Ошибка при создании DLE v2:', error);
|
||||
|
||||
// WebSocket обновление: деплой завершился с ошибкой
|
||||
if (deploymentId) {
|
||||
deploymentTracker.failDeployment(deploymentId, error);
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
try {
|
||||
@@ -423,9 +497,6 @@ class DLEV2Service {
|
||||
// Принимаем как строки, так и числа; конвертируем в base units (18 знаков)
|
||||
try {
|
||||
if (typeof rawAmount === 'number' && Number.isFinite(rawAmount)) {
|
||||
if (typeof ethers.parseUnits !== 'function') {
|
||||
throw new Error('Метод ethers.parseUnits не найден');
|
||||
}
|
||||
return ethers.parseUnits(rawAmount.toString(), 18).toString();
|
||||
}
|
||||
if (typeof rawAmount === 'string') {
|
||||
@@ -435,9 +506,6 @@ class DLEV2Service {
|
||||
return BigInt(a).toString();
|
||||
}
|
||||
// Десятичная строка — конвертируем в base units
|
||||
if (typeof ethers.parseUnits !== 'function') {
|
||||
throw new Error('Метод ethers.parseUnits не найден');
|
||||
}
|
||||
return ethers.parseUnits(a, 18).toString();
|
||||
}
|
||||
// BigInt или иные типы — приводим к строке без изменения масштаба
|
||||
@@ -530,7 +598,7 @@ class DLEV2Service {
|
||||
const hardhatProcess = spawn('npx', ['hardhat', 'run', scriptPath], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
env: envVars,
|
||||
stdio: 'pipe'
|
||||
stdio: ['inherit', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
@@ -575,13 +643,19 @@ class DLEV2Service {
|
||||
|
||||
const envVars = {
|
||||
...process.env,
|
||||
PRIVATE_KEY: opts.privateKey
|
||||
PRIVATE_KEY: opts.privateKey,
|
||||
ETHERSCAN_API_KEY: opts.etherscanApiKey || ''
|
||||
};
|
||||
|
||||
logger.info(`🔑 Передаем в deploy-multichain.js: ETHERSCAN_API_KEY=${opts.etherscanApiKey ? '[ЕСТЬ]' : '[НЕТ]'}`);
|
||||
logger.info(`🔑 Передаем в deploy-multichain.js: PRIVATE_KEY=${opts.privateKey ? '[ЕСТЬ]' : '[НЕТ]'}`);
|
||||
logger.info(`🔑 PRIVATE_KEY длина: ${opts.privateKey ? opts.privateKey.length : 0}`);
|
||||
logger.info(`🔑 PRIVATE_KEY значение: ${opts.privateKey ? opts.privateKey.substring(0, 10) + '...' : 'undefined'}`);
|
||||
|
||||
const p = spawn('npx', ['hardhat', 'run', scriptPath], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
env: envVars,
|
||||
stdio: 'pipe'
|
||||
stdio: ['inherit', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
let stdout = '', stderr = '';
|
||||
@@ -838,11 +912,11 @@ class DLEV2Service {
|
||||
|
||||
// Преобразуем группы в массив
|
||||
return Array.from(groups.values()).map(group => ({
|
||||
...group,
|
||||
// Основной адрес DLE (из первой сети)
|
||||
dleAddress: group.networks[0]?.dleAddress,
|
||||
...group,
|
||||
// Основной адрес DLE (из первой сети)
|
||||
dleAddress: group.networks[0]?.dleAddress,
|
||||
// Общее количество сетей
|
||||
totalNetworks: group.networks.length,
|
||||
totalNetworks: group.networks.length,
|
||||
// Поддерживаемые сети
|
||||
supportedChainIds: group.networks.map(n => n.chainId)
|
||||
}));
|
||||
@@ -894,96 +968,6 @@ class DLEV2Service {
|
||||
}
|
||||
}
|
||||
|
||||
// Авто-расчёт INIT_CODE_HASH
|
||||
async computeInitCodeHash(params) {
|
||||
try {
|
||||
// Проверяем наличие обязательных параметров
|
||||
if (!params.name || !params.symbol || !params.location) {
|
||||
throw new Error('Отсутствуют обязательные параметры для вычисления INIT_CODE_HASH');
|
||||
}
|
||||
|
||||
const hre = require('hardhat');
|
||||
const { ethers } = hre;
|
||||
|
||||
// Проверяем, что контракт DLE существует
|
||||
try {
|
||||
const DLE = await hre.ethers.getContractFactory('DLE');
|
||||
if (!DLE) {
|
||||
throw new Error('Контракт DLE не найден в Hardhat');
|
||||
}
|
||||
} catch (contractError) {
|
||||
throw new Error(`Ошибка загрузки контракта DLE: ${contractError.message}`);
|
||||
}
|
||||
|
||||
const DLE = await hre.ethers.getContractFactory('DLE');
|
||||
const dleConfig = {
|
||||
name: params.name,
|
||||
symbol: params.symbol,
|
||||
location: params.location,
|
||||
coordinates: params.coordinates || "",
|
||||
jurisdiction: params.jurisdiction || 1,
|
||||
okvedCodes: params.okvedCodes || [],
|
||||
kpp: params.kpp || 0,
|
||||
quorumPercentage: params.quorumPercentage || 51,
|
||||
initialPartners: params.initialPartners || [],
|
||||
initialAmounts: params.initialAmounts || [],
|
||||
supportedChainIds: params.supportedChainIds || [1]
|
||||
};
|
||||
// Учитываем актуальную сигнатуру конструктора: (dleConfig, currentChainId, initializer)
|
||||
const initializer = params.initializerAddress || "0x0000000000000000000000000000000000000000";
|
||||
const currentChainId = params.currentChainId || 1; // Fallback на Ethereum mainnet
|
||||
|
||||
logger.info('Вычисление INIT_CODE_HASH с параметрами:', {
|
||||
name: dleConfig.name,
|
||||
symbol: dleConfig.symbol,
|
||||
currentChainId,
|
||||
initializer
|
||||
});
|
||||
|
||||
// Проверяем, что метод getDeployTransaction существует
|
||||
if (typeof DLE.getDeployTransaction !== 'function') {
|
||||
throw new Error('Метод getDeployTransaction не найден в контракте DLE');
|
||||
}
|
||||
|
||||
const deployTx = await DLE.getDeployTransaction(dleConfig, currentChainId, initializer);
|
||||
if (!deployTx || !deployTx.data) {
|
||||
throw new Error('Не удалось получить данные транзакции деплоя');
|
||||
}
|
||||
|
||||
const initCode = deployTx.data;
|
||||
|
||||
// Проверяем, что метод keccak256 существует
|
||||
if (typeof ethers.keccak256 !== 'function') {
|
||||
throw new Error('Метод ethers.keccak256 не найден');
|
||||
}
|
||||
|
||||
const hash = ethers.keccak256(initCode);
|
||||
|
||||
logger.info('INIT_CODE_HASH вычислен успешно:', hash);
|
||||
return hash;
|
||||
} catch (error) {
|
||||
logger.error('Ошибка при вычислении INIT_CODE_HASH:', error);
|
||||
// Fallback: возвращаем хеш на основе параметров
|
||||
const { ethers } = require('ethers');
|
||||
const fallbackData = JSON.stringify({
|
||||
name: params.name,
|
||||
symbol: params.symbol,
|
||||
location: params.location,
|
||||
jurisdiction: params.jurisdiction,
|
||||
supportedChainIds: params.supportedChainIds
|
||||
});
|
||||
|
||||
// Проверяем, что методы существуют
|
||||
if (typeof ethers.toUtf8Bytes !== 'function') {
|
||||
throw new Error('Метод ethers.toUtf8Bytes не найден');
|
||||
}
|
||||
if (typeof ethers.keccak256 !== 'function') {
|
||||
throw new Error('Метод ethers.keccak256 не найден');
|
||||
}
|
||||
|
||||
return ethers.keccak256(ethers.toUtf8Bytes(fallbackData));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1017,14 +1001,7 @@ class DLEV2Service {
|
||||
const wallet = new ethers.Wallet(privateKey, provider);
|
||||
const balance = await provider.getBalance(wallet.address);
|
||||
|
||||
if (typeof ethers.formatEther !== 'function') {
|
||||
throw new Error('Метод ethers.formatEther не найден');
|
||||
}
|
||||
const balanceEth = ethers.formatEther(balance);
|
||||
|
||||
if (typeof ethers.parseEther !== 'function') {
|
||||
throw new Error('Метод ethers.parseEther не найден');
|
||||
}
|
||||
const minBalance = ethers.parseEther("0.001");
|
||||
const ok = balance >= minBalance;
|
||||
|
||||
@@ -1057,155 +1034,7 @@ class DLEV2Service {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Авто-верификация контракта во всех выбранных сетях через Etherscan V2
|
||||
* @param {Object} args
|
||||
* @param {Object} args.deployParams
|
||||
* @param {Object} args.deployResult - { success, data: { dleAddress, networks: [{rpcUrl,address}] } }
|
||||
* @param {string} [args.apiKey]
|
||||
*/
|
||||
async autoVerifyAcrossChains({ deployParams, deployResult, apiKey }) {
|
||||
if (!deployResult?.success) throw new Error('Нет результата деплоя для верификации');
|
||||
|
||||
// Подхватить ключ из secrets, если аргумент не передан
|
||||
if (!apiKey) {
|
||||
try {
|
||||
const { getSecret } = require('./secretStore');
|
||||
apiKey = await getSecret('ETHERSCAN_V2_API_KEY');
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// Получаем компилер, standard-json-input и contractName из artifacts/build-info
|
||||
const { standardJson, compilerVersion, contractName, constructorArgsHex } = await this.prepareVerificationPayload(deployParams);
|
||||
|
||||
// Для каждой сети отправим верификацию, используя адрес из результата для соответствующего chainId
|
||||
const chainIds = Array.isArray(deployParams.supportedChainIds) ? deployParams.supportedChainIds : [];
|
||||
const netMap = new Map();
|
||||
if (Array.isArray(deployResult.data?.networks)) {
|
||||
for (const n of deployResult.data.networks) {
|
||||
if (n && typeof n.chainId === 'number') netMap.set(n.chainId, n.address);
|
||||
}
|
||||
}
|
||||
for (const cid of chainIds) {
|
||||
try {
|
||||
const addrForChain = netMap.get(cid);
|
||||
if (!addrForChain) {
|
||||
logger.warn(`[AutoVerify] Нет адреса для chainId=${cid} в результате деплоя, пропускаю`);
|
||||
continue;
|
||||
}
|
||||
const guid = await etherscanV2.submitVerification({
|
||||
chainId: cid,
|
||||
contractAddress: addrForChain,
|
||||
contractName,
|
||||
compilerVersion,
|
||||
standardJsonInput: standardJson,
|
||||
constructorArgsHex,
|
||||
apiKey
|
||||
});
|
||||
logger.info(`[AutoVerify] Отправлена верификация в chainId=${cid}, guid=${guid}`);
|
||||
verificationStore.updateChain(addrForChain, cid, { guid, status: 'submitted' });
|
||||
} catch (e) {
|
||||
logger.warn(`[AutoVerify] Ошибка отправки верификации для chainId=${cid}: ${e.message}`);
|
||||
const addrForChain = netMap.get(cid) || 'unknown';
|
||||
verificationStore.updateChain(addrForChain, cid, { status: `error: ${e.message}` });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Формирует стандартный JSON input, compilerVersion, contractName и ABI-кодированные аргументы конструктора
|
||||
*/
|
||||
async prepareVerificationPayload(params) {
|
||||
const hre = require('hardhat');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// 1) Найти самый свежий build-info
|
||||
const buildInfoDir = path.join(__dirname, '..', 'artifacts', 'build-info');
|
||||
let latestFile = null;
|
||||
try {
|
||||
const entries = fs.readdirSync(buildInfoDir).filter(f => f.endsWith('.json'));
|
||||
let bestMtime = 0;
|
||||
for (const f of entries) {
|
||||
const fp = path.join(buildInfoDir, f);
|
||||
const st = fs.statSync(fp);
|
||||
if (st.mtimeMs > bestMtime) { bestMtime = st.mtimeMs; latestFile = fp; }
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('Артефакты build-info не найдены:', e.message);
|
||||
}
|
||||
|
||||
let standardJson = null;
|
||||
let compilerVersion = null;
|
||||
let sourcePathForDLE = 'contracts/DLE.sol';
|
||||
let contractName = 'contracts/DLE.sol:DLE';
|
||||
|
||||
if (latestFile) {
|
||||
try {
|
||||
const buildInfo = JSON.parse(fs.readFileSync(latestFile, 'utf8'));
|
||||
// input — это стандартный JSON input для solc
|
||||
standardJson = buildInfo.input || null;
|
||||
// Версия компилятора
|
||||
const long = buildInfo.solcLongVersion || buildInfo.solcVersion || hre.config.solidity?.version;
|
||||
compilerVersion = long ? (long.startsWith('v') ? long : `v${long}`) : undefined;
|
||||
|
||||
// Найти путь контракта DLE
|
||||
if (buildInfo.output && buildInfo.output.contracts) {
|
||||
for (const [filePathKey, contractsMap] of Object.entries(buildInfo.output.contracts)) {
|
||||
if (contractsMap && contractsMap['DLE']) {
|
||||
sourcePathForDLE = filePathKey;
|
||||
contractName = `${filePathKey}:DLE`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('Не удалось прочитать build-info:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Если не нашли — fallback на config
|
||||
if (!compilerVersion) compilerVersion = `v${hre.config.solidity.compilers?.[0]?.version || hre.config.solidity.version}`;
|
||||
if (!standardJson) {
|
||||
// fallback минимальная структура
|
||||
standardJson = {
|
||||
language: 'Solidity',
|
||||
sources: { [sourcePathForDLE]: { content: '' } },
|
||||
settings: { optimizer: { enabled: true, runs: 200 } }
|
||||
};
|
||||
}
|
||||
|
||||
// 2) Посчитать ABI-код аргументов конструктора через сравнение с bytecode
|
||||
// Конструктор: (dleConfig, currentChainId, initializer)
|
||||
const Factory = await hre.ethers.getContractFactory('DLE');
|
||||
const dleConfig = {
|
||||
name: params.name,
|
||||
symbol: params.symbol,
|
||||
location: params.location,
|
||||
coordinates: params.coordinates,
|
||||
jurisdiction: params.jurisdiction,
|
||||
okvedCodes: params.okvedCodes || [],
|
||||
kpp: params.kpp,
|
||||
quorumPercentage: params.quorumPercentage,
|
||||
initialPartners: params.initialPartners,
|
||||
initialAmounts: params.initialAmounts,
|
||||
supportedChainIds: params.supportedChainIds
|
||||
};
|
||||
const initializer = params.initialPartners?.[0] || "0x0000000000000000000000000000000000000000";
|
||||
const deployTx = await Factory.getDeployTransaction(dleConfig, params.currentChainId, initializer);
|
||||
const fullData = deployTx.data; // 0x + creation bytecode + encoded args
|
||||
const bytecode = Factory.bytecode; // 0x + creation bytecode
|
||||
let constructorArgsHex;
|
||||
try {
|
||||
if (fullData && bytecode && fullData.startsWith(bytecode)) {
|
||||
constructorArgsHex = '0x' + fullData.slice(bytecode.length);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('Не удалось выделить constructorArguments из deployTx.data:', e.message);
|
||||
}
|
||||
|
||||
return { standardJson, compilerVersion, contractName, constructorArgsHex };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new DLEV2Service();
|
||||
Reference in New Issue
Block a user