ваше сообщение коммита

This commit is contained in:
2025-09-30 00:23:37 +03:00
parent ca718e3178
commit 4b03951b31
77 changed files with 17161 additions and 7255 deletions

View File

@@ -30,6 +30,12 @@ const userAccessLevel = ref({ level: 'user', tokenCount: 0, hasAccess: false });
const updateIdentities = async () => {
if (!isAuthenticated.value || !userId.value) return;
// Проверяем, что identities ref существует
if (!identities || typeof identities.value === 'undefined') {
console.warn('Identities ref is not initialized');
return;
}
try {
const response = await axios.get('/auth/identities');
if (response.data.success) {
@@ -46,14 +52,26 @@ const updateIdentities = async () => {
}, []);
// Сравниваем новый отфильтрованный список с текущим значением
const currentProviders = identities.value.map(id => id.provider).sort();
const newProviders = filteredIdentities.map(id => id.provider).sort();
const currentProviders = (identities.value || []).map(id => id?.provider || '').sort();
const newProviders = (filteredIdentities || []).map(id => id?.provider || '').sort();
const identitiesChanged = JSON.stringify(currentProviders) !== JSON.stringify(newProviders);
// Обновляем реактивное значение
identities.value = filteredIdentities;
console.log('User identities updated:', identities.value);
// Обновляем реактивное значение с проверкой
try {
if (identities && identities.value !== undefined) {
identities.value = filteredIdentities;
console.log('User identities updated:', identities.value);
} else {
console.warn('Identities ref is not available or not initialized');
}
} catch (error) {
console.error('Error updating identities:', error);
// Если произошла ошибка, пытаемся инициализировать identities
if (identities && typeof identities.value === 'undefined') {
identities.value = [];
}
}
// Если список идентификаторов изменился, принудительно проверяем аутентификацию,
// чтобы обновить authType и другие связанные данные (например, telegramId)
@@ -163,11 +181,21 @@ const updateAuth = async ({
// Обновляем идентификаторы при любом изменении аутентификации
if (authenticated) {
await updateIdentities();
startIdentitiesPolling();
try {
await updateIdentities();
startIdentitiesPolling();
} catch (error) {
console.error('Error updating identities in updateAuth:', error);
}
} else {
stopIdentitiesPolling();
identities.value = [];
try {
if (identities && typeof identities.value !== 'undefined') {
identities.value = [];
}
} catch (error) {
console.error('Error clearing identities:', error);
}
}
console.log('Auth updated:', {
@@ -306,7 +334,11 @@ const checkAuth = async () => {
// Если пользователь аутентифицирован, обновляем список идентификаторов и связываем сообщения
if (response.data.authenticated) {
// Сначала обновляем идентификаторы, чтобы иметь актуальные данные
await updateIdentities();
try {
await updateIdentities();
} catch (error) {
console.error('Error updating identities in checkAuth:', error);
}
// Если пользователь только что аутентифицировался или сменил аккаунт,
// связываем гостевые сообщения с его аккаунтом

View File

@@ -0,0 +1,349 @@
import { ref, computed } from 'vue';
import { ethers } from 'ethers';
import { DLE_ABI, TOKEN_ABI } from '@/utils/dle-abi';
/**
* Композабл для работы с DLE смарт-контрактом
* Содержит правильные ABI и функции для взаимодействия с контрактом
*/
export function useDleContract() {
// Состояние
const isConnected = ref(false);
const provider = ref(null);
const signer = ref(null);
const contract = ref(null);
const userAddress = ref(null);
const chainId = ref(null);
// Используем общий ABI из utils/dle-abi.js
/**
* Подключиться к кошельку
*/
const connectWallet = async () => {
try {
if (!window.ethereum) {
throw new Error('MetaMask не найден. Пожалуйста, установите MetaMask.');
}
// Запрашиваем подключение
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
// Создаем провайдер и подписанта
provider.value = new ethers.BrowserProvider(window.ethereum);
signer.value = await provider.value.getSigner();
userAddress.value = await signer.value.getAddress();
// Получаем информацию о сети
const network = await provider.value.getNetwork();
chainId.value = Number(network.chainId);
isConnected.value = true;
console.log('✅ Кошелек подключен:', {
address: userAddress.value,
chainId: chainId.value,
network: network.name
});
return {
success: true,
address: userAddress.value,
chainId: chainId.value
};
} catch (error) {
console.error('❌ Ошибка подключения к кошельку:', error);
isConnected.value = false;
throw error;
}
};
/**
* Инициализировать контракт
*/
const initContract = (contractAddress) => {
if (!provider.value) {
throw new Error('Провайдер не инициализирован. Сначала подключите кошелек.');
}
contract.value = new ethers.Contract(contractAddress, DLE_ABI, signer.value);
console.log('✅ DLE контракт инициализирован:', contractAddress);
};
/**
* Проверить баланс токенов пользователя
*/
const checkTokenBalance = async (contractAddress) => {
try {
if (!contract.value) {
initContract(contractAddress);
}
const balance = await contract.value.balanceOf(userAddress.value);
const balanceFormatted = ethers.formatEther(balance);
console.log(`💰 Баланс токенов: ${balanceFormatted}`);
return {
success: true,
balance: balanceFormatted,
hasTokens: balance > 0
};
} catch (error) {
console.error('❌ Ошибка проверки баланса:', error);
return {
success: false,
error: error.message,
balance: '0',
hasTokens: false
};
}
};
/**
* Голосовать за предложение
*/
const voteOnProposal = async (contractAddress, proposalId, support) => {
try {
if (!contract.value) {
initContract(contractAddress);
}
console.log('🗳️ Начинаем голосование:', { proposalId, support });
// Проверяем баланс токенов
const balanceCheck = await checkTokenBalance(contractAddress);
if (!balanceCheck.hasTokens) {
throw new Error('У вас нет токенов для голосования');
}
// Отправляем транзакцию голосования
const tx = await contract.value.vote(proposalId, support);
console.log('📤 Транзакция отправлена:', tx.hash);
// Ждем подтверждения
const receipt = await tx.wait();
console.log('✅ Голосование успешно:', receipt);
return {
success: true,
transactionHash: tx.hash,
receipt: receipt
};
} catch (error) {
console.error('❌ Ошибка голосования:', error);
// Улучшенная обработка ошибок
let errorMessage = error.message;
if (error.message.includes('execution reverted')) {
errorMessage = 'Транзакция отклонена смарт-контрактом. Возможные причины:\n' +
'• Предложение уже не активно\n' +
'• Вы уже голосовали за это предложение\n' +
'• Недостаточно прав для голосования\n' +
'• Предложение не существует';
} else if (error.message.includes('user rejected')) {
errorMessage = 'Транзакция отклонена пользователем';
} else if (error.message.includes('insufficient funds')) {
errorMessage = 'Недостаточно средств для оплаты газа';
}
return {
success: false,
error: errorMessage,
originalError: error
};
}
};
/**
* Исполнить предложение
*/
const executeProposal = async (contractAddress, proposalId) => {
try {
if (!contract.value) {
initContract(contractAddress);
}
console.log('⚡ Исполняем предложение:', proposalId);
const tx = await contract.value.executeProposal(proposalId);
console.log('📤 Транзакция отправлена:', tx.hash);
const receipt = await tx.wait();
console.log('✅ Предложение исполнено:', receipt);
return {
success: true,
transactionHash: tx.hash,
receipt: receipt
};
} catch (error) {
console.error('❌ Ошибка исполнения предложения:', error);
return {
success: false,
error: error.message,
originalError: error
};
}
};
/**
* Отменить предложение
*/
const cancelProposal = async (contractAddress, proposalId, reason) => {
try {
if (!contract.value) {
initContract(contractAddress);
}
console.log('❌ Отменяем предложение:', { proposalId, reason });
const tx = await contract.value.cancelProposal(proposalId, reason);
console.log('📤 Транзакция отправлена:', tx.hash);
const receipt = await tx.wait();
console.log('✅ Предложение отменено:', receipt);
return {
success: true,
transactionHash: tx.hash,
receipt: receipt
};
} catch (error) {
console.error('❌ Ошибка отмены предложения:', error);
return {
success: false,
error: error.message,
originalError: error
};
}
};
/**
* Получить состояние предложения
*/
const getProposalState = async (contractAddress, proposalId) => {
try {
if (!contract.value) {
initContract(contractAddress);
}
const state = await contract.value.getProposalState(proposalId);
// 0=Pending, 1=Succeeded, 2=Defeated, 3=Executed, 4=Canceled, 5=ReadyForExecution
const stateNames = {
0: 'Pending',
1: 'Succeeded',
2: 'Defeated',
3: 'Executed',
4: 'Canceled',
5: 'ReadyForExecution'
};
return {
success: true,
state: state,
stateName: stateNames[state] || 'Unknown'
};
} catch (error) {
console.error('❌ Ошибка получения состояния предложения:', error);
return {
success: false,
error: error.message,
state: null
};
}
};
/**
* Проверить результат предложения
*/
const checkProposalResult = async (contractAddress, proposalId) => {
try {
if (!contract.value) {
initContract(contractAddress);
}
const result = await contract.value.checkProposalResult(proposalId);
return {
success: true,
passed: result.passed,
quorumReached: result.quorumReached
};
} catch (error) {
console.error('❌ Ошибка проверки результата предложения:', error);
return {
success: false,
error: error.message,
passed: false,
quorumReached: false
};
}
};
/**
* Получить информацию о DLE
*/
const getDleInfo = async (contractAddress) => {
try {
if (!contract.value) {
initContract(contractAddress);
}
const info = await contract.value.getDLEInfo();
return {
success: true,
data: {
name: info.name,
symbol: info.symbol,
location: info.location,
coordinates: info.coordinates,
jurisdiction: info.jurisdiction,
okvedCodes: info.okvedCodes,
kpp: info.kpp,
creationTimestamp: info.creationTimestamp,
isActive: info.isActive
}
};
} catch (error) {
console.error('❌ Ошибка получения информации о DLE:', error);
return {
success: false,
error: error.message
};
}
};
// Вычисляемые свойства
const isWalletConnected = computed(() => isConnected.value);
const currentUserAddress = computed(() => userAddress.value);
const currentChainId = computed(() => chainId.value);
return {
// Состояние
isConnected,
provider,
signer,
contract,
userAddress,
chainId,
// Вычисляемые свойства
isWalletConnected,
currentUserAddress,
currentChainId,
// Методы
connectWallet,
initContract,
checkTokenBalance,
voteOnProposal,
executeProposal,
cancelProposal,
getProposalState,
checkProposalResult,
getDleInfo
};
}

View File

@@ -0,0 +1,207 @@
/**
* Composable для валидации предложений DLE
* Проверяет реальность предложений по хешам транзакций
*/
import { ref, computed } from 'vue';
export function useProposalValidation() {
const validatedProposals = ref([]);
const validationErrors = ref([]);
const isValidating = ref(false);
// Проверка формата хеша транзакции
const isValidTransactionHash = (hash) => {
if (!hash) return false;
return /^0x[a-fA-F0-9]{64}$/.test(hash);
};
// Проверка формата адреса
const isValidAddress = (address) => {
if (!address) return false;
return /^0x[a-fA-F0-9]{40}$/.test(address);
};
// Проверка chainId
const isValidChainId = (chainId) => {
const validChainIds = [1, 11155111, 17000, 421614, 84532, 8453]; // Mainnet, Sepolia, Holesky, Arbitrum Sepolia, Base Sepolia, Base
return validChainIds.includes(Number(chainId));
};
// Валидация предложения
const validateProposal = (proposal) => {
const errors = [];
// Проверка обязательных полей
if (!proposal.id && proposal.id !== 0) {
errors.push('Отсутствует ID предложения');
}
if (!proposal.description || proposal.description.trim() === '') {
errors.push('Отсутствует описание предложения');
}
if (!proposal.transactionHash) {
errors.push('Отсутствует хеш транзакции');
} else if (!isValidTransactionHash(proposal.transactionHash)) {
errors.push('Неверный формат хеша транзакции');
}
if (!proposal.initiator) {
errors.push('Отсутствует инициатор предложения');
} else if (!isValidAddress(proposal.initiator)) {
errors.push('Неверный формат адреса инициатора');
}
if (!proposal.chainId) {
errors.push('Отсутствует chainId');
} else if (!isValidChainId(proposal.chainId)) {
errors.push('Неподдерживаемый chainId');
}
if (proposal.state === undefined || proposal.state === null) {
errors.push('Отсутствует статус предложения');
}
// Проверка числовых значений
if (typeof proposal.forVotes !== 'number' || proposal.forVotes < 0) {
errors.push('Неверное значение голосов "за"');
}
if (typeof proposal.againstVotes !== 'number' || proposal.againstVotes < 0) {
errors.push('Неверное значение голосов "против"');
}
if (typeof proposal.quorumRequired !== 'number' || proposal.quorumRequired < 0) {
errors.push('Неверное значение требуемого кворума');
}
return {
isValid: errors.length === 0,
errors
};
};
// Валидация массива предложений
const validateProposals = (proposals) => {
isValidating.value = true;
validationErrors.value = [];
validatedProposals.value = [];
const validProposals = [];
const allErrors = [];
proposals.forEach((proposal, index) => {
const validation = validateProposal(proposal);
if (validation.isValid) {
validProposals.push(proposal);
} else {
allErrors.push({
proposalIndex: index,
proposalId: proposal.id,
errors: validation.errors
});
}
});
validatedProposals.value = validProposals;
validationErrors.value = allErrors;
isValidating.value = false;
console.log(`[Proposal Validation] Проверено предложений: ${proposals.length}`);
console.log(`[Proposal Validation] Валидных: ${validProposals.length}`);
console.log(`[Proposal Validation] С ошибками: ${allErrors.length}`);
return {
validProposals,
errors: allErrors,
totalCount: proposals.length,
validCount: validProposals.length,
errorCount: allErrors.length
};
};
// Получение статистики валидации
const validationStats = computed(() => {
const total = validatedProposals.value.length + validationErrors.value.length;
const valid = validatedProposals.value.length;
const invalid = validationErrors.value.length;
return {
total,
valid,
invalid,
validPercentage: total > 0 ? Math.round((valid / total) * 100) : 0,
invalidPercentage: total > 0 ? Math.round((invalid / total) * 100) : 0
};
});
// Проверка, является ли предложение реальным (на основе хеша транзакции)
const isRealProposal = (proposal) => {
if (!proposal.transactionHash) return false;
// Проверяем, что хеш имеет правильный формат
if (!isValidTransactionHash(proposal.transactionHash)) return false;
// Проверяем, что это не тестовые/фейковые хеши
const fakeHashes = [
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
];
if (fakeHashes.includes(proposal.transactionHash.toLowerCase())) return false;
// Проверяем, что хеш не начинается с нулей (подозрительно)
if (proposal.transactionHash.startsWith('0x0000')) return false;
return true;
};
// Фильтрация только реальных предложений
const filterRealProposals = (proposals) => {
return proposals.filter(proposal => isRealProposal(proposal));
};
// Фильтрация активных предложений (исключает выполненные и отмененные)
const filterActiveProposals = (proposals) => {
return proposals.filter(proposal => {
// Исключаем выполненные и отмененные предложения
if (proposal.executed || proposal.canceled) {
console.log(`🚫 [FILTER] Исключаем предложение ${proposal.id}: executed=${proposal.executed}, canceled=${proposal.canceled}`);
return false;
}
// Исключаем предложения с истекшим deadline
if (proposal.deadline) {
const currentTime = Math.floor(Date.now() / 1000);
if (currentTime > proposal.deadline) {
console.log(`⏰ [FILTER] Исключаем предложение ${proposal.id}: deadline истек`);
return false;
}
}
return true;
});
};
return {
// Данные
validatedProposals,
validationErrors,
isValidating,
validationStats,
// Методы
validateProposal,
validateProposals,
isRealProposal,
filterRealProposals,
filterActiveProposals,
// Вспомогательные функции
isValidTransactionHash,
isValidAddress,
isValidChainId
};
}

View File

@@ -0,0 +1,534 @@
import { ref, computed } from 'vue';
import { getProposals } from '@/services/proposalsService';
import { ethers } from 'ethers';
import { useProposalValidation } from './useProposalValidation';
import { voteForProposal, executeProposal as executeProposalUtil, cancelProposal as cancelProposalUtil, checkTokenBalance } from '@/utils/dle-contract';
// Функция checkVoteStatus удалена - в контракте DLE нет публичной функции hasVoted
// Функция checkTokenBalance перенесена в useDleContract.js
// Функция sendTransactionToWallet удалена - теперь используется прямое взаимодействие с контрактом
export function useProposals(dleAddress, isAuthenticated, userAddress) {
const proposals = ref([]);
const filteredProposals = ref([]);
const isLoading = ref(false);
const isVoting = ref(false);
const isExecuting = ref(false);
const isCancelling = ref(false);
const statusFilter = ref('');
const searchQuery = ref('');
// Используем готовые функции из utils/dle-contract.js
// Инициализируем валидацию
const {
validateProposals,
filterRealProposals,
filterActiveProposals,
validationStats,
isValidating
} = useProposalValidation();
const loadProposals = async () => {
if (!dleAddress.value) {
console.warn('Адрес DLE не найден');
return;
}
try {
isLoading.value = true;
const response = await getProposals(dleAddress.value);
if (response.success) {
const rawProposals = response.data.proposals || [];
console.log(`[Proposals] Загружено предложений: ${rawProposals.length}`);
console.log(`[Proposals] Полные данные из блокчейна:`, rawProposals);
// Детальная информация о каждом предложении
rawProposals.forEach((proposal, index) => {
console.log(`[Proposals] Предложение ${index}:`, {
id: proposal.id,
description: proposal.description,
state: proposal.state,
forVotes: proposal.forVotes,
againstVotes: proposal.againstVotes,
quorumRequired: proposal.quorumRequired,
quorumReached: proposal.quorumReached,
executed: proposal.executed,
canceled: proposal.canceled,
initiator: proposal.initiator,
chainId: proposal.chainId,
transactionHash: proposal.transactionHash
});
});
// Применяем валидацию предложений
const validationResult = validateProposals(rawProposals);
// Фильтруем только реальные предложения
const realProposals = filterRealProposals(validationResult.validProposals);
// Фильтруем только активные предложения (исключаем выполненные и отмененные)
const activeProposals = filterActiveProposals(realProposals);
console.log(`[Proposals] Валидных предложений: ${validationResult.validCount}`);
console.log(`[Proposals] Реальных предложений: ${realProposals.length}`);
console.log(`[Proposals] Активных предложений: ${activeProposals.length}`);
if (validationResult.errorCount > 0) {
console.warn(`[Proposals] Найдено ${validationResult.errorCount} предложений с ошибками валидации`);
}
proposals.value = activeProposals;
filterProposals();
}
} catch (error) {
console.error('Ошибка загрузки предложений:', error);
} finally {
isLoading.value = false;
}
};
const filterProposals = () => {
if (!proposals.value || proposals.value.length === 0) {
filteredProposals.value = [];
return;
}
let filtered = [...proposals.value];
if (statusFilter.value) {
filtered = filtered.filter(proposal => {
switch (statusFilter.value) {
case 'active': return proposal.state === 0; // Pending
case 'succeeded': return proposal.state === 1; // Succeeded
case 'defeated': return proposal.state === 2; // Defeated
case 'executed': return proposal.state === 3; // Executed
case 'cancelled': return proposal.state === 4; // Canceled
case 'ready': return proposal.state === 5; // ReadyForExecution
default: return true;
}
});
}
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase();
filtered = filtered.filter(proposal =>
proposal.description.toLowerCase().includes(query) ||
proposal.initiator.toLowerCase().includes(query) ||
proposal.uniqueId.toLowerCase().includes(query)
);
}
filteredProposals.value = filtered;
};
const voteOnProposal = async (proposalId, support) => {
try {
console.log('🚀 [VOTE] Начинаем голосование через DLE контракт:', { proposalId, support, dleAddress: dleAddress.value, userAddress: userAddress.value });
isVoting.value = true;
// Проверяем наличие MetaMask
if (!window.ethereum) {
throw new Error('MetaMask не найден. Пожалуйста, установите MetaMask.');
}
// Проверяем состояние предложения
console.log('🔍 [DEBUG] Проверяем состояние предложения...');
const proposal = proposals.value.find(p => p.id === proposalId);
if (!proposal) {
throw new Error('Предложение не найдено');
}
console.log('📊 [DEBUG] Данные предложения:', {
id: proposal.id,
state: proposal.state,
deadline: proposal.deadline,
forVotes: proposal.forVotes,
againstVotes: proposal.againstVotes,
executed: proposal.executed,
canceled: proposal.canceled
});
// Проверяем, что предложение активно (Pending)
if (proposal.state !== 0) {
const statusText = getProposalStatusText(proposal.state);
throw new Error(`Предложение не активно (статус: ${statusText}). Голосование возможно только для активных предложений.`);
}
// Проверяем, что предложение не выполнено и не отменено
if (proposal.executed) {
throw new Error('Предложение уже выполнено. Голосование невозможно.');
}
if (proposal.canceled) {
throw new Error('Предложение отменено. Голосование невозможно.');
}
// Проверяем deadline
const currentTime = Math.floor(Date.now() / 1000);
if (proposal.deadline && currentTime > proposal.deadline) {
throw new Error('Время голосования истекло. Голосование невозможно.');
}
// Проверяем баланс токенов пользователя
console.log('💰 [DEBUG] Проверяем баланс токенов...');
try {
const balanceCheck = await checkTokenBalance(dleAddress.value, userAddress.value);
console.log('💰 [DEBUG] Баланс токенов:', balanceCheck);
if (!balanceCheck.hasTokens) {
throw new Error('У вас нет токенов для голосования. Необходимо иметь токены DLE для участия в голосовании.');
}
} catch (balanceError) {
console.warn('⚠️ [DEBUG] Ошибка проверки баланса (продолжаем):', balanceError.message);
// Не останавливаем голосование, если не удалось проверить баланс
}
// Проверяем сеть кошелька
console.log('🌐 [DEBUG] Проверяем сеть кошелька...');
try {
const chainId = await window.ethereum.request({ method: 'eth_chainId' });
console.log('🌐 [DEBUG] Текущая сеть:', chainId);
console.log('🌐 [DEBUG] Сеть предложения:', proposal.chainId);
if (chainId !== proposal.chainId) {
throw new Error(`Неправильная сеть! Текущая сеть: ${chainId}, требуется: ${proposal.chainId}`);
}
} catch (networkError) {
console.warn('⚠️ [DEBUG] Ошибка проверки сети (продолжаем):', networkError.message);
}
// Голосуем через готовую функцию из utils/dle-contract.js
console.log('🗳️ Отправляем голосование через смарт-контракт...');
const result = await voteForProposal(dleAddress.value, proposalId, support);
console.log('✅ Голосование успешно отправлено:', result.txHash);
alert(`Голосование успешно отправлено! Хеш транзакции: ${result.txHash}`);
// Принудительно обновляем данные предложения
console.log('🔄 [VOTE] Обновляем данные после голосования...');
await loadProposals();
// Дополнительная задержка для подтверждения в блокчейне
setTimeout(async () => {
console.log('🔄 [VOTE] Повторное обновление через 3 секунды...');
await loadProposals();
}, 3000);
} catch (error) {
console.error('❌ Ошибка голосования:', error);
// Улучшенная обработка ошибок
let errorMessage = error.message;
if (error.message.includes('execution reverted')) {
if (error.data === '0xe7005635') {
errorMessage = 'Голосование отклонено смарт-контрактом. Возможные причины:\n' +
'• Вы уже голосовали за это предложение\n' +
'• У вас нет токенов для голосования\n' +
'• Предложение не активно\n' +
'• Время голосования истекло';
} else if (error.data === '0xc7567e07') {
errorMessage = 'Голосование отклонено смарт-контрактом. Возможные причины:\n' +
'• Вы уже голосовали за это предложение\n' +
'• У вас нет токенов для голосования\n' +
'• Предложение не активно\n' +
'• Время голосования истекло\n' +
'• Неправильная сеть для голосования';
} else {
errorMessage = `Транзакция отклонена смарт-контрактом (код: ${error.data}). Проверьте условия голосования.`;
}
} else if (error.message.includes('user rejected')) {
errorMessage = 'Транзакция отклонена пользователем';
} else if (error.message.includes('insufficient funds')) {
errorMessage = 'Недостаточно средств для оплаты газа';
}
alert('Ошибка при голосовании: ' + errorMessage);
} finally {
isVoting.value = false;
}
};
const executeProposal = async (proposalId) => {
try {
console.log('⚡ [EXECUTE] Исполняем предложение через DLE контракт:', { proposalId, dleAddress: dleAddress.value });
isExecuting.value = true;
// Проверяем состояние предложения перед выполнением
console.log('🔍 [DEBUG] Проверяем состояние предложения для выполнения...');
const proposal = proposals.value.find(p => p.id === proposalId);
if (!proposal) {
throw new Error('Предложение не найдено');
}
console.log('📊 [DEBUG] Данные предложения для выполнения:', {
id: proposal.id,
state: proposal.state,
executed: proposal.executed,
canceled: proposal.canceled,
quorumReached: proposal.quorumReached
});
// Проверяем, что предложение можно выполнить
if (proposal.executed) {
throw new Error('Предложение уже выполнено. Повторное выполнение невозможно.');
}
if (proposal.canceled) {
throw new Error('Предложение отменено. Выполнение невозможно.');
}
// Проверяем, что предложение готово к выполнению
if (proposal.state !== 5) {
const statusText = getProposalStatusText(proposal.state);
throw new Error(`Предложение не готово к выполнению (статус: ${statusText}). Выполнение возможно только для предложений в статусе "Готово к выполнению".`);
}
// Исполняем предложение через готовую функцию из utils/dle-contract.js
const result = await executeProposalUtil(dleAddress.value, proposalId);
console.log('✅ Предложение успешно исполнено:', result.txHash);
alert(`Предложение успешно исполнено! Хеш транзакции: ${result.txHash}`);
// Принудительно обновляем состояние предложения в UI
updateProposalState(proposalId, {
executed: true,
state: 1, // Выполнено
canceled: false
});
await loadProposals(); // Перезагружаем данные
} catch (error) {
console.error('❌ Ошибка выполнения предложения:', error);
// Улучшенная обработка ошибок
let errorMessage = error.message;
if (error.message.includes('execution reverted')) {
errorMessage = 'Выполнение отклонено смарт-контрактом. Возможные причины:\n' +
'• Предложение уже выполнено\n' +
'• Предложение отменено\n' +
'• Кворум не достигнут\n' +
'• Предложение не активно';
} else if (error.message.includes('user rejected')) {
errorMessage = 'Транзакция отклонена пользователем';
} else if (error.message.includes('insufficient funds')) {
errorMessage = 'Недостаточно средств для оплаты газа';
}
alert('Ошибка при исполнении предложения: ' + errorMessage);
} finally {
isExecuting.value = false;
}
};
const cancelProposal = async (proposalId, reason = 'Отменено пользователем') => {
try {
console.log('❌ [CANCEL] Отменяем предложение через DLE контракт:', { proposalId, reason, dleAddress: dleAddress.value });
isCancelling.value = true;
// Проверяем состояние предложения перед отменой
console.log('🔍 [DEBUG] Проверяем состояние предложения для отмены...');
const proposal = proposals.value.find(p => p.id === proposalId);
if (!proposal) {
throw new Error('Предложение не найдено');
}
console.log('📊 [DEBUG] Данные предложения для отмены:', {
id: proposal.id,
state: proposal.state,
executed: proposal.executed,
canceled: proposal.canceled,
deadline: proposal.deadline
});
// Проверяем, что предложение можно отменить
if (proposal.executed) {
throw new Error('Предложение уже выполнено. Отмена невозможна.');
}
if (proposal.canceled) {
throw new Error('Предложение уже отменено. Повторная отмена невозможна.');
}
// Проверяем, что предложение активно (Pending)
if (proposal.state !== 0) {
const statusText = getProposalStatusText(proposal.state);
throw new Error(`Предложение не активно (статус: ${statusText}). Отмена возможна только для активных предложений.`);
}
// Проверяем, что пользователь является инициатором
if (proposal.initiator !== userAddress.value) {
throw new Error('Только инициатор предложения может его отменить.');
}
// Проверяем deadline (нужен запас 15 минут)
const currentTime = Math.floor(Date.now() / 1000);
if (proposal.deadline) {
const timeRemaining = proposal.deadline - currentTime;
if (timeRemaining <= 900) { // 15 минут запас
throw new Error('Время для отмены истекло. Отмена возможна только за 15 минут до окончания голосования.');
}
}
// Отменяем предложение через готовую функцию из utils/dle-contract.js
const result = await cancelProposalUtil(dleAddress.value, proposalId, reason);
console.log('✅ Предложение успешно отменено:', result.txHash);
alert(`Предложение успешно отменено! Хеш транзакции: ${result.txHash}`);
// Принудительно обновляем состояние предложения в UI
updateProposalState(proposalId, {
canceled: true,
state: 2, // Отменено
executed: false
});
await loadProposals(); // Перезагружаем данные
} catch (error) {
console.error('❌ Ошибка отмены предложения:', error);
// Улучшенная обработка ошибок
let errorMessage = error.message;
if (error.message.includes('execution reverted')) {
errorMessage = 'Отмена отклонена смарт-контрактом. Возможные причины:\n' +
'• Предложение уже отменено\n' +
'• Предложение уже выполнено\n' +
'• Предложение не активно\n' +
'• Недостаточно прав для отмены';
} else if (error.message.includes('user rejected')) {
errorMessage = 'Транзакция отклонена пользователем';
} else if (error.message.includes('insufficient funds')) {
errorMessage = 'Недостаточно средств для оплаты газа';
}
alert('Ошибка при отмене предложения: ' + errorMessage);
} finally {
isCancelling.value = false;
}
};
const getProposalStatusClass = (state) => {
switch (state) {
case 0: return 'status-active'; // Pending
case 1: return 'status-succeeded'; // Succeeded
case 2: return 'status-defeated'; // Defeated
case 3: return 'status-executed'; // Executed
case 4: return 'status-cancelled'; // Canceled
case 5: return 'status-ready'; // ReadyForExecution
default: return 'status-active';
}
};
const getProposalStatusText = (state) => {
switch (state) {
case 0: return 'Активное';
case 1: return 'Успешное';
case 2: return 'Отклоненное';
case 3: return 'Выполнено';
case 4: return 'Отменено';
case 5: return 'Готово к выполнению';
default: return 'Неизвестно';
}
};
const getQuorumPercentage = (proposal) => {
// Получаем реальные данные из предложения
const forVotes = Number(proposal.forVotes || 0);
const againstVotes = Number(proposal.againstVotes || 0);
const totalVotes = forVotes + againstVotes;
// Используем реальный totalSupply из предложения или fallback
const totalSupply = Number(proposal.totalSupply || 3e+24); // Fallback к 3M DLE
console.log(`📊 [QUORUM] Предложение ${proposal.id}:`, {
forVotes: forVotes,
againstVotes: againstVotes,
totalVotes: totalVotes,
totalSupply: totalSupply,
forVotesFormatted: `${(forVotes / 1e+18).toFixed(2)} DLE`,
againstVotesFormatted: `${(againstVotes / 1e+18).toFixed(2)} DLE`,
totalVotesFormatted: `${(totalVotes / 1e+18).toFixed(2)} DLE`,
totalSupplyFormatted: `${(totalSupply / 1e+18).toFixed(2)} DLE`
});
const percentage = totalSupply > 0 ? (totalVotes / totalSupply) * 100 : 0;
return percentage.toFixed(2);
};
const getRequiredQuorumPercentage = (proposal) => {
// Получаем требуемый кворум из предложения
const requiredQuorum = Number(proposal.quorumRequired || 0);
// Используем реальный totalSupply из предложения или fallback
const totalSupply = Number(proposal.totalSupply || 3e+24); // Fallback к 3M DLE
console.log(`📊 [REQUIRED QUORUM] Предложение ${proposal.id}:`, {
requiredQuorum: requiredQuorum,
totalSupply: totalSupply,
requiredQuorumFormatted: `${(requiredQuorum / 1e+18).toFixed(2)} DLE`,
totalSupplyFormatted: `${(totalSupply / 1e+18).toFixed(2)} DLE`
});
const percentage = totalSupply > 0 ? (requiredQuorum / totalSupply) * 100 : 0;
return percentage.toFixed(2);
};
const canVote = (proposal) => {
return proposal.state === 0; // Pending - только активные предложения
};
const canExecute = (proposal) => {
return proposal.state === 5; // ReadyForExecution - готово к выполнению
};
const canCancel = (proposal) => {
// Можно отменить только активные предложения (Pending)
return proposal.state === 0 &&
!proposal.executed &&
!proposal.canceled;
};
// Принудительное обновление состояния предложения в UI
const updateProposalState = (proposalId, updates) => {
const proposal = proposals.value.find(p => p.id === proposalId);
if (proposal) {
Object.assign(proposal, updates);
console.log(`🔄 [UI] Обновлено состояние предложения ${proposalId}:`, updates);
// Принудительно обновляем фильтрацию
filterProposals();
}
};
return {
proposals,
filteredProposals,
isLoading,
isVoting,
isExecuting,
isCancelling,
statusFilter,
searchQuery,
loadProposals,
filterProposals,
voteOnProposal,
executeProposal,
cancelProposal,
getProposalStatusClass,
getProposalStatusText,
getQuorumPercentage,
getRequiredQuorumPercentage,
canVote,
canExecute,
canCancel,
updateProposalState,
// Валидация
validationStats,
isValidating
};
}