// SPDX-License-Identifier: PROPRIETARY AND MIT // Copyright (c) 2024-2025 Тарабанов Александр Викторович // All rights reserved. pragma solidity ^0.8.20; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; /** * @title TimelockModule * @dev Модуль временной задержки для критических операций DLE * * НАЗНАЧЕНИЕ: * - Добавляет обязательную задержку между принятием и исполнением решений * - Даёт время сообществу на обнаружение и отмену вредоносных предложений * - Повышает безопасность критических операций (смена кворума, добавление модулей) * * ПРИНЦИП РАБОТЫ: * 1. DLE исполняет операцию не напрямую, а через Timelock * 2. Timelock ставит операцию в очередь с задержкой * 3. После истечения задержки любой может исполнить операцию * 4. В период задержки операцию можно отменить через экстренное голосование */ contract TimelockModule is ReentrancyGuard { // Структура отложенной операции struct QueuedOperation { bytes32 id; // Уникальный ID операции address target; // Целевой контракт (обычно DLE) bytes data; // Данные для вызова uint256 executeAfter; // Время, после которого можно исполнить uint256 queuedAt; // Время постановки в очередь bool executed; // Исполнена ли операция bool cancelled; // Отменена ли операция address proposer; // Кто поставил в очередь string description; // Описание операции uint256 delay; // Задержка для этой операции } // Основные настройки address public immutable dleContract; // Адрес DLE контракта uint256 public defaultDelay = 2 days; // Стандартная задержка uint256 public emergencyDelay = 30 minutes; // Экстренная задержка uint256 public maxDelay = 30 days; // Максимальная задержка uint256 public minDelay = 1 hours; // Минимальная задержка // Хранение операций mapping(bytes32 => QueuedOperation) public queuedOperations; bytes32[] public operationQueue; // Список всех операций mapping(bytes32 => uint256) public operationIndex; // ID => индекс в очереди // Категории операций с разными задержками mapping(bytes4 => uint256) public operationDelays; // selector => delay mapping(bytes4 => bool) public criticalOperations; // критические операции mapping(bytes4 => bool) public emergencyOperations; // экстренные операции // Статистика uint256 public totalOperations; uint256 public executedOperations; uint256 public cancelledOperations; // События event OperationQueued( bytes32 indexed operationId, address indexed target, bytes data, uint256 executeAfter, uint256 delay, string description ); event OperationExecuted(bytes32 indexed operationId, address indexed executor); event OperationCancelled(bytes32 indexed operationId, address indexed canceller, string reason); event DelayUpdated(bytes4 indexed selector, uint256 oldDelay, uint256 newDelay); event DefaultDelayUpdated(uint256 oldDelay, uint256 newDelay); event EmergencyExecution(bytes32 indexed operationId, string reason); // Модификаторы modifier onlyDLE() { require(msg.sender == dleContract, "Only DLE can call"); _; } modifier validOperation(bytes32 operationId) { require(queuedOperations[operationId].id == operationId, "Operation not found"); require(!queuedOperations[operationId].executed, "Already executed"); require(!queuedOperations[operationId].cancelled, "Operation cancelled"); _; } constructor(address _dleContract) { require(_dleContract != address(0), "DLE contract cannot be zero"); require(_dleContract.code.length > 0, "DLE contract must exist"); dleContract = _dleContract; totalOperations = 0; // Настраиваем задержки для разных типов операций _setupOperationDelays(); } /** * @dev Поставить операцию в очередь (вызывается из DLE) * @param target Целевой контракт * @param data Данные операции * @param description Описание операции */ function queueOperation( address target, bytes memory data, string memory description ) external onlyDLE returns (bytes32) { require(target != address(0), "Target cannot be zero"); require(data.length >= 4, "Invalid operation data"); // Определяем задержку для операции bytes4 selector; assembly { selector := mload(add(data, 0x20)) } uint256 delay = _getOperationDelay(selector); // Создаём уникальный ID операции bytes32 operationId = keccak256(abi.encodePacked( target, data, block.timestamp, totalOperations )); // Проверяем что операция ещё не существует require(queuedOperations[operationId].id == bytes32(0), "Operation already exists"); // Создаём операцию queuedOperations[operationId] = QueuedOperation({ id: operationId, target: target, data: data, executeAfter: block.timestamp + delay, queuedAt: block.timestamp, executed: false, cancelled: false, proposer: msg.sender, // Адрес вызывающего (обычно DLE контракт) description: description, delay: delay }); // Добавляем в очередь operationQueue.push(operationId); operationIndex[operationId] = operationQueue.length - 1; totalOperations++; emit OperationQueued(operationId, target, data, block.timestamp + delay, delay, description); return operationId; } /** * @dev Исполнить операцию после истечения задержки (может любой) * @param operationId ID операции */ function executeOperation(bytes32 operationId) external nonReentrant validOperation(operationId) { QueuedOperation storage operation = queuedOperations[operationId]; require(block.timestamp >= operation.executeAfter, "Timelock not expired"); require(block.timestamp <= operation.executeAfter + 7 days, "Operation expired"); // Операции истекают через неделю operation.executed = true; executedOperations++; // Исполняем операцию (bool success, bytes memory result) = operation.target.call(operation.data); require(success, string(abi.encodePacked("Execution failed: ", result))); emit OperationExecuted(operationId, msg.sender); } /** * @dev Отменить операцию (только через DLE governance) * @param operationId ID операции * @param reason Причина отмены */ function cancelOperation( bytes32 operationId, string memory reason ) external onlyDLE validOperation(operationId) { QueuedOperation storage operation = queuedOperations[operationId]; operation.cancelled = true; cancelledOperations++; emit OperationCancelled(operationId, msg.sender, reason); } /** * @dev Экстренное исполнение без задержки (только для критических ситуаций) * @param operationId ID операции * @param reason Причина экстренного исполнения */ function emergencyExecute( bytes32 operationId, string memory reason ) external onlyDLE nonReentrant validOperation(operationId) { QueuedOperation storage operation = queuedOperations[operationId]; // Проверяем что операция помечена как экстренная bytes memory opData = operation.data; bytes4 selector; assembly { selector := mload(add(opData, 0x20)) } require(emergencyOperations[selector], "Not emergency operation"); operation.executed = true; executedOperations++; // Исполняем операцию (bool success, bytes memory result) = operation.target.call(operation.data); require(success, string(abi.encodePacked("Emergency execution failed: ", result))); emit OperationExecuted(operationId, msg.sender); emit EmergencyExecution(operationId, reason); } /** * @dev Обновить задержку для типа операции (только через governance) * @param selector Селектор функции * @param newDelay Новая задержка * @param isCritical Является ли операция критической * @param isEmergency Может ли исполняться экстренно */ function updateOperationDelay( bytes4 selector, uint256 newDelay, bool isCritical, bool isEmergency ) external onlyDLE { require(newDelay >= minDelay, "Delay too short"); require(newDelay <= maxDelay, "Delay too long"); uint256 oldDelay = operationDelays[selector]; operationDelays[selector] = newDelay; criticalOperations[selector] = isCritical; emergencyOperations[selector] = isEmergency; emit DelayUpdated(selector, oldDelay, newDelay); } /** * @dev Обновить стандартную задержку (только через governance) * @param newDelay Новая стандартная задержка */ function updateDefaultDelay(uint256 newDelay) external onlyDLE { require(newDelay >= minDelay, "Delay too short"); require(newDelay <= maxDelay, "Delay too long"); uint256 oldDelay = defaultDelay; defaultDelay = newDelay; emit DefaultDelayUpdated(oldDelay, newDelay); } // ===== VIEW ФУНКЦИИ ===== /** * @dev Получить информацию об операции */ function getOperation(bytes32 operationId) external view returns (QueuedOperation memory) { return queuedOperations[operationId]; } /** * @dev Проверить, готова ли операция к исполнению */ function isReady(bytes32 operationId) external view returns (bool) { QueuedOperation storage operation = queuedOperations[operationId]; return operation.id != bytes32(0) && !operation.executed && !operation.cancelled && block.timestamp >= operation.executeAfter; } /** * @dev Получить время до исполнения операции */ function getTimeToExecution(bytes32 operationId) external view returns (uint256) { QueuedOperation storage operation = queuedOperations[operationId]; if (operation.executeAfter <= block.timestamp) { return 0; } return operation.executeAfter - block.timestamp; } /** * @dev Получить список активных операций */ function getActiveOperations() external view returns (bytes32[] memory) { uint256 activeCount = 0; // Считаем активные операции for (uint256 i = 0; i < operationQueue.length; i++) { QueuedOperation storage op = queuedOperations[operationQueue[i]]; if (!op.executed && !op.cancelled) { activeCount++; } } // Заполняем массив bytes32[] memory activeOps = new bytes32[](activeCount); uint256 index = 0; for (uint256 i = 0; i < operationQueue.length; i++) { QueuedOperation storage op = queuedOperations[operationQueue[i]]; if (!op.executed && !op.cancelled) { activeOps[index] = operationQueue[i]; index++; } } return activeOps; } /** * @dev Получить статистику Timelock */ function getTimelockStats() external view returns ( uint256 total, uint256 executed, uint256 cancelled, uint256 pending, uint256 currentDelay ) { return ( totalOperations, executedOperations, cancelledOperations, totalOperations - executedOperations - cancelledOperations, defaultDelay ); } // ===== ВНУТРЕННИЕ ФУНКЦИИ ===== /** * @dev Определить задержку для операции */ function _getOperationDelay(bytes4 selector) internal view returns (uint256) { uint256 customDelay = operationDelays[selector]; if (customDelay > 0) { return customDelay; } // Используем стандартную задержку return defaultDelay; } /** * @dev Настроить стандартные задержки для операций */ function _setupOperationDelays() internal { // Критические операции - длинная задержка (7 дней) bytes4 updateQuorum = bytes4(keccak256("updateQuorumPercentage(uint256)")); bytes4 addModule = bytes4(keccak256("_addModule(bytes32,address)")); bytes4 removeModule = bytes4(keccak256("_removeModule(bytes32)")); bytes4 addChain = bytes4(keccak256("_addSupportedChain(uint256)")); bytes4 removeChain = bytes4(keccak256("_removeSupportedChain(uint256)")); operationDelays[updateQuorum] = 7 days; operationDelays[addModule] = 7 days; operationDelays[removeModule] = 7 days; operationDelays[addChain] = 5 days; operationDelays[removeChain] = 5 days; criticalOperations[updateQuorum] = true; criticalOperations[addModule] = true; criticalOperations[removeModule] = true; criticalOperations[addChain] = true; criticalOperations[removeChain] = true; // Обычные операции - стандартная задержка (2 дня) bytes4 updateDLEInfo = bytes4(keccak256("updateDLEInfo(string,string,string,string,uint256,string[],uint256)")); bytes4 updateChainId = bytes4(keccak256("updateCurrentChainId(uint256)")); bytes4 updateVotingDurations = bytes4(keccak256("_updateVotingDurations(uint256,uint256)")); operationDelays[updateDLEInfo] = 2 days; operationDelays[updateChainId] = 3 days; operationDelays[updateVotingDurations] = 1 days; // Treasury операции - короткая задержка (1 день) bytes4 treasuryTransfer = bytes4(keccak256("treasuryTransfer(address,address,uint256)")); bytes4 treasuryAddToken = bytes4(keccak256("treasuryAddToken(address,string,uint8)")); operationDelays[treasuryTransfer] = 1 days; operationDelays[treasuryAddToken] = 1 days; // Экстренные операции (могут исполняться немедленно при необходимости) emergencyOperations[removeModule] = true; // Удаление вредоносного модуля emergencyOperations[removeChain] = true; // Отключение скомпрометированной сети } }