ваше сообщение коммита
This commit is contained in:
346
backend/routes/dleMultichainExecution.js
Normal file
346
backend/routes/dleMultichainExecution.js
Normal file
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
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(rpcUrl);
|
||||
|
||||
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(rpcUrl);
|
||||
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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user