616 lines
24 KiB
Solidity
616 lines
24 KiB
Solidity
// 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/ERC20.sol";
|
||
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
||
|
||
/**
|
||
* @title DLE (Digital Legal Entity)
|
||
* @dev Основной контракт DLE с модульной архитектурой и мульти-чейн поддержкой
|
||
*/
|
||
contract DLE is ERC20, ReentrancyGuard {
|
||
struct DLEInfo {
|
||
string name;
|
||
string symbol;
|
||
string location;
|
||
string coordinates;
|
||
uint256 jurisdiction;
|
||
uint256 oktmo;
|
||
string[] okvedCodes;
|
||
uint256 kpp;
|
||
uint256 creationTimestamp;
|
||
bool isActive;
|
||
}
|
||
|
||
struct DLEConfig {
|
||
string name;
|
||
string symbol;
|
||
string location;
|
||
string coordinates;
|
||
uint256 jurisdiction;
|
||
uint256 oktmo;
|
||
string[] okvedCodes;
|
||
uint256 kpp;
|
||
uint256 quorumPercentage;
|
||
address[] initialPartners;
|
||
uint256[] initialAmounts;
|
||
uint256[] supportedChainIds; // Поддерживаемые цепочки
|
||
}
|
||
|
||
struct Proposal {
|
||
uint256 id;
|
||
string description;
|
||
uint256 forVotes;
|
||
uint256 againstVotes;
|
||
bool executed;
|
||
uint256 deadline;
|
||
address initiator;
|
||
bytes operation; // Операция для исполнения
|
||
mapping(address => bool) hasVoted;
|
||
mapping(uint256 => bool) chainVoteSynced; // Синхронизация голосов между цепочками
|
||
}
|
||
|
||
struct MultiSigOperation {
|
||
bytes32 operationHash;
|
||
uint256 forSignatures;
|
||
uint256 againstSignatures;
|
||
bool executed;
|
||
uint256 deadline;
|
||
address initiator;
|
||
mapping(address => bool) hasSigned;
|
||
mapping(uint256 => bool) chainSignSynced; // Синхронизация подписей между цепочками
|
||
}
|
||
|
||
// Основные настройки
|
||
DLEInfo public dleInfo;
|
||
uint256 public quorumPercentage;
|
||
uint256 public proposalCounter;
|
||
uint256 public multiSigCounter;
|
||
uint256 public currentChainId;
|
||
|
||
// Модули
|
||
mapping(bytes32 => address) public modules;
|
||
mapping(bytes32 => bool) public activeModules;
|
||
|
||
// Предложения и мультиподписи
|
||
mapping(uint256 => Proposal) public proposals;
|
||
mapping(uint256 => MultiSigOperation) public multiSigOperations;
|
||
|
||
// Мульти-чейн
|
||
mapping(uint256 => bool) public supportedChains;
|
||
mapping(uint256 => bool) public executedProposals; // Синхронизация исполненных предложений
|
||
mapping(uint256 => bool) public executedMultiSig; // Синхронизация исполненных мультиподписей
|
||
|
||
// События
|
||
event DLEInitialized(
|
||
string name,
|
||
string symbol,
|
||
string location,
|
||
string coordinates,
|
||
uint256 jurisdiction,
|
||
uint256 oktmo,
|
||
string[] okvedCodes,
|
||
uint256 kpp,
|
||
address tokenAddress,
|
||
uint256[] supportedChainIds
|
||
);
|
||
event InitialTokensDistributed(address[] partners, uint256[] amounts);
|
||
event ProposalCreated(uint256 proposalId, address initiator, string description);
|
||
event ProposalVoted(uint256 proposalId, address voter, bool support, uint256 votingPower);
|
||
event ProposalExecuted(uint256 proposalId, bytes operation);
|
||
event MultiSigOperationCreated(uint256 operationId, address initiator, bytes32 operationHash);
|
||
event MultiSigSigned(uint256 operationId, address signer, bool support, uint256 signaturePower);
|
||
event MultiSigExecuted(uint256 operationId, bytes32 operationHash);
|
||
event ModuleAdded(bytes32 moduleId, address moduleAddress);
|
||
event ModuleRemoved(bytes32 moduleId);
|
||
event CrossChainExecutionSync(uint256 proposalId, uint256 fromChainId, uint256 toChainId);
|
||
event CrossChainVoteSync(uint256 proposalId, uint256 fromChainId, uint256 toChainId);
|
||
event CrossChainMultiSigSync(uint256 operationId, uint256 fromChainId, uint256 toChainId);
|
||
|
||
constructor(
|
||
DLEConfig memory config,
|
||
uint256 _currentChainId
|
||
) ERC20(config.name, config.symbol) {
|
||
dleInfo = DLEInfo({
|
||
name: config.name,
|
||
symbol: config.symbol,
|
||
location: config.location,
|
||
coordinates: config.coordinates,
|
||
jurisdiction: config.jurisdiction,
|
||
oktmo: config.oktmo,
|
||
okvedCodes: config.okvedCodes,
|
||
kpp: config.kpp,
|
||
creationTimestamp: block.timestamp,
|
||
isActive: true
|
||
});
|
||
|
||
quorumPercentage = config.quorumPercentage;
|
||
currentChainId = _currentChainId;
|
||
|
||
// Настраиваем поддерживаемые цепочки
|
||
for (uint256 i = 0; i < config.supportedChainIds.length; i++) {
|
||
supportedChains[config.supportedChainIds[i]] = true;
|
||
}
|
||
|
||
// Распределяем начальные токены партнерам
|
||
require(config.initialPartners.length == config.initialAmounts.length, "Arrays length mismatch");
|
||
require(config.initialPartners.length > 0, "No initial partners");
|
||
|
||
for (uint256 i = 0; i < config.initialPartners.length; i++) {
|
||
require(config.initialPartners[i] != address(0), "Zero address");
|
||
require(config.initialAmounts[i] > 0, "Zero amount");
|
||
_mint(config.initialPartners[i], config.initialAmounts[i]);
|
||
}
|
||
|
||
emit InitialTokensDistributed(config.initialPartners, config.initialAmounts);
|
||
emit DLEInitialized(
|
||
config.name,
|
||
config.symbol,
|
||
config.location,
|
||
config.coordinates,
|
||
config.jurisdiction,
|
||
config.oktmo,
|
||
config.okvedCodes,
|
||
config.kpp,
|
||
address(this),
|
||
config.supportedChainIds
|
||
);
|
||
}
|
||
|
||
/**
|
||
* @dev Создать предложение с выбором цепочки для кворума
|
||
* @param _description Описание предложения
|
||
* @param _duration Длительность голосования в секундах
|
||
* @param _operation Операция для исполнения
|
||
* @param _governanceChainId ID цепочки для сбора голосов
|
||
*/
|
||
function createProposal(
|
||
string memory _description,
|
||
uint256 _duration,
|
||
bytes memory _operation,
|
||
uint256 _governanceChainId
|
||
) external returns (uint256) {
|
||
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
||
require(_duration > 0, "Duration must be positive");
|
||
require(supportedChains[_governanceChainId], "Chain not supported");
|
||
require(checkChainConnection(_governanceChainId), "Chain not available");
|
||
|
||
uint256 proposalId = proposalCounter++;
|
||
Proposal storage proposal = proposals[proposalId];
|
||
|
||
proposal.id = proposalId;
|
||
proposal.description = _description;
|
||
proposal.forVotes = 0;
|
||
proposal.againstVotes = 0;
|
||
proposal.executed = false;
|
||
proposal.deadline = block.timestamp + _duration;
|
||
proposal.initiator = msg.sender;
|
||
proposal.operation = _operation;
|
||
|
||
emit ProposalCreated(proposalId, msg.sender, _description);
|
||
return proposalId;
|
||
}
|
||
|
||
/**
|
||
* @dev Голосовать за предложение
|
||
* @param _proposalId ID предложения
|
||
* @param _support Поддержка предложения
|
||
*/
|
||
function vote(uint256 _proposalId, bool _support) external nonReentrant {
|
||
Proposal storage proposal = proposals[_proposalId];
|
||
require(proposal.id == _proposalId, "Proposal does not exist");
|
||
require(block.timestamp < proposal.deadline, "Voting ended");
|
||
require(!proposal.executed, "Proposal already executed");
|
||
require(!proposal.hasVoted[msg.sender], "Already voted");
|
||
require(balanceOf(msg.sender) > 0, "No tokens to vote");
|
||
|
||
uint256 votingPower = balanceOf(msg.sender);
|
||
proposal.hasVoted[msg.sender] = true;
|
||
|
||
if (_support) {
|
||
proposal.forVotes += votingPower;
|
||
} else {
|
||
proposal.againstVotes += votingPower;
|
||
}
|
||
|
||
emit ProposalVoted(_proposalId, msg.sender, _support, votingPower);
|
||
}
|
||
|
||
/**
|
||
* @dev Синхронизировать голос из другой цепочки
|
||
* @param _proposalId ID предложения
|
||
* @param _fromChainId ID цепочки откуда синхронизируем
|
||
* @param _forVotes Голоса за
|
||
* @param _againstVotes Голоса против
|
||
*/
|
||
function syncVoteFromChain(
|
||
uint256 _proposalId,
|
||
uint256 _fromChainId,
|
||
uint256 _forVotes,
|
||
uint256 _againstVotes,
|
||
bytes memory /* _proof */
|
||
) external {
|
||
Proposal storage proposal = proposals[_proposalId];
|
||
require(proposal.id == _proposalId, "Proposal does not exist");
|
||
require(supportedChains[_fromChainId], "Chain not supported");
|
||
require(!proposal.chainVoteSynced[_fromChainId], "Already synced");
|
||
|
||
// Здесь должна быть проверка proof (для простоты пропускаем)
|
||
// В реальной реализации нужно проверять доказательство
|
||
|
||
proposal.forVotes += _forVotes;
|
||
proposal.againstVotes += _againstVotes;
|
||
proposal.chainVoteSynced[_fromChainId] = true;
|
||
|
||
emit CrossChainVoteSync(_proposalId, _fromChainId, currentChainId);
|
||
}
|
||
|
||
/**
|
||
* @dev Проверить результат предложения
|
||
* @param _proposalId ID предложения
|
||
* @return passed Прошло ли предложение
|
||
* @return quorumReached Достигнут ли кворум
|
||
*/
|
||
function checkProposalResult(uint256 _proposalId) public view returns (bool passed, bool quorumReached) {
|
||
Proposal storage proposal = proposals[_proposalId];
|
||
require(proposal.id == _proposalId, "Proposal does not exist");
|
||
|
||
uint256 totalVotes = proposal.forVotes + proposal.againstVotes;
|
||
uint256 quorumRequired = (totalSupply() * quorumPercentage) / 100;
|
||
|
||
quorumReached = totalVotes >= quorumRequired;
|
||
passed = quorumReached && proposal.forVotes > proposal.againstVotes;
|
||
|
||
return (passed, quorumReached);
|
||
}
|
||
|
||
/**
|
||
* @dev Исполнить предложение
|
||
* @param _proposalId ID предложения
|
||
*/
|
||
function executeProposal(uint256 _proposalId) external {
|
||
Proposal storage proposal = proposals[_proposalId];
|
||
require(proposal.id == _proposalId, "Proposal does not exist");
|
||
require(!proposal.executed, "Proposal already executed");
|
||
require(block.timestamp >= proposal.deadline, "Voting not ended");
|
||
|
||
(bool passed, bool quorumReached) = checkProposalResult(_proposalId);
|
||
require(passed && quorumReached, "Proposal not passed");
|
||
|
||
proposal.executed = true;
|
||
|
||
// Исполняем операцию
|
||
_executeOperation(proposal.operation);
|
||
|
||
emit ProposalExecuted(_proposalId, proposal.operation);
|
||
}
|
||
|
||
/**
|
||
* @dev Создать мультиподпись операцию
|
||
* @param _operationHash Хеш операции
|
||
* @param _duration Длительность сбора подписей
|
||
*/
|
||
function createMultiSigOperation(
|
||
bytes32 _operationHash,
|
||
uint256 _duration
|
||
) external returns (uint256) {
|
||
require(balanceOf(msg.sender) > 0, "Must hold tokens to create operation");
|
||
require(_duration > 0, "Duration must be positive");
|
||
|
||
uint256 operationId = multiSigCounter++;
|
||
MultiSigOperation storage operation = multiSigOperations[operationId];
|
||
|
||
operation.operationHash = _operationHash;
|
||
operation.forSignatures = 0;
|
||
operation.againstSignatures = 0;
|
||
operation.executed = false;
|
||
operation.deadline = block.timestamp + _duration;
|
||
operation.initiator = msg.sender;
|
||
|
||
emit MultiSigOperationCreated(operationId, msg.sender, _operationHash);
|
||
return operationId;
|
||
}
|
||
|
||
/**
|
||
* @dev Подписать мультиподпись операцию
|
||
* @param _operationId ID операции
|
||
* @param _support Поддержка операции
|
||
*/
|
||
function signMultiSigOperation(uint256 _operationId, bool _support) external nonReentrant {
|
||
MultiSigOperation storage operation = multiSigOperations[_operationId];
|
||
require(operation.operationHash != bytes32(0), "Operation does not exist");
|
||
require(block.timestamp < operation.deadline, "Signing ended");
|
||
require(!operation.executed, "Operation already executed");
|
||
require(!operation.hasSigned[msg.sender], "Already signed");
|
||
require(balanceOf(msg.sender) > 0, "No tokens to sign");
|
||
|
||
uint256 signaturePower = balanceOf(msg.sender);
|
||
operation.hasSigned[msg.sender] = true;
|
||
|
||
if (_support) {
|
||
operation.forSignatures += signaturePower;
|
||
} else {
|
||
operation.againstSignatures += signaturePower;
|
||
}
|
||
|
||
emit MultiSigSigned(_operationId, msg.sender, _support, signaturePower);
|
||
}
|
||
|
||
/**
|
||
* @dev Синхронизировать мультиподпись из другой цепочки
|
||
* @param _operationId ID операции
|
||
* @param _fromChainId ID цепочки откуда синхронизируем
|
||
* @param _forSignatures Подписи за
|
||
* @param _againstSignatures Подписи против
|
||
*/
|
||
function syncMultiSigFromChain(
|
||
uint256 _operationId,
|
||
uint256 _fromChainId,
|
||
uint256 _forSignatures,
|
||
uint256 _againstSignatures,
|
||
bytes memory /* _proof */
|
||
) external {
|
||
MultiSigOperation storage operation = multiSigOperations[_operationId];
|
||
require(operation.operationHash != bytes32(0), "Operation does not exist");
|
||
require(supportedChains[_fromChainId], "Chain not supported");
|
||
require(!operation.chainSignSynced[_fromChainId], "Already synced");
|
||
|
||
// Здесь должна быть проверка proof
|
||
// В реальной реализации нужно проверять доказательство
|
||
|
||
operation.forSignatures += _forSignatures;
|
||
operation.againstSignatures += _againstSignatures;
|
||
operation.chainSignSynced[_fromChainId] = true;
|
||
|
||
emit CrossChainMultiSigSync(_operationId, _fromChainId, currentChainId);
|
||
}
|
||
|
||
/**
|
||
* @dev Проверить результат мультиподписи
|
||
* @param _operationId ID операции
|
||
* @return passed Прошла ли операция
|
||
* @return quorumReached Достигнут ли кворум
|
||
*/
|
||
function checkMultiSigResult(uint256 _operationId) public view returns (bool passed, bool quorumReached) {
|
||
MultiSigOperation storage operation = multiSigOperations[_operationId];
|
||
require(operation.operationHash != bytes32(0), "Operation does not exist");
|
||
|
||
uint256 totalSignatures = operation.forSignatures + operation.againstSignatures;
|
||
uint256 quorumRequired = (totalSupply() * quorumPercentage) / 100;
|
||
|
||
quorumReached = totalSignatures >= quorumRequired;
|
||
passed = quorumReached && operation.forSignatures > operation.againstSignatures;
|
||
|
||
return (passed, quorumReached);
|
||
}
|
||
|
||
/**
|
||
* @dev Исполнить мультиподпись операцию
|
||
* @param _operationId ID операции
|
||
*/
|
||
function executeMultiSigOperation(uint256 _operationId) external {
|
||
MultiSigOperation storage operation = multiSigOperations[_operationId];
|
||
require(operation.operationHash != bytes32(0), "Operation does not exist");
|
||
require(!operation.executed, "Operation already executed");
|
||
require(block.timestamp >= operation.deadline, "Signing not ended");
|
||
|
||
(bool passed, bool quorumReached) = checkMultiSigResult(_operationId);
|
||
require(passed && quorumReached, "Operation not passed");
|
||
|
||
operation.executed = true;
|
||
|
||
emit MultiSigExecuted(_operationId, operation.operationHash);
|
||
}
|
||
|
||
/**
|
||
* @dev Синхронизировать исполнение из другой цепочки
|
||
* @param _proposalId ID предложения
|
||
* @param _fromChainId ID цепочки откуда синхронизируем
|
||
*/
|
||
function syncExecutionFromChain(
|
||
uint256 _proposalId,
|
||
uint256 _fromChainId,
|
||
bytes memory /* _proof */
|
||
) external {
|
||
require(supportedChains[_fromChainId], "Chain not supported");
|
||
require(!executedProposals[_proposalId], "Already executed");
|
||
|
||
// Здесь должна быть проверка proof
|
||
// В реальной реализации нужно проверять доказательство
|
||
|
||
executedProposals[_proposalId] = true;
|
||
|
||
// Получаем операцию из предложения
|
||
Proposal storage proposal = proposals[_proposalId];
|
||
if (proposal.id == _proposalId) {
|
||
_executeOperation(proposal.operation);
|
||
}
|
||
|
||
emit CrossChainExecutionSync(_proposalId, _fromChainId, currentChainId);
|
||
}
|
||
|
||
/**
|
||
* @dev Проверить подключение к цепочке
|
||
* @param _chainId ID цепочки
|
||
* @return isAvailable Доступна ли цепочка
|
||
*/
|
||
function checkChainConnection(uint256 _chainId) public view returns (bool isAvailable) {
|
||
// В реальной реализации здесь должна быть проверка подключения
|
||
// Для примера возвращаем true для поддерживаемых цепочек
|
||
return supportedChains[_chainId];
|
||
}
|
||
|
||
/**
|
||
* @dev Проверить все подключения перед синхронизацией
|
||
* @param _proposalId ID предложения
|
||
* @return allChainsReady Готовы ли все цепочки
|
||
*/
|
||
function checkSyncReadiness(uint256 _proposalId) public view returns (bool allChainsReady) {
|
||
Proposal storage proposal = proposals[_proposalId];
|
||
require(proposal.id == _proposalId, "Proposal does not exist");
|
||
|
||
// Проверяем все поддерживаемые цепочки
|
||
for (uint256 i = 0; i < getSupportedChainCount(); i++) {
|
||
uint256 chainId = getSupportedChainId(i);
|
||
if (!checkChainConnection(chainId)) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @dev Синхронизация только при 100% готовности
|
||
* @param _proposalId ID предложения
|
||
*/
|
||
function syncToAllChains(uint256 _proposalId) external {
|
||
require(checkSyncReadiness(_proposalId), "Not all chains ready");
|
||
|
||
// Выполняем синхронизацию во все цепочки
|
||
for (uint256 i = 0; i < getSupportedChainCount(); i++) {
|
||
uint256 chainId = getSupportedChainId(i);
|
||
syncToChain(_proposalId, chainId);
|
||
}
|
||
|
||
emit SyncCompleted(_proposalId);
|
||
}
|
||
|
||
/**
|
||
* @dev Синхронизация в конкретную цепочку
|
||
* @param _proposalId ID предложения
|
||
* @param _chainId ID цепочки
|
||
*/
|
||
function syncToChain(uint256 _proposalId, uint256 _chainId) internal {
|
||
// В реальной реализации здесь будет вызов cross-chain bridge
|
||
// Для примера просто эмитим событие
|
||
emit CrossChainExecutionSync(_proposalId, currentChainId, _chainId);
|
||
}
|
||
|
||
/**
|
||
* @dev Получить количество поддерживаемых цепочек
|
||
*/
|
||
function getSupportedChainCount() public pure returns (uint256) {
|
||
// В реальной реализации нужно хранить массив поддерживаемых цепочек
|
||
// Для примера возвращаем 4 (Ethereum, Polygon, BSC, Arbitrum)
|
||
return 4;
|
||
}
|
||
|
||
/**
|
||
* @dev Получить ID поддерживаемой цепочки по индексу
|
||
* @param _index Индекс цепочки
|
||
*/
|
||
function getSupportedChainId(uint256 _index) public pure returns (uint256) {
|
||
if (_index == 0) return 1; // Ethereum
|
||
if (_index == 1) return 137; // Polygon
|
||
if (_index == 2) return 56; // BSC
|
||
if (_index == 3) return 42161; // Arbitrum
|
||
revert("Invalid chain index");
|
||
}
|
||
|
||
/**
|
||
* @dev Исполнить операцию
|
||
* @param _operation Операция для исполнения
|
||
*/
|
||
function _executeOperation(bytes memory _operation) internal {
|
||
// Декодируем операцию
|
||
(bytes4 selector, bytes memory data) = abi.decode(_operation, (bytes4, bytes));
|
||
|
||
if (selector == bytes4(keccak256("transfer(address,uint256)"))) {
|
||
// Операция передачи токенов
|
||
(address to, uint256 amount) = abi.decode(data, (address, uint256));
|
||
_transfer(msg.sender, to, amount);
|
||
} else if (selector == bytes4(keccak256("mint(address,uint256)"))) {
|
||
// Операция минтинга токенов
|
||
(address to, uint256 amount) = abi.decode(data, (address, uint256));
|
||
_mint(to, amount);
|
||
} else if (selector == bytes4(keccak256("burn(address,uint256)"))) {
|
||
// Операция сжигания токенов
|
||
(address from, uint256 amount) = abi.decode(data, (address, uint256));
|
||
_burn(from, amount);
|
||
} else {
|
||
// Неизвестная операция
|
||
revert("Unknown operation");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @dev Добавить модуль
|
||
* @param _moduleId ID модуля
|
||
* @param _moduleAddress Адрес модуля
|
||
*/
|
||
function addModule(bytes32 _moduleId, address _moduleAddress) external {
|
||
require(balanceOf(msg.sender) > 0, "Must hold tokens to add module");
|
||
require(_moduleAddress != address(0), "Zero address");
|
||
require(!activeModules[_moduleId], "Module already exists");
|
||
|
||
modules[_moduleId] = _moduleAddress;
|
||
activeModules[_moduleId] = true;
|
||
|
||
emit ModuleAdded(_moduleId, _moduleAddress);
|
||
}
|
||
|
||
/**
|
||
* @dev Удалить модуль
|
||
* @param _moduleId ID модуля
|
||
*/
|
||
function removeModule(bytes32 _moduleId) external {
|
||
require(balanceOf(msg.sender) > 0, "Must hold tokens to remove module");
|
||
require(activeModules[_moduleId], "Module does not exist");
|
||
|
||
delete modules[_moduleId];
|
||
activeModules[_moduleId] = false;
|
||
|
||
emit ModuleRemoved(_moduleId);
|
||
}
|
||
|
||
/**
|
||
* @dev Получить информацию о DLE
|
||
*/
|
||
function getDLEInfo() external view returns (DLEInfo memory) {
|
||
return dleInfo;
|
||
}
|
||
|
||
/**
|
||
* @dev Проверить, активен ли модуль
|
||
* @param _moduleId ID модуля
|
||
*/
|
||
function isModuleActive(bytes32 _moduleId) external view returns (bool) {
|
||
return activeModules[_moduleId];
|
||
}
|
||
|
||
/**
|
||
* @dev Получить адрес модуля
|
||
* @param _moduleId ID модуля
|
||
*/
|
||
function getModuleAddress(bytes32 _moduleId) external view returns (address) {
|
||
return modules[_moduleId];
|
||
}
|
||
|
||
/**
|
||
* @dev Проверить, поддерживается ли цепочка
|
||
* @param _chainId ID цепочки
|
||
*/
|
||
function isChainSupported(uint256 _chainId) external view returns (bool) {
|
||
return supportedChains[_chainId];
|
||
}
|
||
|
||
/**
|
||
* @dev Получить текущий ID цепочки
|
||
*/
|
||
function getCurrentChainId() external view returns (uint256) {
|
||
return currentChainId;
|
||
}
|
||
|
||
// События для новых функций
|
||
event SyncCompleted(uint256 proposalId);
|
||
} |