Files
DLE/backend/services/dleService.js

252 lines
9.8 KiB
JavaScript
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.

const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
const { ethers } = require('ethers');
const logger = require('../utils/logger');
/**
* Сервис для управления DLE (Digital Legal Entity)
*/
class DLEService {
/**
* Создает новое DLE с заданными параметрами
* @param {Object} dleParams - Параметры DLE
* @returns {Promise<Object>} - Результат создания DLE
*/
async createDLE(dleParams) {
try {
logger.info('Начало создания DLE с параметрами:', dleParams);
// Валидация входных данных
this.validateDLEParams(dleParams);
// Подготовка параметров для деплоя
const deployParams = this.prepareDeployParams(dleParams);
// Сохраняем параметры во временный файл
const paramsFile = this.saveParamsToFile(deployParams);
// Копируем параметры во временный файл с предсказуемым именем
const tempParamsFile = path.join(__dirname, '../scripts/deploy/current-params.json');
logger.info(`Копирование параметров из ${paramsFile} в ${tempParamsFile}`);
const deployDir = path.dirname(tempParamsFile);
if (!fs.existsSync(deployDir)) {
fs.mkdirSync(deployDir, { recursive: true });
}
fs.copyFileSync(paramsFile, tempParamsFile);
logger.info(`Файл параметров скопирован успешно`);
// Запускаем скрипт без передачи аргументов командной строки
const result = await this.runDeployScript(paramsFile);
logger.info('DLE успешно создано:', result);
return result;
} catch (error) {
logger.error('Ошибка при создании DLE:', error);
throw error;
}
}
/**
* Валидирует параметры DLE
* @param {Object} params - Параметры DLE
*/
validateDLEParams(params) {
const requiredFields = [
'name', 'symbol', 'location', 'isicCodes',
'partners', 'amounts', 'minTimelockDelay',
'votingDelay', 'votingPeriod', 'proposalThreshold', 'quorumPercentage'
];
for (const field of requiredFields) {
if (params[field] === undefined) {
throw new Error(`Отсутствует обязательный параметр: ${field}`);
}
}
if (params.partners.length !== params.amounts.length) {
throw new Error('Количество партнеров должно соответствовать количеству сумм распределения');
}
if (params.partners.length === 0) {
throw new Error('Должен быть указан хотя бы один партнер');
}
if (params.quorumPercentage > 100) {
throw new Error('Процент кворума не может превышать 100%');
}
}
/**
* Подготавливает параметры для деплоя
* @param {Object} params - Параметры DLE
* @returns {Object} - Подготовленные параметры
*/
prepareDeployParams(params) {
// Создаем копию объекта, чтобы не изменять исходный
const deployParams = { ...params };
// Преобразуем суммы из строк или чисел в BigNumber, если нужно
deployParams.amounts = params.amounts.map(amount => {
if (typeof amount === 'string' && !amount.startsWith('0x')) {
return ethers.parseEther(amount).toString();
}
return amount.toString();
});
// Преобразуем proposalThreshold в BigNumber, если нужно
if (typeof deployParams.proposalThreshold === 'string' && !deployParams.proposalThreshold.startsWith('0x')) {
deployParams.proposalThreshold = ethers.parseEther(deployParams.proposalThreshold).toString();
} else {
deployParams.proposalThreshold = deployParams.proposalThreshold.toString();
}
return deployParams;
}
/**
* Сохраняет параметры деплоя во временный файл
* @param {Object} params - Параметры деплоя
* @returns {string} - Путь к файлу с параметрами
*/
saveParamsToFile(params) {
const tempDir = path.join(__dirname, '../temp');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
const fileName = `dle-params-${Date.now()}.json`;
const filePath = path.join(tempDir, fileName);
fs.writeFileSync(filePath, JSON.stringify(params, null, 2));
return filePath;
}
/**
* Запускает скрипт деплоя DLE
* @param {string} paramsFile - Путь к файлу с параметрами
* @returns {Promise<Object>} - Результат деплоя
*/
runDeployScript(paramsFile) {
return new Promise((resolve, reject) => {
// Путь к новому скрипту для ручного деплоя (без фабрики)
const scriptPath = path.join(__dirname, '../scripts/deploy/create-dle-manual.js');
// Проверяем, существует ли скрипт
if (!fs.existsSync(scriptPath)) {
reject(new Error('Скрипт деплоя не найден: ' + scriptPath));
return;
}
// Запускаем скрипт без передачи аргументов командной строки
const hardhatProcess = spawn('npx', ['hardhat', 'run', scriptPath, '--network', 'sepolia'], {
cwd: path.join(__dirname, '..'),
env: { ...process.env },
stdio: 'pipe'
});
let stdout = '';
let stderr = '';
hardhatProcess.stdout.on('data', (data) => {
const output = data.toString();
stdout += output;
logger.debug(`[Hardhat] ${output}`);
});
hardhatProcess.stderr.on('data', (data) => {
const output = data.toString();
stderr += output;
logger.error(`[Hardhat Error] ${output}`);
});
hardhatProcess.on('close', (code) => {
if (code !== 0) {
logger.error(`Скрипт деплоя завершился с кодом: ${code}`);
reject(new Error(`Ошибка деплоя: ${stderr}`));
return;
}
// Пытаемся извлечь адреса контрактов из вывода
try {
const tokenAddressMatch = stdout.match(/Адрес токена: (0x[a-fA-F0-9]{40})/);
const timelockAddressMatch = stdout.match(/Адрес таймлока: (0x[a-fA-F0-9]{40})/);
const governorAddressMatch = stdout.match(/Адрес контракта Governor: (0x[a-fA-F0-9]{40})/);
if (tokenAddressMatch && timelockAddressMatch && governorAddressMatch) {
resolve({
tokenAddress: tokenAddressMatch[1],
timelockAddress: timelockAddressMatch[1],
governorAddress: governorAddressMatch[1],
success: true
});
} else {
// Если не удалось извлечь адреса, ищем файл с результатами
const dlesDir = path.join(__dirname, '../contracts-data/dles');
if (fs.existsSync(dlesDir)) {
const files = fs.readdirSync(dlesDir);
if (files.length > 0) {
// Берем самый свежий файл
const latestFile = files
.map(f => ({ name: f, time: fs.statSync(path.join(dlesDir, f)).mtime.getTime() }))
.sort((a, b) => b.time - a.time)[0].name;
const dleData = JSON.parse(fs.readFileSync(path.join(dlesDir, latestFile), 'utf8'));
resolve({
...dleData,
success: true
});
} else {
reject(new Error('Не удалось найти информацию о созданном DLE'));
}
} else {
reject(new Error('Не удалось найти директорию с результатами создания DLE'));
}
}
} catch (error) {
logger.error('Ошибка при извлечении результатов деплоя:', error);
reject(error);
}
});
});
}
/**
* Получает список всех созданных DLE
* @returns {Array<Object>} - Список DLE
*/
getAllDLEs() {
try {
const dlesDir = path.join(__dirname, '../contracts-data/dles');
if (!fs.existsSync(dlesDir)) {
return [];
}
const files = fs.readdirSync(dlesDir);
return files
.filter(file => file.endsWith('.json') && file !== 'test.json' && file !== 'node-test.json')
.map(file => {
try {
const data = JSON.parse(fs.readFileSync(path.join(dlesDir, file), 'utf8'));
// Добавляем имя файла к данным DLE для возможности удаления пустых DLE
return { ...data, _fileName: file };
} catch (error) {
logger.error(`Ошибка при чтении файла ${file}:`, error);
// Для поврежденных файлов возвращаем минимальную информацию
return {
_fileName: file,
_corrupted: true
};
}
});
} catch (error) {
logger.error('Ошибка при получении списка DLE:', error);
throw error;
}
}
}
module.exports = new DLEService();