Files
DLE/backend/utils/operationDecoder.js
2025-10-30 22:41:04 +03:00

282 lines
9.1 KiB
JavaScript

/**
* 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/VC-HB3-Accelerator
*/
const { ethers } = require('ethers');
/**
* Декодирует операцию из формата abi.encodeWithSelector
* @param {string} operation - Закодированная операция (hex string)
* @returns {Object} - Декодированная операция
*/
function decodeOperation(operation) {
try {
if (!operation || operation.length < 4) {
return {
type: 'unknown',
selector: null,
data: null,
decoded: null,
error: 'Invalid operation format'
};
}
// Извлекаем селектор (первые 4 байта)
const selector = operation.slice(0, 10); // 0x + 4 байта
const data = operation.slice(10); // Остальные данные
// Определяем тип операции по селектору
const operationType = getOperationType(selector);
if (operationType === 'unknown') {
return {
type: 'unknown',
selector: selector,
data: data,
decoded: null,
error: 'Unknown operation selector'
};
}
// Декодируем данные в зависимости от типа операции
let decoded = null;
try {
decoded = decodeOperationData(operationType, data);
} catch (decodeError) {
return {
type: operationType,
selector: selector,
data: data,
decoded: null,
error: `Failed to decode ${operationType}: ${decodeError.message}`
};
}
return {
type: operationType,
selector: selector,
data: data,
decoded: decoded,
error: null
};
} catch (error) {
return {
type: 'error',
selector: null,
data: null,
decoded: null,
error: error.message
};
}
}
/**
* Определяет тип операции по селектору
* @param {string} selector - Селектор функции (0x + 4 байта)
* @returns {string} - Тип операции
*/
function getOperationType(selector) {
const selectors = {
'0x12345678': '_addModule', // Пример селектора
'0x87654321': '_removeModule', // Пример селектора
'0xabcdef12': '_addSupportedChain', // Пример селектора
'0x21fedcba': '_removeSupportedChain', // Пример селектора
'0x1234abcd': '_transferTokens', // Пример селектора
'0xabcd1234': '_updateVotingDurations', // Пример селектора
'0x5678efgh': '_setLogoURI', // Пример селектора
'0xefgh5678': '_updateQuorumPercentage', // Пример селектора
'0x9abc1234': '_updateDLEInfo', // Пример селектора
'0x12349abc': 'offchainAction' // Пример селектора
};
// Вычисляем реальные селекторы
const realSelectors = {
[ethers.id('_addModule(bytes32,address)').slice(0, 10)]: '_addModule',
[ethers.id('_removeModule(bytes32)').slice(0, 10)]: '_removeModule',
[ethers.id('_addSupportedChain(uint256)').slice(0, 10)]: '_addSupportedChain',
[ethers.id('_removeSupportedChain(uint256)').slice(0, 10)]: '_removeSupportedChain',
[ethers.id('_transferTokens(address,uint256)').slice(0, 10)]: '_transferTokens',
[ethers.id('_updateVotingDurations(uint256,uint256)').slice(0, 10)]: '_updateVotingDurations',
[ethers.id('_setLogoURI(string)').slice(0, 10)]: '_setLogoURI',
[ethers.id('_updateQuorumPercentage(uint256)').slice(0, 10)]: '_updateQuorumPercentage',
[ethers.id('_updateDLEInfo(string,string,string,string,uint256,string[],uint256)').slice(0, 10)]: '_updateDLEInfo',
[ethers.id('offchainAction(bytes32,string,bytes32)').slice(0, 10)]: 'offchainAction'
};
return realSelectors[selector] || 'unknown';
}
/**
* Декодирует данные операции в зависимости от типа
* @param {string} operationType - Тип операции
* @param {string} data - Закодированные данные
* @returns {Object} - Декодированные данные
*/
function decodeOperationData(operationType, data) {
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
switch (operationType) {
case '_addModule':
const [moduleId, moduleAddress] = abiCoder.decode(['bytes32', 'address'], '0x' + data);
return {
moduleId: moduleId,
moduleAddress: moduleAddress
};
case '_removeModule':
const [moduleIdToRemove] = abiCoder.decode(['bytes32'], '0x' + data);
return {
moduleId: moduleIdToRemove
};
case '_addSupportedChain':
const [chainIdToAdd] = abiCoder.decode(['uint256'], '0x' + data);
return {
chainId: Number(chainIdToAdd)
};
case '_removeSupportedChain':
const [chainIdToRemove] = abiCoder.decode(['uint256'], '0x' + data);
return {
chainId: Number(chainIdToRemove)
};
case '_transferTokens':
const [recipient, amount] = abiCoder.decode(['address', 'uint256'], '0x' + data);
return {
recipient: recipient,
amount: amount.toString(),
amountFormatted: ethers.formatEther(amount)
};
case '_updateVotingDurations':
const [minDuration, maxDuration] = abiCoder.decode(['uint256', 'uint256'], '0x' + data);
return {
minDuration: Number(minDuration),
maxDuration: Number(maxDuration)
};
case '_setLogoURI':
const [logoURI] = abiCoder.decode(['string'], '0x' + data);
return {
logoURI: logoURI
};
case '_updateQuorumPercentage':
const [quorumPercentage] = abiCoder.decode(['uint256'], '0x' + data);
return {
quorumPercentage: Number(quorumPercentage)
};
case '_updateDLEInfo':
const [name, symbol, location, coordinates, jurisdiction, okvedCodes, kpp] = abiCoder.decode(
['string', 'string', 'string', 'string', 'uint256', 'string[]', 'uint256'],
'0x' + data
);
return {
name: name,
symbol: symbol,
location: location,
coordinates: coordinates,
jurisdiction: Number(jurisdiction),
okvedCodes: okvedCodes,
kpp: Number(kpp)
};
case 'offchainAction':
const [actionId, kind, payloadHash] = abiCoder.decode(['bytes32', 'string', 'bytes32'], '0x' + data);
return {
actionId: actionId,
kind: kind,
payloadHash: payloadHash
};
default:
throw new Error(`Unknown operation type: ${operationType}`);
}
}
/**
* Форматирует декодированную операцию для отображения
* @param {Object} decodedOperation - Декодированная операция
* @returns {string} - Отформатированное описание
*/
function formatOperation(decodedOperation) {
if (decodedOperation.error) {
return `Ошибка: ${decodedOperation.error}`;
}
const { type, decoded } = decodedOperation;
switch (type) {
case '_addModule':
return `Добавить модуль: ${decoded.moduleId} (${decoded.moduleAddress})`;
case '_removeModule':
return `Удалить модуль: ${decoded.moduleId}`;
case '_addSupportedChain':
return `Добавить поддерживаемую сеть: ${decoded.chainId}`;
case '_removeSupportedChain':
return `Удалить поддерживаемую сеть: ${decoded.chainId}`;
case '_transferTokens':
return `Перевести токены: ${decoded.amountFormatted} DLE на адрес ${decoded.recipient}`;
case '_updateVotingDurations':
return `Обновить длительность голосования: ${decoded.minDuration}-${decoded.maxDuration} секунд`;
case '_setLogoURI':
return `Обновить логотип: ${decoded.logoURI}`;
case '_updateQuorumPercentage':
return `Обновить процент кворума: ${decoded.quorumPercentage}%`;
case '_updateDLEInfo':
return `Обновить информацию DLE: ${decoded.name} (${decoded.symbol})`;
case 'offchainAction':
return `Оффчейн действие: ${decoded.kind} (${decoded.actionId})`;
default:
return `Неизвестная операция: ${type}`;
}
}
/**
* Получает название сети по ID
* @param {number} chainId - ID сети
* @returns {string} - Название сети
*/
function getChainName(chainId) {
const chainNames = {
1: 'Ethereum Mainnet',
11155111: 'Sepolia',
17000: 'Holesky',
421614: 'Arbitrum Sepolia',
84532: 'Base Sepolia',
137: 'Polygon',
80001: 'Polygon Mumbai',
56: 'BSC',
97: 'BSC Testnet'
};
return chainNames[chainId] || `Chain ${chainId}`;
}
module.exports = {
decodeOperation,
formatOperation,
getChainName
};