Files
DLE/backend/routes/dleMultichainExecution.js
2026-03-01 22:03:48 +03:00

347 lines
13 KiB
JavaScript
Raw Permalink 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-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 DeployParamsService = require('../services/deployParamsService');
/**
* Получить информацию о мультиконтрактном предложении
* @route POST /api/dle-multichain/get-proposal-multichain-info
*/
router.post('/get-proposal-multichain-info', async (req, res) => {
try {
const { dleAddress, proposalId, governanceChainId } = req.body;
if (!dleAddress || proposalId === undefined || !governanceChainId) {
return res.status(400).json({
success: false,
error: 'Адрес DLE, ID предложения и ID сети голосования обязательны'
});
}
console.log(`[DLE Multichain] Получение информации о предложении ${proposalId} для DLE: ${dleAddress}`);
// Получаем RPC URL для сети голосования
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(governanceChainId);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: `RPC URL для сети ${governanceChainId} не найден`
});
}
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function proposals(uint256) external view returns (uint256 id, string memory description, uint256 forVotes, uint256 againstVotes, bool executed, bool canceled, uint256 deadline, address initiator, bytes memory operation, uint256 governanceChainId, uint256 snapshotTimepoint, uint256[] memory targetChains)",
"function getProposalState(uint256 _proposalId) external view returns (uint8 state)",
"function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)",
"function getSupportedChainCount() external view returns (uint256)",
"function getSupportedChainId(uint256 _index) external view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем данные предложения
const proposal = await dle.proposals(proposalId);
const state = await dle.getProposalState(proposalId);
const result = await dle.checkProposalResult(proposalId);
// Получаем поддерживаемые сети
const chainCount = await dle.getSupportedChainCount();
const supportedChains = [];
for (let i = 0; i < chainCount; i++) {
const chainId = await dle.getSupportedChainId(i);
supportedChains.push(Number(chainId));
}
const proposalInfo = {
id: Number(proposal.id),
description: proposal.description,
forVotes: Number(proposal.forVotes),
againstVotes: Number(proposal.againstVotes),
executed: proposal.executed,
canceled: proposal.canceled,
deadline: Number(proposal.deadline),
initiator: proposal.initiator,
operation: proposal.operation,
governanceChainId: Number(proposal.governanceChainId),
targetChains: proposal.targetChains.map(chain => Number(chain)),
snapshotTimepoint: Number(proposal.snapshotTimepoint),
state: Number(state),
isPassed: result.passed,
quorumReached: result.quorumReached,
supportedChains: supportedChains,
canExecuteInTargetChains: result.passed && result.quorumReached && !proposal.executed && !proposal.canceled
};
console.log(`[DLE Multichain] Информация о предложении получена:`, proposalInfo);
res.json({
success: true,
data: proposalInfo
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при получении информации о предложении:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении информации о предложении: ' + error.message
});
}
});
/**
* Исполнить предложение во всех целевых сетях
* @route POST /api/dle-multichain/execute-in-all-target-chains
*/
router.post('/execute-in-all-target-chains', async (req, res) => {
try {
const { dleAddress, proposalId, deploymentId, userAddress } = req.body;
if (!dleAddress || proposalId === undefined || !deploymentId || !userAddress) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[DLE Multichain] Исполнение предложения ${proposalId} во всех целевых сетях для DLE: ${dleAddress}`);
// Получаем параметры деплоя
const deployParamsService = new DeployParamsService();
const deployParams = await deployParamsService.getDeployParams(deploymentId);
if (!deployParams || !deployParams.privateKey) {
return res.status(400).json({
success: false,
error: 'Приватный ключ не найден в параметрах деплоя'
});
}
// Получаем информацию о предложении
const proposalInfoResponse = await fetch(`${req.protocol}://${req.get('host')}/api/dle-multichain/get-proposal-multichain-info`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
dleAddress,
proposalId,
governanceChainId: deployParams.currentChainId
})
});
const proposalInfo = await proposalInfoResponse.json();
if (!proposalInfo.success) {
return res.status(400).json({
success: false,
error: 'Не удалось получить информацию о предложении'
});
}
const { targetChains, canExecuteInTargetChains } = proposalInfo.data;
if (!canExecuteInTargetChains) {
return res.status(400).json({
success: false,
error: 'Предложение не готово к исполнению в целевых сетях'
});
}
if (targetChains.length === 0) {
return res.status(400).json({
success: false,
error: 'У предложения нет целевых сетей для исполнения'
});
}
// Исполняем в каждой целевой сети
const executionResults = [];
for (const targetChainId of targetChains) {
try {
console.log(`[DLE Multichain] Исполнение в сети ${targetChainId}`);
const result = await executeProposalInChain(
dleAddress,
proposalId,
targetChainId,
deployParams.privateKey,
userAddress
);
executionResults.push({
chainId: targetChainId,
success: true,
transactionHash: result.transactionHash
});
} catch (error) {
console.error(`[DLE Multichain] Ошибка исполнения в сети ${targetChainId}:`, error.message);
executionResults.push({
chainId: targetChainId,
success: false,
error: error.message
});
}
}
const successCount = executionResults.filter(r => r.success).length;
const totalCount = executionResults.length;
console.log(`[DLE Multichain] Исполнение завершено: ${successCount}/${totalCount} успешно`);
res.json({
success: true,
data: {
proposalId,
targetChains,
executionResults,
summary: {
total: totalCount,
successful: successCount,
failed: totalCount - successCount
}
}
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при исполнении во всех целевых сетях:', error);
res.status(500).json({
success: false,
error: 'Ошибка при исполнении во всех целевых сетях: ' + error.message
});
}
});
/**
* Исполнить предложение в конкретной целевой сети
* @route POST /api/dle-multichain/execute-in-target-chain
*/
router.post('/execute-in-target-chain', async (req, res) => {
try {
const { dleAddress, proposalId, targetChainId, deploymentId, userAddress } = req.body;
if (!dleAddress || proposalId === undefined || !targetChainId || !deploymentId || !userAddress) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[DLE Multichain] Исполнение предложения ${proposalId} в сети ${targetChainId} для DLE: ${dleAddress}`);
// Получаем параметры деплоя
const deployParamsService = new DeployParamsService();
const deployParams = await deployParamsService.getDeployParams(deploymentId);
if (!deployParams || !deployParams.privateKey) {
return res.status(400).json({
success: false,
error: 'Приватный ключ не найден в параметрах деплоя'
});
}
// Исполняем в целевой сети
const result = await executeProposalInChain(
dleAddress,
proposalId,
targetChainId,
deployParams.privateKey,
userAddress
);
res.json({
success: true,
data: {
proposalId,
targetChainId,
transactionHash: result.transactionHash,
blockNumber: result.blockNumber
}
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при исполнении в целевой сети:', error);
res.status(500).json({
success: false,
error: 'Ошибка при исполнении в целевой сети: ' + error.message
});
}
});
/**
* Вспомогательная функция для исполнения предложения в конкретной сети
*/
async function executeProposalInChain(dleAddress, proposalId, chainId, privateKey, userAddress) {
// Получаем RPC URL для целевой сети
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(chainId);
if (!rpcUrl) {
throw new Error(`RPC URL для сети ${chainId} не найден`);
}
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const wallet = new ethers.Wallet(privateKey, provider);
const dleAbi = [
"function executeProposalBySignatures(uint256 _proposalId, address[] calldata signers, bytes[] calldata signatures) external"
];
const dle = new ethers.Contract(dleAddress, dleAbi, wallet);
// Для простоты используем подпись от одного адреса (кошелька с приватным ключом)
// В реальности нужно собрать подписи от держателей токенов
const signers = [wallet.address];
const signatures = []; // TODO: Реализовать сбор подписей
// Временная заглушка - используем прямое исполнение если это возможно
// В реальности нужно реализовать сбор подписей от держателей токенов
try {
// Пытаемся исполнить напрямую (если это сеть голосования)
const directExecuteAbi = [
"function executeProposal(uint256 _proposalId) external"
];
const directDle = new ethers.Contract(dleAddress, directExecuteAbi, wallet);
const tx = await directDle.executeProposal(proposalId);
const receipt = await tx.wait();
return {
transactionHash: receipt.hash,
blockNumber: receipt.blockNumber
};
} catch (directError) {
// Если прямое исполнение невозможно, используем подписи
if (signatures.length === 0) {
throw new Error('Необходимо собрать подписи от держателей токенов для исполнения в целевой сети');
}
const tx = await dle.executeProposalBySignatures(proposalId, signers, signatures);
const receipt = await tx.wait();
return {
transactionHash: receipt.hash,
blockNumber: receipt.blockNumber
};
}
}
module.exports = router;