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

This commit is contained in:
2025-08-29 18:37:57 +03:00
parent 8e50c6c4d8
commit 4e4cb611a1
53 changed files with 4380 additions and 5902 deletions

View File

@@ -0,0 +1,527 @@
// 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 TreasuryModule
* @dev Модуль казны для управления активами DLE
*
* ОСНОВНЫЕ ФУНКЦИИ:
* - Управление различными ERC20 токенами
* - Хранение и перевод нативных монет (ETH, BNB, MATIC и т.д.)
* - Интеграция с DLE governance для авторизации операций
* - Поддержка мульти-чейн операций
* - Batch операции для оптимизации газа
*
* БЕЗОПАСНОСТЬ:
* - Только DLE контракт может выполнять операции
* - Защита от реентерабельности
* - Валидация всех входных параметров
* - Поддержка emergency pause
*/
contract TreasuryModule is ReentrancyGuard {
using SafeERC20 for IERC20;
using Address for address payable;
// Структура для информации о токене
struct TokenInfo {
address tokenAddress; // Адрес токена (0x0 для нативной монеты)
string symbol; // Символ токена
uint8 decimals; // Количество знаков после запятой
bool isActive; // Активен ли токен
bool isNative; // Является ли нативной монетой
uint256 addedTimestamp; // Время добавления
uint256 balance; // Кэшированный баланс (обновляется при операциях)
}
// Структура для batch операции
struct BatchTransfer {
address tokenAddress; // Адрес токена (0x0 для нативной монеты)
address recipient; // Получатель
uint256 amount; // Количество
}
// Основные переменные
address public immutable dleContract; // Адрес основного DLE контракта
uint256 public immutable chainId; // ID текущей сети
// Хранение токенов
mapping(address => TokenInfo) public supportedTokens; // tokenAddress => TokenInfo
address[] public tokenList; // Список всех добавленных токенов
mapping(address => uint256) public tokenIndex; // tokenAddress => index в tokenList
// Статистика
uint256 public totalTokensSupported;
uint256 public totalTransactions;
mapping(address => uint256) public tokenTransactionCount; // tokenAddress => count
// Система экстренного останова
bool public emergencyPaused;
address public emergencyAdmin;
// События
event TokenAdded(
address indexed tokenAddress,
string symbol,
uint8 decimals,
bool isNative,
uint256 timestamp
);
event TokenRemoved(address indexed tokenAddress, string symbol, uint256 timestamp);
event TokenStatusUpdated(address indexed tokenAddress, bool newStatus);
event FundsDeposited(
address indexed tokenAddress,
address indexed from,
uint256 amount,
uint256 newBalance
);
event FundsTransferred(
address indexed tokenAddress,
address indexed to,
uint256 amount,
uint256 remainingBalance,
bytes32 indexed proposalId
);
event BatchTransferExecuted(
uint256 transferCount,
uint256 totalAmount,
bytes32 indexed proposalId
);
event EmergencyPauseToggled(bool isPaused, address admin);
event BalanceUpdated(address indexed tokenAddress, uint256 oldBalance, uint256 newBalance);
// Модификаторы
modifier onlyDLE() {
require(msg.sender == dleContract, "Only DLE contract can call this");
_;
}
modifier whenNotPaused() {
require(!emergencyPaused, "Treasury is paused");
_;
}
modifier onlyEmergencyAdmin() {
require(msg.sender == emergencyAdmin, "Only emergency admin");
_;
}
modifier validToken(address tokenAddress) {
require(supportedTokens[tokenAddress].isActive, "Token not supported or inactive");
_;
}
constructor(address _dleContract, uint256 _chainId, address _emergencyAdmin) {
require(_dleContract != address(0), "DLE contract cannot be zero");
require(_emergencyAdmin != address(0), "Emergency admin cannot be zero");
require(_chainId > 0, "Chain ID must be positive");
dleContract = _dleContract;
chainId = _chainId;
emergencyAdmin = _emergencyAdmin;
// Автоматически добавляем нативную монету сети
_addNativeToken();
}
/**
* @dev Получить средства (может вызывать кто угодно для пополнения казны)
*/
receive() external payable {
if (msg.value > 0) {
_updateTokenBalance(address(0), supportedTokens[address(0)].balance + msg.value);
emit FundsDeposited(address(0), msg.sender, msg.value, supportedTokens[address(0)].balance);
}
}
/**
* @dev Добавить новый токен в казну (только через DLE governance)
* @param tokenAddress Адрес токена (0x0 для нативной монеты)
* @param symbol Символ токена
* @param decimals Количество знаков после запятой
*/
function addToken(
address tokenAddress,
string memory symbol,
uint8 decimals
) external onlyDLE whenNotPaused {
require(!supportedTokens[tokenAddress].isActive, "Token already supported");
require(bytes(symbol).length > 0, "Symbol cannot be empty");
require(bytes(symbol).length <= 20, "Symbol too long");
// Для ERC20 токенов проверяем, что контракт существует
if (tokenAddress != address(0)) {
require(tokenAddress.code.length > 0, "Token contract does not exist");
// Проверяем базовые ERC20 функции
try IERC20(tokenAddress).totalSupply() returns (uint256) {
// Token contract is valid
} catch {
revert("Invalid ERC20 token");
}
}
// Добавляем токен
supportedTokens[tokenAddress] = TokenInfo({
tokenAddress: tokenAddress,
symbol: symbol,
decimals: decimals,
isActive: true,
isNative: tokenAddress == address(0),
addedTimestamp: block.timestamp,
balance: 0
});
tokenList.push(tokenAddress);
tokenIndex[tokenAddress] = tokenList.length - 1;
totalTokensSupported++;
// Обновляем баланс
_refreshTokenBalance(tokenAddress);
emit TokenAdded(tokenAddress, symbol, decimals, tokenAddress == address(0), block.timestamp);
}
/**
* @dev Удалить токен из казны (только через DLE governance)
* @param tokenAddress Адрес токена для удаления
*/
function removeToken(address tokenAddress) external onlyDLE whenNotPaused validToken(tokenAddress) {
require(tokenAddress != address(0), "Cannot remove native token");
TokenInfo memory tokenInfo = supportedTokens[tokenAddress];
require(tokenInfo.balance == 0, "Token balance must be zero before removal");
// Удаляем из массива
uint256 index = tokenIndex[tokenAddress];
uint256 lastIndex = tokenList.length - 1;
if (index != lastIndex) {
address lastToken = tokenList[lastIndex];
tokenList[index] = lastToken;
tokenIndex[lastToken] = index;
}
tokenList.pop();
delete tokenIndex[tokenAddress];
delete supportedTokens[tokenAddress];
totalTokensSupported--;
emit TokenRemoved(tokenAddress, tokenInfo.symbol, block.timestamp);
}
/**
* @dev Изменить статус токена (активен/неактивен)
* @param tokenAddress Адрес токена
* @param isActive Новый статус
*/
function setTokenStatus(address tokenAddress, bool isActive) external onlyDLE {
require(supportedTokens[tokenAddress].tokenAddress == tokenAddress, "Token not found");
require(tokenAddress != address(0), "Cannot deactivate native token");
supportedTokens[tokenAddress].isActive = isActive;
emit TokenStatusUpdated(tokenAddress, isActive);
}
/**
* @dev Перевести токены (только через DLE governance)
* @param tokenAddress Адрес токена (0x0 для нативной монеты)
* @param recipient Получатель
* @param amount Количество для перевода
* @param proposalId ID предложения DLE (для логирования)
*/
function transferFunds(
address tokenAddress,
address recipient,
uint256 amount,
bytes32 proposalId
) external onlyDLE whenNotPaused validToken(tokenAddress) nonReentrant {
require(recipient != address(0), "Recipient cannot be zero");
require(amount > 0, "Amount must be positive");
TokenInfo storage tokenInfo = supportedTokens[tokenAddress];
require(tokenInfo.balance >= amount, "Insufficient balance");
// Обновляем баланс
_updateTokenBalance(tokenAddress, tokenInfo.balance - amount);
// Выполняем перевод
if (tokenInfo.isNative) {
payable(recipient).sendValue(amount);
} else {
IERC20(tokenAddress).safeTransfer(recipient, amount);
}
totalTransactions++;
tokenTransactionCount[tokenAddress]++;
emit FundsTransferred(
tokenAddress,
recipient,
amount,
tokenInfo.balance,
proposalId
);
}
/**
* @dev Выполнить batch перевод (только через DLE governance)
* @param transfers Массив переводов
* @param proposalId ID предложения DLE
*/
function batchTransfer(
BatchTransfer[] memory transfers,
bytes32 proposalId
) external onlyDLE whenNotPaused nonReentrant {
require(transfers.length > 0, "No transfers provided");
require(transfers.length <= 100, "Too many transfers"); // Защита от DoS
uint256 totalAmount = 0;
for (uint256 i = 0; i < transfers.length; i++) {
BatchTransfer memory transfer = transfers[i];
require(transfer.recipient != address(0), "Recipient cannot be zero");
require(transfer.amount > 0, "Amount must be positive");
require(supportedTokens[transfer.tokenAddress].isActive, "Token not supported");
TokenInfo storage tokenInfo = supportedTokens[transfer.tokenAddress];
require(tokenInfo.balance >= transfer.amount, "Insufficient balance");
// Обновляем баланс
_updateTokenBalance(transfer.tokenAddress, tokenInfo.balance - transfer.amount);
// Выполняем перевод
if (tokenInfo.isNative) {
payable(transfer.recipient).sendValue(transfer.amount);
} else {
IERC20(transfer.tokenAddress).safeTransfer(transfer.recipient, transfer.amount);
}
totalAmount += transfer.amount;
tokenTransactionCount[transfer.tokenAddress]++;
emit FundsTransferred(
transfer.tokenAddress,
transfer.recipient,
transfer.amount,
tokenInfo.balance,
proposalId
);
}
totalTransactions += transfers.length;
emit BatchTransferExecuted(transfers.length, totalAmount, proposalId);
}
/**
* @dev Пополнить казну ERC20 токенами
* @param tokenAddress Адрес токена
* @param amount Количество для пополнения
*/
function depositToken(
address tokenAddress,
uint256 amount
) external whenNotPaused validToken(tokenAddress) nonReentrant {
require(amount > 0, "Amount must be positive");
require(tokenAddress != address(0), "Use receive() for native deposits");
IERC20(tokenAddress).safeTransferFrom(msg.sender, address(this), amount);
_updateTokenBalance(tokenAddress, supportedTokens[tokenAddress].balance + amount);
emit FundsDeposited(tokenAddress, msg.sender, amount, supportedTokens[tokenAddress].balance);
}
/**
* @dev Обновить баланс токена (синхронизация с реальным балансом)
* @param tokenAddress Адрес токена
*/
function refreshBalance(address tokenAddress) external validToken(tokenAddress) {
_refreshTokenBalance(tokenAddress);
}
/**
* @dev Обновить балансы всех токенов
*/
function refreshAllBalances() external {
for (uint256 i = 0; i < tokenList.length; i++) {
if (supportedTokens[tokenList[i]].isActive) {
_refreshTokenBalance(tokenList[i]);
}
}
}
/**
* @dev Экстренная пауза (только emergency admin)
*/
function emergencyPause() external onlyEmergencyAdmin {
emergencyPaused = !emergencyPaused;
emit EmergencyPauseToggled(emergencyPaused, msg.sender);
}
// ===== VIEW ФУНКЦИИ =====
/**
* @dev Получить информацию о токене
*/
function getTokenInfo(address tokenAddress) external view returns (TokenInfo memory) {
return supportedTokens[tokenAddress];
}
/**
* @dev Получить список всех токенов
*/
function getAllTokens() external view returns (address[] memory) {
return tokenList;
}
/**
* @dev Получить активные токены
*/
function getActiveTokens() external view returns (address[] memory) {
uint256 activeCount = 0;
// Считаем активные токены
for (uint256 i = 0; i < tokenList.length; i++) {
if (supportedTokens[tokenList[i]].isActive) {
activeCount++;
}
}
// Создаём массив активных токенов
address[] memory activeTokens = new address[](activeCount);
uint256 index = 0;
for (uint256 i = 0; i < tokenList.length; i++) {
if (supportedTokens[tokenList[i]].isActive) {
activeTokens[index] = tokenList[i];
index++;
}
}
return activeTokens;
}
/**
* @dev Получить баланс токена
*/
function getTokenBalance(address tokenAddress) external view returns (uint256) {
return supportedTokens[tokenAddress].balance;
}
/**
* @dev Получить реальный баланс токена (обращение к блокчейну)
*/
function getRealTokenBalance(address tokenAddress) external view returns (uint256) {
if (tokenAddress == address(0)) {
return address(this).balance;
} else {
return IERC20(tokenAddress).balanceOf(address(this));
}
}
/**
* @dev Проверить, поддерживается ли токен
*/
function isTokenSupported(address tokenAddress) external view returns (bool) {
return supportedTokens[tokenAddress].isActive;
}
/**
* @dev Получить статистику казны
*/
function getTreasuryStats() external view returns (
uint256 totalTokens,
uint256 totalTxs,
uint256 currentChainId,
bool isPaused
) {
return (
totalTokensSupported,
totalTransactions,
chainId,
emergencyPaused
);
}
// ===== ВНУТРЕННИЕ ФУНКЦИИ =====
/**
* @dev Автоматически добавить нативную монету
*/
function _addNativeToken() internal {
string memory nativeSymbol;
// Определяем символ нативной монеты по chain ID
if (chainId == 1 || chainId == 11155111) { // Ethereum Mainnet / Sepolia
nativeSymbol = "ETH";
} else if (chainId == 56 || chainId == 97) { // BSC Mainnet / Testnet
nativeSymbol = "BNB";
} else if (chainId == 137 || chainId == 80001) { // Polygon Mainnet / Mumbai
nativeSymbol = "MATIC";
} else if (chainId == 42161) { // Arbitrum One
nativeSymbol = "ETH";
} else if (chainId == 10) { // Optimism
nativeSymbol = "ETH";
} else if (chainId == 43114) { // Avalanche
nativeSymbol = "AVAX";
} else {
nativeSymbol = "NATIVE"; // Для неизвестных сетей
}
supportedTokens[address(0)] = TokenInfo({
tokenAddress: address(0),
symbol: nativeSymbol,
decimals: 18,
isActive: true,
isNative: true,
addedTimestamp: block.timestamp,
balance: 0
});
tokenList.push(address(0));
tokenIndex[address(0)] = 0;
totalTokensSupported = 1;
emit TokenAdded(address(0), nativeSymbol, 18, true, block.timestamp);
}
/**
* @dev Обновить кэшированный баланс токена
*/
function _updateTokenBalance(address tokenAddress, uint256 newBalance) internal {
uint256 oldBalance = supportedTokens[tokenAddress].balance;
supportedTokens[tokenAddress].balance = newBalance;
emit BalanceUpdated(tokenAddress, oldBalance, newBalance);
}
/**
* @dev Синхронизировать кэшированный баланс с реальным
*/
function _refreshTokenBalance(address tokenAddress) internal {
uint256 realBalance;
if (tokenAddress == address(0)) {
realBalance = address(this).balance;
} else {
realBalance = IERC20(tokenAddress).balanceOf(address(this));
}
_updateTokenBalance(tokenAddress, realBalance);
}
}