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

This commit is contained in:
2025-09-25 14:59:05 +03:00
parent 7b2f6937c8
commit ca718e3178
29 changed files with 4086 additions and 6110 deletions

View File

@@ -0,0 +1,471 @@
// SPDX-License-Identifier: PROPRIETARY AND MIT
// 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
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Address.sol";
/**
* @title HierarchicalVotingModule
* @dev Модуль для иерархического голосования между DLE
*
* ОСНОВНЫЕ ФУНКЦИИ:
* - Владение токенами других DLE
* - Создание предложений для голосования в других DLE
* - Внутреннее голосование для внешнего голосования
* - Выполнение внешнего голосования после достижения кворума
*
* БЕЗОПАСНОСТЬ:
* - Только DLE контракт может выполнять операции
* - Защита от реентерабельности
* - Валидация всех входных параметров
* - Проверка прав через governance
*/
contract HierarchicalVotingModule is ReentrancyGuard {
using SafeERC20 for IERC20;
using Address for address;
// Структура для внешнего голосования
struct ExternalVotingProposal {
address targetDLE; // Адрес целевого DLE
uint256 targetProposalId; // ID предложения в целевом DLE
bool support; // Поддержка предложения
string reason; // Причина голосования
bool executed; // Выполнено ли внешнее голосование
uint256 internalProposalId; // ID внутреннего предложения в DLE
uint256 votingPower; // Сила голоса (количество токенов)
uint256 createdAt; // Время создания
}
// Структура для информации о внешнем DLE
struct ExternalDLEInfo {
address dleAddress; // Адрес DLE
string name; // Название DLE
string symbol; // Символ токена DLE
uint256 tokenBalance; // Количество токенов на балансе
bool isActive; // Активен ли DLE
uint256 addedAt; // Время добавления
}
// Основные переменные
address public immutable dleContract; // Адрес основного DLE контракта
address public treasuryModule; // Адрес TreasuryModule (может быть установлен позже)
// Хранение внешних DLE
mapping(address => ExternalDLEInfo) public externalDLEs;
address[] public externalDLEList;
mapping(address => uint256) public externalDLEIndex;
// Внешние предложения
mapping(uint256 => ExternalVotingProposal) public externalVotingProposals;
uint256 public externalProposalCounter;
// Статистика
uint256 public totalExternalDLEs;
uint256 public totalExternalProposals;
uint256 public totalExternalVotes;
// События
event TreasuryModuleSet(address indexed treasuryModule, uint256 timestamp);
event ExternalDLEAdded(
address indexed dleAddress,
string name,
string symbol,
uint256 tokenBalance,
uint256 timestamp
);
event ExternalDLERemoved(address indexed dleAddress, uint256 timestamp);
event ExternalVotingProposalCreated(
uint256 indexed proposalId,
address indexed targetDLE,
uint256 targetProposalId,
bool support,
string reason,
uint256 internalProposalId
);
event ExternalVoteExecuted(
uint256 indexed proposalId,
address indexed targetDLE,
uint256 targetProposalId,
bool support,
uint256 votingPower
);
event ExternalDLEBalanceUpdated(
address indexed dleAddress,
uint256 oldBalance,
uint256 newBalance
);
// Модификаторы
modifier onlyDLE() {
require(msg.sender == dleContract, "Only DLE contract can call this");
_;
}
modifier validExternalDLE(address dleAddress) {
require(externalDLEs[dleAddress].isActive, "External DLE not active");
_;
}
constructor(address _dleContract) {
require(_dleContract != address(0), "DLE contract cannot be zero");
dleContract = _dleContract;
treasuryModule = address(0); // Будет установлен позже через governance
}
/**
* @dev Установить адрес TreasuryModule (только через DLE governance)
* @param _treasuryModule Адрес TreasuryModule
*/
function setTreasuryModule(address _treasuryModule) external onlyDLE {
require(_treasuryModule != address(0), "Treasury module cannot be zero");
require(_treasuryModule.code.length > 0, "Treasury module contract does not exist");
treasuryModule = _treasuryModule;
emit TreasuryModuleSet(_treasuryModule, block.timestamp);
}
/**
* @dev Добавить внешний DLE (только через DLE governance)
* @param dleAddress Адрес внешнего DLE
* @param name Название DLE
* @param symbol Символ токена DLE
*/
function addExternalDLE(
address dleAddress,
string memory name,
string memory symbol
) external onlyDLE {
require(dleAddress != address(0), "DLE address cannot be zero");
require(!externalDLEs[dleAddress].isActive, "External DLE already added");
require(bytes(name).length > 0, "Name cannot be empty");
require(bytes(symbol).length > 0, "Symbol cannot be empty");
require(treasuryModule != address(0), "Treasury module not set");
// Проверяем, что DLE контракт существует
require(dleAddress.code.length > 0, "DLE contract does not exist");
// Получаем баланс токенов этого DLE в TreasuryModule
uint256 tokenBalance = IERC20(dleAddress).balanceOf(treasuryModule);
externalDLEs[dleAddress] = ExternalDLEInfo({
dleAddress: dleAddress,
name: name,
symbol: symbol,
tokenBalance: tokenBalance,
isActive: true,
addedAt: block.timestamp
});
externalDLEList.push(dleAddress);
externalDLEIndex[dleAddress] = externalDLEList.length - 1;
totalExternalDLEs++;
emit ExternalDLEAdded(dleAddress, name, symbol, tokenBalance, block.timestamp);
}
/**
* @dev Удалить внешний DLE (только через DLE governance)
* @param dleAddress Адрес внешнего DLE
*/
function removeExternalDLE(address dleAddress) external onlyDLE validExternalDLE(dleAddress) {
require(externalDLEs[dleAddress].tokenBalance == 0, "Token balance must be zero");
// Удаляем из массива
uint256 index = externalDLEIndex[dleAddress];
uint256 lastIndex = externalDLEList.length - 1;
if (index != lastIndex) {
address lastDLE = externalDLEList[lastIndex];
externalDLEList[index] = lastDLE;
externalDLEIndex[lastDLE] = index;
}
externalDLEList.pop();
delete externalDLEIndex[dleAddress];
delete externalDLEs[dleAddress];
totalExternalDLEs--;
emit ExternalDLERemoved(dleAddress, block.timestamp);
}
/**
* @dev Создать предложение для внешнего голосования
* @param targetDLE Адрес целевого DLE
* @param targetProposalId ID предложения в целевом DLE
* @param support Поддержка предложения
* @param reason Причина голосования
* @return proposalId ID созданного предложения
*/
function createExternalVotingProposal(
address targetDLE,
uint256 targetProposalId,
bool support,
string memory reason
) external onlyDLE validExternalDLE(targetDLE) returns (uint256) {
require(targetProposalId > 0, "Target proposal ID must be positive");
require(bytes(reason).length > 0, "Reason cannot be empty");
ExternalDLEInfo memory dleInfo = externalDLEs[targetDLE];
require(dleInfo.tokenBalance > 0, "No tokens in target DLE");
// Создаем описание для внутреннего предложения
string memory description = string(abi.encodePacked(
"Vote in DLE ", dleInfo.name, " (", dleInfo.symbol, ") on proposal #",
_toString(targetProposalId), ": ", reason
));
// Создаем внутреннее предложение через DLE
// Это требует интеграции с DLE контрактом
uint256 internalProposalId = _createInternalProposal(description);
uint256 proposalId = externalProposalCounter++;
externalVotingProposals[proposalId] = ExternalVotingProposal({
targetDLE: targetDLE,
targetProposalId: targetProposalId,
support: support,
reason: reason,
executed: false,
internalProposalId: internalProposalId,
votingPower: dleInfo.tokenBalance,
createdAt: block.timestamp
});
totalExternalProposals++;
emit ExternalVotingProposalCreated(
proposalId,
targetDLE,
targetProposalId,
support,
reason,
internalProposalId
);
return proposalId;
}
/**
* @dev Выполнить внешнее голосование (после прохождения внутреннего предложения)
* @param proposalId ID внешнего предложения
*/
function executeExternalVote(uint256 proposalId) external onlyDLE nonReentrant {
ExternalVotingProposal storage proposal = externalVotingProposals[proposalId];
require(proposal.targetDLE != address(0), "Proposal not found");
require(!proposal.executed, "External vote already executed");
// Проверяем, что внутреннее предложение прошло
require(_isInternalProposalPassed(proposal.internalProposalId), "Internal proposal not passed");
// Выполняем голосование в целевом DLE
_executeVoteInTargetDLE(proposal.targetDLE, proposal.targetProposalId, proposal.support);
proposal.executed = true;
totalExternalVotes++;
emit ExternalVoteExecuted(
proposalId,
proposal.targetDLE,
proposal.targetProposalId,
proposal.support,
proposal.votingPower
);
}
/**
* @dev Обновить баланс токенов внешнего DLE
* @param dleAddress Адрес внешнего DLE
*/
function updateExternalDLEBalance(address dleAddress) external onlyDLE validExternalDLE(dleAddress) {
uint256 oldBalance = externalDLEs[dleAddress].tokenBalance;
uint256 newBalance = IERC20(dleAddress).balanceOf(treasuryModule);
externalDLEs[dleAddress].tokenBalance = newBalance;
emit ExternalDLEBalanceUpdated(dleAddress, oldBalance, newBalance);
}
/**
* @dev Обновить балансы всех внешних DLE
*/
function updateAllExternalDLEBalances() external onlyDLE {
for (uint256 i = 0; i < externalDLEList.length; i++) {
address dleAddress = externalDLEList[i];
if (externalDLEs[dleAddress].isActive) {
uint256 oldBalance = externalDLEs[dleAddress].tokenBalance;
uint256 newBalance = IERC20(dleAddress).balanceOf(treasuryModule);
externalDLEs[dleAddress].tokenBalance = newBalance;
emit ExternalDLEBalanceUpdated(dleAddress, oldBalance, newBalance);
}
}
}
// ===== VIEW ФУНКЦИИ =====
/**
* @dev Получить информацию о внешнем DLE
*/
function getExternalDLEInfo(address dleAddress) external view returns (ExternalDLEInfo memory) {
return externalDLEs[dleAddress];
}
/**
* @dev Получить список всех внешних DLE
*/
function getAllExternalDLEs() external view returns (address[] memory) {
return externalDLEList;
}
/**
* @dev Получить активные внешние DLE
*/
function getActiveExternalDLEs() external view returns (address[] memory) {
uint256 activeCount = 0;
for (uint256 i = 0; i < externalDLEList.length; i++) {
if (externalDLEs[externalDLEList[i]].isActive) {
activeCount++;
}
}
address[] memory activeDLEs = new address[](activeCount);
uint256 index = 0;
for (uint256 i = 0; i < externalDLEList.length; i++) {
if (externalDLEs[externalDLEList[i]].isActive) {
activeDLEs[index] = externalDLEList[i];
index++;
}
}
return activeDLEs;
}
/**
* @dev Получить информацию о внешнем предложении
*/
function getExternalVotingProposal(uint256 proposalId) external view returns (ExternalVotingProposal memory) {
return externalVotingProposals[proposalId];
}
/**
* @dev Получить статистику модуля
*/
function getModuleStats() external view returns (
uint256 totalDLEs,
uint256 totalProposals,
uint256 totalVotes,
uint256 activeDLEs
) {
uint256 activeCount = 0;
for (uint256 i = 0; i < externalDLEList.length; i++) {
if (externalDLEs[externalDLEList[i]].isActive) {
activeCount++;
}
}
return (
totalExternalDLEs,
totalExternalProposals,
totalExternalVotes,
activeCount
);
}
// ===== ВНУТРЕННИЕ ФУНКЦИИ =====
/**
* @dev Создать внутреннее предложение в DLE
* @param description Описание предложения
* @return proposalId ID созданного предложения
*/
function _createInternalProposal(string memory description) internal returns (uint256) {
// Создаем предложение через стандартный интерфейс DLE
(bool success, bytes memory data) = dleContract.call(
abi.encodeWithSignature(
"createProposal(string,uint256,bytes,uint256,uint256[],uint256)",
description,
7 days, // 7 дней голосования
"", // Пустая операция
block.chainid, // Текущая сеть
new uint256[](0), // Пустой массив целевых цепочек
0 // Без timelock
)
);
require(success, "Failed to create internal proposal");
return abi.decode(data, (uint256));
}
/**
* @dev Проверить, прошло ли внутреннее предложение
* @param proposalId ID внутреннего предложения
* @return passed Прошло ли предложение
*/
function _isInternalProposalPassed(uint256 proposalId) internal view returns (bool) {
(bool success, bytes memory data) = dleContract.staticcall(
abi.encodeWithSignature("checkProposalResult(uint256)", proposalId)
);
if (!success) return false;
(bool passed, bool quorumReached) = abi.decode(data, (bool, bool));
return passed && quorumReached;
}
/**
* @dev Выполнить голосование в целевом DLE
* @param targetDLE Адрес целевого DLE
* @param proposalId ID предложения
* @param support Поддержка предложения
*/
function _executeVoteInTargetDLE(
address targetDLE,
uint256 proposalId,
bool support
) internal {
// Выполняем голосование напрямую в целевом DLE
// Это требует, чтобы целевой DLE имел функцию vote
(bool success, ) = targetDLE.call(
abi.encodeWithSignature("vote(uint256,bool)", proposalId, support)
);
require(success, "Failed to execute vote in target DLE");
}
/**
* @dev Конвертировать uint256 в string
*/
function _toString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
}