321 lines
11 KiB
Solidity
321 lines
11 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
|
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
|
|
|
/**
|
|
* @title ChainRegistry
|
|
* @notice Central registry for all supported blockchains (EVM and non-EVM)
|
|
* @dev Maps chain identifiers to adapter contracts and metadata
|
|
*/
|
|
contract ChainRegistry is
|
|
Initializable,
|
|
AccessControlUpgradeable,
|
|
UUPSUpgradeable
|
|
{
|
|
bytes32 public constant REGISTRY_ADMIN_ROLE = keccak256("REGISTRY_ADMIN_ROLE");
|
|
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
|
|
|
|
enum ChainType {
|
|
EVM, // Ethereum, Polygon, Arbitrum, etc.
|
|
XRPL, // XRP Ledger
|
|
XDC, // XDC Network (EVM-compatible but different address format)
|
|
Stellar, // Stellar Network
|
|
Algorand, // Algorand
|
|
Hedera, // Hedera Hashgraph
|
|
Tron, // Tron
|
|
TON, // The Open Network
|
|
Cosmos, // Cosmos Hub
|
|
Solana, // Solana
|
|
Fabric, // Hyperledger Fabric
|
|
Corda, // R3 Corda
|
|
Indy, // Hyperledger Indy
|
|
Firefly, // Hyperledger Firefly
|
|
Cacti, // Hyperledger Cacti
|
|
Other // Custom/unknown
|
|
}
|
|
|
|
struct ChainMetadata {
|
|
uint256 chainId; // For EVM chains (0 for non-EVM)
|
|
string chainIdentifier; // For non-EVM (e.g., "XRPL", "Fabric-Channel1")
|
|
ChainType chainType;
|
|
address adapter; // Bridge adapter contract address
|
|
bool isActive;
|
|
uint256 minConfirmations; // Required confirmations
|
|
uint256 avgBlockTime; // Average block time in seconds
|
|
bool requiresOracle; // Non-EVM chains need oracle
|
|
string rpcEndpoint; // RPC endpoint (encrypted or public)
|
|
string explorerUrl; // Block explorer URL
|
|
bytes additionalData; // Chain-specific config
|
|
uint256 addedAt;
|
|
uint256 lastUpdated;
|
|
}
|
|
|
|
// EVM chains: chainId => metadata
|
|
mapping(uint256 => ChainMetadata) public evmChains;
|
|
|
|
// Non-EVM chains: identifier => metadata
|
|
mapping(string => ChainMetadata) public nonEvmChains;
|
|
|
|
// All registered chain identifiers
|
|
uint256[] public registeredEVMChainIds;
|
|
string[] public registeredNonEVMIdentifiers;
|
|
|
|
// Adapter validation
|
|
mapping(address => bool) public isValidAdapter;
|
|
mapping(address => ChainType) public adapterToChainType;
|
|
|
|
event ChainRegistered(
|
|
uint256 indexed chainId,
|
|
string indexed chainIdentifier,
|
|
ChainType chainType,
|
|
address adapter
|
|
);
|
|
|
|
event ChainUpdated(
|
|
uint256 indexed chainId,
|
|
string indexed chainIdentifier,
|
|
bool isActive
|
|
);
|
|
|
|
event ChainDeactivated(
|
|
uint256 indexed chainId,
|
|
string indexed chainIdentifier
|
|
);
|
|
|
|
event AdapterUpdated(
|
|
uint256 indexed chainId,
|
|
string indexed chainIdentifier,
|
|
address oldAdapter,
|
|
address newAdapter
|
|
);
|
|
|
|
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
constructor() {
|
|
_disableInitializers();
|
|
}
|
|
|
|
function initialize(address admin) external initializer {
|
|
__AccessControl_init();
|
|
__UUPSUpgradeable_init();
|
|
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
_grantRole(REGISTRY_ADMIN_ROLE, admin);
|
|
_grantRole(UPGRADER_ROLE, admin);
|
|
}
|
|
|
|
function _authorizeUpgrade(address newImplementation)
|
|
internal override onlyRole(UPGRADER_ROLE) {}
|
|
|
|
/**
|
|
* @notice Register an EVM chain
|
|
*/
|
|
function registerEVMChain(
|
|
uint256 chainId,
|
|
address adapter,
|
|
string calldata explorerUrl,
|
|
uint256 minConfirmations,
|
|
uint256 avgBlockTime,
|
|
bytes calldata additionalData
|
|
) external onlyRole(REGISTRY_ADMIN_ROLE) {
|
|
require(chainId != 0, "Invalid chain ID");
|
|
require(adapter != address(0), "Zero adapter");
|
|
require(adapter.code.length > 0, "Not a contract");
|
|
|
|
ChainMetadata storage chain = evmChains[chainId];
|
|
|
|
// If new chain, add to list
|
|
if (chain.addedAt == 0) {
|
|
registeredEVMChainIds.push(chainId);
|
|
}
|
|
|
|
chain.chainId = chainId;
|
|
chain.chainIdentifier = string(abi.encodePacked("EVM-", chainId));
|
|
chain.chainType = ChainType.EVM;
|
|
chain.adapter = adapter;
|
|
chain.isActive = true;
|
|
chain.minConfirmations = minConfirmations;
|
|
chain.avgBlockTime = avgBlockTime;
|
|
chain.requiresOracle = false;
|
|
chain.explorerUrl = explorerUrl;
|
|
chain.additionalData = additionalData;
|
|
chain.lastUpdated = block.timestamp;
|
|
|
|
if (chain.addedAt == 0) {
|
|
chain.addedAt = block.timestamp;
|
|
}
|
|
|
|
isValidAdapter[adapter] = true;
|
|
adapterToChainType[adapter] = ChainType.EVM;
|
|
|
|
emit ChainRegistered(chainId, chain.chainIdentifier, ChainType.EVM, adapter);
|
|
}
|
|
|
|
/**
|
|
* @notice Register a non-EVM chain
|
|
*/
|
|
function registerNonEVMChain(
|
|
string calldata chainIdentifier,
|
|
ChainType chainType,
|
|
address adapter,
|
|
string calldata explorerUrl,
|
|
uint256 minConfirmations,
|
|
uint256 avgBlockTime,
|
|
bool requiresOracle,
|
|
bytes calldata additionalData
|
|
) external onlyRole(REGISTRY_ADMIN_ROLE) {
|
|
require(bytes(chainIdentifier).length > 0, "Empty identifier");
|
|
require(adapter != address(0), "Zero adapter");
|
|
require(adapter.code.length > 0, "Not a contract");
|
|
require(chainType != ChainType.EVM, "Use registerEVMChain");
|
|
|
|
ChainMetadata storage chain = nonEvmChains[chainIdentifier];
|
|
|
|
// If new chain, add to list
|
|
if (chain.addedAt == 0) {
|
|
registeredNonEVMIdentifiers.push(chainIdentifier);
|
|
}
|
|
|
|
chain.chainId = 0;
|
|
chain.chainIdentifier = chainIdentifier;
|
|
chain.chainType = chainType;
|
|
chain.adapter = adapter;
|
|
chain.isActive = true;
|
|
chain.minConfirmations = minConfirmations;
|
|
chain.avgBlockTime = avgBlockTime;
|
|
chain.requiresOracle = requiresOracle;
|
|
chain.explorerUrl = explorerUrl;
|
|
chain.additionalData = additionalData;
|
|
chain.lastUpdated = block.timestamp;
|
|
|
|
if (chain.addedAt == 0) {
|
|
chain.addedAt = block.timestamp;
|
|
}
|
|
|
|
isValidAdapter[adapter] = true;
|
|
adapterToChainType[adapter] = chainType;
|
|
|
|
emit ChainRegistered(0, chainIdentifier, chainType, adapter);
|
|
}
|
|
|
|
/**
|
|
* @notice Update chain adapter
|
|
*/
|
|
function updateAdapter(
|
|
uint256 chainId,
|
|
string calldata chainIdentifier,
|
|
address newAdapter
|
|
) external onlyRole(REGISTRY_ADMIN_ROLE) {
|
|
require(newAdapter != address(0), "Zero adapter");
|
|
require(newAdapter.code.length > 0, "Not a contract");
|
|
|
|
address oldAdapter;
|
|
|
|
if (chainId != 0) {
|
|
ChainMetadata storage chain = evmChains[chainId];
|
|
require(chain.addedAt != 0, "Chain not registered");
|
|
oldAdapter = chain.adapter;
|
|
chain.adapter = newAdapter;
|
|
chain.lastUpdated = block.timestamp;
|
|
} else {
|
|
ChainMetadata storage chain = nonEvmChains[chainIdentifier];
|
|
require(chain.addedAt != 0, "Chain not registered");
|
|
oldAdapter = chain.adapter;
|
|
chain.adapter = newAdapter;
|
|
chain.lastUpdated = block.timestamp;
|
|
}
|
|
|
|
isValidAdapter[oldAdapter] = false;
|
|
isValidAdapter[newAdapter] = true;
|
|
|
|
emit AdapterUpdated(chainId, chainIdentifier, oldAdapter, newAdapter);
|
|
}
|
|
|
|
/**
|
|
* @notice Enable/disable chain
|
|
*/
|
|
function setChainActive(
|
|
uint256 chainId,
|
|
string calldata chainIdentifier,
|
|
bool active
|
|
) external onlyRole(REGISTRY_ADMIN_ROLE) {
|
|
if (chainId != 0) {
|
|
ChainMetadata storage chain = evmChains[chainId];
|
|
require(chain.addedAt != 0, "Chain not registered");
|
|
chain.isActive = active;
|
|
chain.lastUpdated = block.timestamp;
|
|
} else {
|
|
ChainMetadata storage chain = nonEvmChains[chainIdentifier];
|
|
require(chain.addedAt != 0, "Chain not registered");
|
|
chain.isActive = active;
|
|
chain.lastUpdated = block.timestamp;
|
|
}
|
|
|
|
emit ChainUpdated(chainId, chainIdentifier, active);
|
|
|
|
if (!active) {
|
|
emit ChainDeactivated(chainId, chainIdentifier);
|
|
}
|
|
}
|
|
|
|
// View functions
|
|
|
|
function getEVMChain(uint256 chainId)
|
|
external view returns (ChainMetadata memory) {
|
|
return evmChains[chainId];
|
|
}
|
|
|
|
function getNonEVMChain(string calldata chainIdentifier)
|
|
external view returns (ChainMetadata memory) {
|
|
return nonEvmChains[chainIdentifier];
|
|
}
|
|
|
|
function getAdapter(uint256 chainId, string calldata chainIdentifier)
|
|
external view returns (address) {
|
|
if (chainId != 0) {
|
|
return evmChains[chainId].adapter;
|
|
} else {
|
|
return nonEvmChains[chainIdentifier].adapter;
|
|
}
|
|
}
|
|
|
|
function isChainActive(uint256 chainId, string calldata chainIdentifier)
|
|
external view returns (bool) {
|
|
if (chainId != 0) {
|
|
return evmChains[chainId].isActive;
|
|
} else {
|
|
return nonEvmChains[chainIdentifier].isActive;
|
|
}
|
|
}
|
|
|
|
function getAllEVMChains()
|
|
external view returns (uint256[] memory, ChainMetadata[] memory) {
|
|
uint256 length = registeredEVMChainIds.length;
|
|
ChainMetadata[] memory chains = new ChainMetadata[](length);
|
|
|
|
for (uint256 i = 0; i < length; i++) {
|
|
chains[i] = evmChains[registeredEVMChainIds[i]];
|
|
}
|
|
|
|
return (registeredEVMChainIds, chains);
|
|
}
|
|
|
|
function getAllNonEVMChains()
|
|
external view returns (string[] memory, ChainMetadata[] memory) {
|
|
uint256 length = registeredNonEVMIdentifiers.length;
|
|
ChainMetadata[] memory chains = new ChainMetadata[](length);
|
|
|
|
for (uint256 i = 0; i < length; i++) {
|
|
chains[i] = nonEvmChains[registeredNonEVMIdentifiers[i]];
|
|
}
|
|
|
|
return (registeredNonEVMIdentifiers, chains);
|
|
}
|
|
|
|
function getTotalChains() external view returns (uint256, uint256) {
|
|
return (registeredEVMChainIds.length, registeredNonEVMIdentifiers.length);
|
|
}
|
|
}
|