Files
DLE/backend/routes/dleProposals.js

799 lines
28 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.

/**
* 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');
// Получение списка всех предложений
router.post('/get-proposals', async (req, res) => {
try {
const { dleAddress } = req.body;
if (!dleAddress) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
});
}
console.log(`[DLE Proposals] Получение списка предложений для DLE: ${dleAddress}`);
// Получаем RPC URL для Sepolia
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
// ABI для чтения предложений (используем правильные функции из смарт-контракта)
const dleAbi = [
"function getProposalSummary(uint256 _proposalId) external view returns (uint256 id, string memory description, uint256 forVotes, uint256 againstVotes, bool executed, bool canceled, uint256 deadline, address initiator, uint256 governanceChainId, uint256 snapshotTimepoint, uint256[] memory targets)",
"function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)",
"function getProposalState(uint256 _proposalId) external view returns (uint8 state)",
"event ProposalCreated(uint256 proposalId, address initiator, string description)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем события ProposalCreated для определения количества предложений
const currentBlock = await provider.getBlockNumber();
const fromBlock = Math.max(0, currentBlock - 10000); // Последние 10000 блоков
const events = await dle.queryFilter('ProposalCreated', fromBlock, currentBlock);
console.log(`[DLE Proposals] Найдено событий ProposalCreated: ${events.length}`);
console.log(`[DLE Proposals] Диапазон блоков: ${fromBlock} - ${currentBlock}`);
const proposals = [];
// Читаем информацию о каждом предложении
for (let i = 0; i < events.length; i++) {
try {
const proposalId = events[i].args.proposalId;
console.log(`[DLE Proposals] Читаем предложение ID: ${proposalId}`);
// Пробуем несколько раз для новых предложений
let proposal, isPassed;
let retryCount = 0;
const maxRetries = 3;
while (retryCount < maxRetries) {
try {
proposal = await dle.getProposalSummary(proposalId);
const result = await dle.checkProposalResult(proposalId);
isPassed = result.passed;
break; // Успешно прочитали
} catch (error) {
retryCount++;
console.log(`[DLE Proposals] Попытка ${retryCount} чтения предложения ${proposalId} не удалась:`, error.message);
if (retryCount < maxRetries) {
await new Promise(resolve => setTimeout(resolve, 2000)); // Ждем 2 секунды
} else {
throw error; // Превышено количество попыток
}
}
}
console.log(`[DLE Proposals] Данные предложения ${proposalId}:`, {
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,
governanceChainId: Number(proposal.governanceChainId),
snapshotTimepoint: Number(proposal.snapshotTimepoint),
targets: proposal.targets
});
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,
governanceChainId: Number(proposal.governanceChainId),
snapshotTimepoint: Number(proposal.snapshotTimepoint),
targetChains: proposal.targets.map(chainId => Number(chainId)),
isPassed: isPassed,
blockNumber: events[i].blockNumber
};
proposals.push(proposalInfo);
} catch (error) {
console.log(`[DLE Proposals] Ошибка при чтении предложения ${i}:`, error.message);
// Если это ошибка декодирования, возможно предложение еще не полностью записано
if (error.message.includes('could not decode result data')) {
console.log(`[DLE Proposals] Предложение ${i} еще не полностью синхронизировано, пропускаем`);
continue;
}
// Продолжаем с следующим предложением
}
}
// Сортируем по ID предложения (новые сверху)
proposals.sort((a, b) => b.id - a.id);
console.log(`[DLE Proposals] Найдено предложений: ${proposals.length}`);
res.json({
success: true,
data: {
proposals: proposals,
totalCount: proposals.length
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при получении списка предложений:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении списка предложений: ' + error.message
});
}
});
// Получение информации о предложении
router.post('/get-proposal-info', async (req, res) => {
try {
const { dleAddress, proposalId } = req.body;
if (!dleAddress || proposalId === undefined) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны: dleAddress, proposalId'
});
}
console.log(`[DLE Proposals] Получение информации о предложении ${proposalId} в DLE: ${dleAddress}`);
// Получаем RPC URL для Sepolia
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
// ABI для чтения информации о предложении
const dleAbi = [
"function proposals(uint256) external view returns (tuple(string description, uint256 duration, bytes operation, uint256 governanceChainId, uint256 startTime, bool executed, uint256 forVotes, uint256 againstVotes))",
"function checkProposalResult(uint256 _proposalId) external view returns (bool)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Читаем информацию о предложении
const proposal = await dle.proposals(proposalId);
const isPassed = await dle.checkProposalResult(proposalId);
// governanceChainId не сохраняется в предложении, используем текущую цепочку
const governanceChainId = 11155111; // Sepolia chain ID
const proposalInfo = {
description: proposal.description,
duration: Number(proposal.duration),
operation: proposal.operation,
governanceChainId: Number(proposal.governanceChainId),
startTime: Number(proposal.startTime),
executed: proposal.executed,
forVotes: Number(proposal.forVotes),
againstVotes: Number(proposal.againstVotes),
isPassed: isPassed
};
console.log(`[DLE Proposals] Информация о предложении получена:`, proposalInfo);
res.json({
success: true,
data: proposalInfo
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при получении информации о предложении:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении информации о предложении: ' + error.message
});
}
});
// Получить состояние предложения
router.post('/get-proposal-state', async (req, res) => {
try {
const { dleAddress, proposalId } = req.body;
if (!dleAddress || proposalId === undefined) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[DLE Proposals] Получение состояния предложения ${proposalId} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getProposalState(uint256 _proposalId) public view returns (uint8 state)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем состояние предложения
const state = await dle.getProposalState(proposalId);
console.log(`[DLE Proposals] Состояние предложения ${proposalId}: ${state}`);
res.json({
success: true,
data: {
proposalId: Number(proposalId),
state: Number(state)
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при получении состояния предложения:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении состояния предложения: ' + error.message
});
}
});
// Получить голоса по предложению
router.post('/get-proposal-votes', async (req, res) => {
try {
const { dleAddress, proposalId } = req.body;
if (!dleAddress || proposalId === undefined) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[DLE Proposals] Получение голосов по предложению ${proposalId} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getProposalVotes(uint256 _proposalId) external view returns (uint256 forVotes, uint256 againstVotes, uint256 totalVotes, uint256 quorumRequired)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем голоса по предложению
const votes = await dle.getProposalVotes(proposalId);
console.log(`[DLE Proposals] Голоса по предложению ${proposalId}:`, votes);
res.json({
success: true,
data: {
proposalId: Number(proposalId),
forVotes: Number(votes.forVotes),
againstVotes: Number(votes.againstVotes),
totalVotes: Number(votes.totalVotes),
quorumRequired: Number(votes.quorumRequired)
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при получении голосов по предложению:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении голосов по предложению: ' + error.message
});
}
});
// Получить количество предложений
router.post('/get-proposals-count', async (req, res) => {
try {
const { dleAddress } = req.body;
if (!dleAddress) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
});
}
console.log(`[DLE Proposals] Получение количества предложений для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getProposalsCount() external view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем количество предложений
const count = await dle.getProposalsCount();
console.log(`[DLE Proposals] Количество предложений: ${count}`);
res.json({
success: true,
data: {
count: Number(count)
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при получении количества предложений:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении количества предложений: ' + error.message
});
}
});
// Получить список предложений с пагинацией
router.post('/list-proposals', async (req, res) => {
try {
const { dleAddress, offset, limit } = req.body;
if (!dleAddress || offset === undefined || limit === undefined) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[DLE Proposals] Получение списка предложений для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function listProposals(uint256 offset, uint256 limit) external view returns (uint256[] memory)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем список предложений
const proposals = await dle.listProposals(offset, limit);
console.log(`[DLE Proposals] Список предложений:`, proposals);
res.json({
success: true,
data: {
proposals: proposals.map(p => Number(p)),
offset: Number(offset),
limit: Number(limit)
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при получении списка предложений:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении списка предложений: ' + error.message
});
}
});
// Получить голосующую силу на момент времени
router.post('/get-voting-power-at', async (req, res) => {
try {
const { dleAddress, voter, timepoint } = req.body;
if (!dleAddress || !voter || timepoint === undefined) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[DLE Proposals] Получение голосующей силы для ${voter} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getVotingPowerAt(address voter, uint256 timepoint) external view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем голосующую силу
const votingPower = await dle.getVotingPowerAt(voter, timepoint);
console.log(`[DLE Proposals] Голосующая сила для ${voter}: ${votingPower}`);
res.json({
success: true,
data: {
voter: voter,
timepoint: Number(timepoint),
votingPower: Number(votingPower)
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при получении голосующей силы:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении голосующей силы: ' + error.message
});
}
});
// Получить требуемый кворум на момент времени
router.post('/get-quorum-at', async (req, res) => {
try {
const { dleAddress, timepoint } = req.body;
if (!dleAddress || timepoint === undefined) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[DLE Proposals] Получение требуемого кворума для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getQuorumAt(uint256 timepoint) external view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем требуемый кворум
const quorum = await dle.getQuorumAt(timepoint);
console.log(`[DLE Proposals] Требуемый кворум: ${quorum}`);
res.json({
success: true,
data: {
timepoint: Number(timepoint),
quorum: Number(quorum)
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при получении требуемого кворума:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении требуемого кворума: ' + error.message
});
}
});
// Исполнить предложение
router.post('/execute-proposal', async (req, res) => {
try {
const { dleAddress, proposalId, userAddress, privateKey } = req.body;
if (!dleAddress || proposalId === undefined || !userAddress || !privateKey) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны, включая приватный ключ'
});
}
console.log(`[DLE Proposals] Исполнение предложения ${proposalId} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const wallet = new ethers.Wallet(privateKey, provider);
const dleAbi = [
"function executeProposal(uint256 _proposalId) external"
];
const dle = new ethers.Contract(dleAddress, dleAbi, wallet);
// Исполняем предложение
const tx = await dle.executeProposal(proposalId);
const receipt = await tx.wait();
console.log(`[DLE Proposals] Предложение исполнено:`, receipt);
res.json({
success: true,
data: {
transactionHash: receipt.hash
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при исполнении предложения:', error);
res.status(500).json({
success: false,
error: 'Ошибка при исполнении предложения: ' + error.message
});
}
});
// Отменить предложение
router.post('/cancel-proposal', async (req, res) => {
try {
const { dleAddress, proposalId, reason, userAddress } = req.body;
if (!dleAddress || proposalId === undefined || !reason || !userAddress) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[DLE Proposals] Отмена предложения ${proposalId} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function cancelProposal(uint256 _proposalId, string calldata reason) external"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Отменяем предложение
const tx = await dle.cancelProposal(proposalId, reason);
const receipt = await tx.wait();
console.log(`[DLE Proposals] Предложение отменено:`, receipt);
res.json({
success: true,
data: {
transactionHash: receipt.hash
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при отмене предложения:', error);
res.status(500).json({
success: false,
error: 'Ошибка при отмене предложения: ' + error.message
});
}
});
// Получить количество предложений
router.post('/get-proposals-count', async (req, res) => {
try {
const { dleAddress } = req.body;
if (!dleAddress) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
});
}
console.log(`[DLE Proposals] Получение количества предложений для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getProposalsCount() external view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
const count = await dle.getProposalsCount();
console.log(`[DLE Proposals] Количество предложений: ${count}`);
res.json({
success: true,
data: {
count: Number(count)
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при получении количества предложений:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении количества предложений: ' + error.message
});
}
});
// Получить список предложений с пагинацией
router.post('/list-proposals', async (req, res) => {
try {
const { dleAddress, offset = 0, limit = 10 } = req.body;
if (!dleAddress) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
});
}
console.log(`[DLE Proposals] Получение списка предложений для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function listProposals(uint256 offset, uint256 limit) external view returns (uint256[] memory)",
"function getProposalSummary(uint256 _proposalId) external view returns (uint256 id, string memory description, uint256 forVotes, uint256 againstVotes, bool executed, bool canceled, uint256 deadline, address initiator, uint256 governanceChainId, uint256 snapshotTimepoint, uint256[] memory targets)",
"function getProposalState(uint256 _proposalId) external view returns (uint8 state)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем список ID предложений
const proposalIds = await dle.listProposals(offset, limit);
console.log(`[DLE Proposals] Получены ID предложений:`, proposalIds);
console.log(`[DLE Proposals] Количество ID:`, proposalIds.length);
const proposals = [];
// Получаем детали каждого предложения
console.log(`[DLE Proposals] Начинаем обработку предложений...`);
for (const proposalId of proposalIds) {
try {
const proposal = await dle.getProposalSummary(proposalId);
const state = await dle.getProposalState(proposalId);
proposals.push({
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,
governanceChainId: Number(proposal.governanceChainId),
snapshotTimepoint: Number(proposal.snapshotTimepoint),
targetChains: proposal.targets.map(chain => Number(chain)),
state: Number(state)
});
} catch (error) {
console.log(`[DLE Proposals] Ошибка при получении деталей предложения ${proposalId}:`, error.message);
// Добавляем базовую информацию о предложении
proposals.push({
id: Number(proposalId),
description: `Предложение #${Number(proposalId)}`,
forVotes: 0,
againstVotes: 0,
executed: false,
canceled: false,
deadline: 0,
initiator: '0x0000000000000000000000000000000000000000',
governanceChainId: 0,
snapshotTimepoint: 0,
targetChains: [],
state: 0
});
}
}
console.log(`[DLE Proposals] Получено предложений: ${proposals.length}`);
res.json({
success: true,
data: {
proposals: proposals,
offset: Number(offset),
limit: Number(limit)
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при получении списка предложений:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении списка предложений: ' + error.message
});
}
});
module.exports = router;