397 lines
16 KiB
JavaScript
397 lines
16 KiB
JavaScript
/**
|
||
* Copyright (c) 2024-2026 Тарабанов Александр Викторович
|
||
* 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/VC-HB3-Accelerator
|
||
*/
|
||
|
||
const express = require('express');
|
||
const router = express.Router();
|
||
const { ethers } = require('ethers');
|
||
const rpcProviderService = require('../services/rpcProviderService');
|
||
const { MODULE_IDS, MODULE_ID_TO_TYPE, MODULE_NAMES } = require('../constants/moduleIds');
|
||
const { getSupportedChainIds } = require('../utils/networkLoader');
|
||
|
||
// Получить расширенную историю DLE
|
||
router.post('/get-extended-history', async (req, res) => {
|
||
try {
|
||
const { dleAddress } = req.body;
|
||
|
||
if (!dleAddress) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
error: 'Адрес DLE обязателен'
|
||
});
|
||
}
|
||
|
||
console.log(`[DLE History] Получение расширенной истории для DLE: ${dleAddress}`);
|
||
|
||
// Определяем корректную сеть для данного адреса
|
||
let rpcUrl, targetChainId;
|
||
let candidateChainIds = []; // Будет заполнено из deploy_params
|
||
|
||
try {
|
||
// Получаем поддерживаемые сети из параметров деплоя
|
||
const latestParams = await deployParamsService.getLatestDeployParams(1);
|
||
if (latestParams.length > 0) {
|
||
const params = latestParams[0];
|
||
candidateChainIds = params.supportedChainIds || candidateChainIds;
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
|
||
}
|
||
|
||
for (const cid of candidateChainIds) {
|
||
try {
|
||
const url = await rpcProviderService.getRpcUrlByChainId(cid);
|
||
if (!url) continue;
|
||
const prov = new ethers.JsonRpcProvider(url);
|
||
const code = await prov.getCode(dleAddress);
|
||
if (code && code !== '0x') {
|
||
rpcUrl = url;
|
||
targetChainId = cid;
|
||
break;
|
||
}
|
||
} catch (_) {}
|
||
}
|
||
|
||
if (!rpcUrl) {
|
||
return res.status(500).json({
|
||
success: false,
|
||
error: 'Не удалось найти сеть, где по адресу есть контракт'
|
||
});
|
||
}
|
||
if (!rpcUrl) {
|
||
return res.status(500).json({
|
||
success: false,
|
||
error: 'RPC URL для Sepolia не найден'
|
||
});
|
||
}
|
||
|
||
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
|
||
|
||
const dleAbi = [
|
||
"function getDLEInfo() external view returns (tuple(string name, string symbol, string location, string coordinates, uint256 jurisdiction, uint256 oktmo, string[] okvedCodes, uint256 kpp, uint256 creationTimestamp, bool isActive))",
|
||
"function getGovernanceParams() external view returns (uint256 quorumPct, uint256 chainId, uint256 supportedCount)",
|
||
"function getCurrentChainId() external view returns (uint256)",
|
||
"function listSupportedChains() external view returns (uint256[] memory)",
|
||
"function getProposalsCount() external view returns (uint256)",
|
||
"event QuorumPercentageUpdated(uint256 oldQuorumPercentage, uint256 newQuorumPercentage)",
|
||
"event DLEInfoUpdated(string name, string symbol, string location, string coordinates, uint256 jurisdiction, string[] okvedCodes, uint256 kpp)",
|
||
"event ModuleAdded(bytes32 moduleId, address moduleAddress)",
|
||
"event ModuleRemoved(bytes32 moduleId)",
|
||
"event ChainAdded(uint256 chainId)",
|
||
"event ChainRemoved(uint256 chainId)",
|
||
"event ProposalExecutionApprovedInChain(uint256 proposalId, uint256 chainId)",
|
||
"event ProposalCreated(uint256 proposalId, address initiator, string description)",
|
||
"event ProposalExecuted(uint256 proposalId, bytes operation)",
|
||
"event ProposalCancelled(uint256 proposalId, string reason)"
|
||
];
|
||
|
||
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
|
||
|
||
// Получаем текущие данные для сравнения
|
||
const dleInfo = await dle.getDLEInfo();
|
||
const governanceParams = await dle.getGovernanceParams();
|
||
const currentChainId = await dle.getCurrentChainId();
|
||
const supportedChains = await dle.listSupportedChains();
|
||
const proposalsCount = await dle.getProposalsCount();
|
||
|
||
const history = [];
|
||
|
||
// 1. Событие создания DLE
|
||
history.push({
|
||
id: 1,
|
||
type: 'dle_created',
|
||
title: 'DLE создан',
|
||
description: `Создан DLE "${dleInfo.name}" (${dleInfo.symbol})`,
|
||
timestamp: Number(dleInfo.creationTimestamp) * 1000,
|
||
blockNumber: 0,
|
||
transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
||
details: {
|
||
name: dleInfo.name,
|
||
symbol: dleInfo.symbol,
|
||
location: dleInfo.location,
|
||
jurisdiction: Number(dleInfo.jurisdiction),
|
||
supportedChains: supportedChains.map(chain => Number(chain))
|
||
}
|
||
});
|
||
|
||
// 2. История изменений настроек (кворум, цепочка)
|
||
const currentBlock = await provider.getBlockNumber();
|
||
const fromBlock = Math.max(0, currentBlock - 10000);
|
||
|
||
try {
|
||
// События изменения кворума
|
||
const quorumEvents = await dle.queryFilter('QuorumPercentageUpdated', fromBlock, currentBlock);
|
||
for (let i = 0; i < quorumEvents.length; i++) {
|
||
const event = quorumEvents[i];
|
||
history.push({
|
||
id: history.length + 1,
|
||
type: 'quorum_updated',
|
||
title: 'Изменен кворум',
|
||
description: `Кворум изменен с ${Number(event.args.oldQuorumPercentage)}% на ${Number(event.args.newQuorumPercentage)}%`,
|
||
timestamp: event.blockNumber * 1000,
|
||
blockNumber: event.blockNumber,
|
||
transactionHash: event.transactionHash,
|
||
details: {
|
||
oldQuorum: Number(event.args.oldQuorumPercentage),
|
||
newQuorum: Number(event.args.newQuorumPercentage)
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
// События обновления информации DLE
|
||
const infoEvents = await dle.queryFilter('DLEInfoUpdated', fromBlock, currentBlock);
|
||
for (let i = 0; i < infoEvents.length; i++) {
|
||
const event = infoEvents[i];
|
||
history.push({
|
||
id: history.length + 1,
|
||
type: 'dle_info_updated',
|
||
title: 'Обновлена информация DLE',
|
||
description: `Обновлена информация: ${event.args.name} (${event.args.symbol})`,
|
||
timestamp: event.blockNumber * 1000,
|
||
blockNumber: event.blockNumber,
|
||
transactionHash: event.transactionHash,
|
||
details: {
|
||
name: event.args.name,
|
||
symbol: event.args.symbol,
|
||
location: event.args.location,
|
||
jurisdiction: Number(event.args.jurisdiction)
|
||
}
|
||
});
|
||
}
|
||
|
||
// 3. История модулей
|
||
const moduleAddedEvents = await dle.queryFilter('ModuleAdded', fromBlock, currentBlock);
|
||
for (let i = 0; i < moduleAddedEvents.length; i++) {
|
||
const event = moduleAddedEvents[i];
|
||
const moduleName = getModuleName(event.args.moduleId);
|
||
history.push({
|
||
id: history.length + 1,
|
||
type: 'module_added',
|
||
title: 'Модуль добавлен',
|
||
description: `Добавлен модуль "${moduleName}"`,
|
||
timestamp: event.blockNumber * 1000,
|
||
blockNumber: event.blockNumber,
|
||
transactionHash: event.transactionHash,
|
||
details: {
|
||
moduleId: event.args.moduleId,
|
||
moduleName: moduleName,
|
||
moduleAddress: event.args.moduleAddress
|
||
}
|
||
});
|
||
}
|
||
|
||
const moduleRemovedEvents = await dle.queryFilter('ModuleRemoved', fromBlock, currentBlock);
|
||
for (let i = 0; i < moduleRemovedEvents.length; i++) {
|
||
const event = moduleRemovedEvents[i];
|
||
const moduleName = getModuleName(event.args.moduleId);
|
||
history.push({
|
||
id: history.length + 1,
|
||
type: 'module_removed',
|
||
title: 'Модуль удален',
|
||
description: `Удален модуль "${moduleName}"`,
|
||
timestamp: event.blockNumber * 1000,
|
||
blockNumber: event.blockNumber,
|
||
transactionHash: event.transactionHash,
|
||
details: {
|
||
moduleId: event.args.moduleId,
|
||
moduleName: moduleName
|
||
}
|
||
});
|
||
}
|
||
|
||
// 4. Мульти-чейн история
|
||
const chainAddedEvents = await dle.queryFilter('ChainAdded', fromBlock, currentBlock);
|
||
for (let i = 0; i < chainAddedEvents.length; i++) {
|
||
const event = chainAddedEvents[i];
|
||
const chainName = getChainName(Number(event.args.chainId));
|
||
history.push({
|
||
id: history.length + 1,
|
||
type: 'chain_added',
|
||
title: 'Сеть добавлена',
|
||
description: `Добавлена сеть "${chainName}" (ID: ${Number(event.args.chainId)})`,
|
||
timestamp: event.blockNumber * 1000,
|
||
blockNumber: event.blockNumber,
|
||
transactionHash: event.transactionHash,
|
||
details: {
|
||
chainId: Number(event.args.chainId),
|
||
chainName: chainName
|
||
}
|
||
});
|
||
}
|
||
|
||
const chainRemovedEvents = await dle.queryFilter('ChainRemoved', fromBlock, currentBlock);
|
||
for (let i = 0; i < chainRemovedEvents.length; i++) {
|
||
const event = chainRemovedEvents[i];
|
||
const chainName = getChainName(Number(event.args.chainId));
|
||
history.push({
|
||
id: history.length + 1,
|
||
type: 'chain_removed',
|
||
title: 'Сеть удалена',
|
||
description: `Удалена сеть "${chainName}" (ID: ${Number(event.args.chainId)})`,
|
||
timestamp: event.blockNumber * 1000,
|
||
blockNumber: event.blockNumber,
|
||
transactionHash: event.transactionHash,
|
||
details: {
|
||
chainId: Number(event.args.chainId),
|
||
chainName: chainName
|
||
}
|
||
});
|
||
}
|
||
|
||
const executionApprovedEvents = await dle.queryFilter('ProposalExecutionApprovedInChain', fromBlock, currentBlock);
|
||
for (let i = 0; i < executionApprovedEvents.length; i++) {
|
||
const event = executionApprovedEvents[i];
|
||
const chainName = getChainName(Number(event.args.chainId));
|
||
history.push({
|
||
id: history.length + 1,
|
||
type: 'proposal_execution_approved',
|
||
title: 'Исполнение предложения одобрено',
|
||
description: `Исполнение предложения #${Number(event.args.proposalId)} одобрено в сети "${chainName}"`,
|
||
timestamp: event.blockNumber * 1000,
|
||
blockNumber: event.blockNumber,
|
||
transactionHash: event.transactionHash,
|
||
details: {
|
||
proposalId: Number(event.args.proposalId),
|
||
chainId: Number(event.args.chainId),
|
||
chainName: chainName
|
||
}
|
||
});
|
||
}
|
||
|
||
// 5. События предложений (базовые)
|
||
const proposalEvents = await dle.queryFilter('ProposalCreated', fromBlock, currentBlock);
|
||
for (let i = 0; i < proposalEvents.length; i++) {
|
||
const event = proposalEvents[i];
|
||
history.push({
|
||
id: history.length + 1,
|
||
type: 'proposal_created',
|
||
title: `Предложение #${Number(event.args.proposalId)} создано`,
|
||
description: event.args.description,
|
||
timestamp: event.blockNumber * 1000,
|
||
blockNumber: event.blockNumber,
|
||
transactionHash: event.transactionHash,
|
||
details: {
|
||
proposalId: Number(event.args.proposalId),
|
||
initiator: event.args.initiator,
|
||
description: event.args.description
|
||
}
|
||
});
|
||
}
|
||
|
||
const proposalExecutedEvents = await dle.queryFilter('ProposalExecuted', fromBlock, currentBlock);
|
||
for (let i = 0; i < proposalExecutedEvents.length; i++) {
|
||
const event = proposalExecutedEvents[i];
|
||
history.push({
|
||
id: history.length + 1,
|
||
type: 'proposal_executed',
|
||
title: `Предложение #${Number(event.args.proposalId)} исполнено`,
|
||
description: `Предложение успешно исполнено`,
|
||
timestamp: event.blockNumber * 1000,
|
||
blockNumber: event.blockNumber,
|
||
transactionHash: event.transactionHash,
|
||
details: {
|
||
proposalId: Number(event.args.proposalId),
|
||
operation: event.args.operation
|
||
}
|
||
});
|
||
}
|
||
|
||
const proposalCancelledEvents = await dle.queryFilter('ProposalCancelled', fromBlock, currentBlock);
|
||
for (let i = 0; i < proposalCancelledEvents.length; i++) {
|
||
const event = proposalCancelledEvents[i];
|
||
history.push({
|
||
id: history.length + 1,
|
||
type: 'proposal_cancelled',
|
||
title: `Предложение #${Number(event.args.proposalId)} отменено`,
|
||
description: `Причина: ${event.args.reason}`,
|
||
timestamp: event.blockNumber * 1000,
|
||
blockNumber: event.blockNumber,
|
||
transactionHash: event.transactionHash,
|
||
details: {
|
||
proposalId: Number(event.args.proposalId),
|
||
reason: event.args.reason
|
||
}
|
||
});
|
||
}
|
||
|
||
} catch (error) {
|
||
console.log(`[DLE History] Ошибка при получении событий:`, error.message);
|
||
}
|
||
|
||
// Сортируем по времени (новые сверху)
|
||
history.sort((a, b) => b.timestamp - a.timestamp);
|
||
|
||
console.log(`[DLE History] Расширенная история получена:`, history.length, 'событий');
|
||
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
history: history,
|
||
totalEvents: history.length,
|
||
dleInfo: {
|
||
name: dleInfo.name,
|
||
symbol: dleInfo.symbol,
|
||
creationTimestamp: Number(dleInfo.creationTimestamp),
|
||
proposalsCount: Number(proposalsCount),
|
||
currentChainId: Number(currentChainId),
|
||
supportedChains: supportedChains.map(chain => Number(chain))
|
||
}
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('[DLE History] Ошибка при получении расширенной истории:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
error: 'Ошибка при получении расширенной истории: ' + error.message
|
||
});
|
||
}
|
||
});
|
||
|
||
// Вспомогательные функции
|
||
function getModuleName(moduleId) {
|
||
// Проверяем стандартные модули
|
||
if (MODULE_ID_TO_TYPE[moduleId]) {
|
||
const moduleType = MODULE_ID_TO_TYPE[moduleId];
|
||
return MODULE_NAMES[moduleType] || moduleType;
|
||
}
|
||
|
||
// Дополнительные модули (если появятся в будущем)
|
||
const additionalModuleNames = {
|
||
'0x6d756c7469736967000000000000000000000000000000000000000000000000': 'Multisig',
|
||
'0x646561637469766174696f6e0000000000000000000000000000000000000000': 'Deactivation',
|
||
'0x616e616c79746963730000000000000000000000000000000000000000000000': 'Analytics',
|
||
'0x6e6f74696669636174696f6e7300000000000000000000000000000000000000': 'Notifications'
|
||
};
|
||
|
||
return additionalModuleNames[moduleId] || `Module ${moduleId}`;
|
||
}
|
||
|
||
function getChainName(chainId) {
|
||
const chainNames = {
|
||
1: 'Ethereum Mainnet',
|
||
11155111: 'Sepolia Testnet',
|
||
137: 'Polygon',
|
||
56: 'BSC',
|
||
42161: 'Arbitrum One',
|
||
17000: 'Holesky Testnet'
|
||
};
|
||
return chainNames[chainId] || `Chain ID: ${chainId}`;
|
||
}
|
||
|
||
// Экспортируем функции для использования в других модулях
|
||
module.exports = {
|
||
router,
|
||
getModuleName,
|
||
getChainName
|
||
};
|