// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "../vendor/openzeppelin/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "../registry/UniversalAssetRegistry.sol"; import "./UniversalCCIPBridge.sol"; /** * @title BridgeOrchestrator * @notice Routes bridge requests to appropriate asset-specific bridges * @dev Central routing layer for multi-asset bridge system */ contract BridgeOrchestrator is Initializable, AccessControlUpgradeable, UUPSUpgradeable { bytes32 public constant ROUTER_ADMIN_ROLE = keccak256("ROUTER_ADMIN_ROLE"); bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); // Core dependencies UniversalAssetRegistry public assetRegistry; UniversalCCIPBridge public defaultBridge; // Asset type to bridge mapping mapping(bytes32 => address) public assetTypeToBridge; mapping(address => bool) public isRegisteredBridge; // Routing statistics struct RoutingStats { uint256 totalBridges; uint256 successfulBridges; uint256 failedBridges; uint256 lastBridgeTime; } mapping(address => RoutingStats) public bridgeStats; mapping(UniversalAssetRegistry.AssetType => RoutingStats) public assetTypeStats; event BridgeRouted( address indexed token, UniversalAssetRegistry.AssetType assetType, address indexed bridgeContract, bytes32 indexed messageId ); event AssetTypeBridgeRegistered( bytes32 indexed assetTypeHash, UniversalAssetRegistry.AssetType assetType, address bridgeContract ); event BridgeUnregistered( bytes32 indexed assetTypeHash, address bridgeContract ); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize( address _assetRegistry, address _defaultBridge, address admin ) external initializer { __AccessControl_init(); __UUPSUpgradeable_init(); require(_assetRegistry != address(0), "Zero registry"); require(_defaultBridge != address(0), "Zero bridge"); assetRegistry = UniversalAssetRegistry(_assetRegistry); defaultBridge = UniversalCCIPBridge(payable(_defaultBridge)); _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(ROUTER_ADMIN_ROLE, admin); _grantRole(UPGRADER_ROLE, admin); } function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {} /** * @notice Route bridge request to appropriate bridge */ function bridge( UniversalCCIPBridge.BridgeOperation calldata op ) external payable returns (bytes32 messageId) { // Get asset information UniversalAssetRegistry.UniversalAsset memory asset = assetRegistry.getAsset(op.token); require(asset.isActive, "Asset not active"); // Get bridge contract for this asset type bytes32 assetTypeHash = bytes32(uint256(asset.assetType)); address bridgeContract = assetTypeToBridge[assetTypeHash]; // Use default bridge if no specialized bridge if (bridgeContract == address(0)) { bridgeContract = address(defaultBridge); } require(isRegisteredBridge[bridgeContract], "Bridge not registered"); // Forward call to specialized bridge (bool success, bytes memory data) = bridgeContract.call{value: msg.value}( abi.encodeWithSelector( UniversalCCIPBridge.bridge.selector, op ) ); require(success, "Bridge call failed"); messageId = abi.decode(data, (bytes32)); // Update statistics _updateStats(bridgeContract, asset.assetType, true); emit BridgeRouted(op.token, asset.assetType, bridgeContract, messageId); return messageId; } /** * @notice Register asset type bridge */ function registerAssetTypeBridge( UniversalAssetRegistry.AssetType assetType, address bridgeContract ) external onlyRole(ROUTER_ADMIN_ROLE) { require(bridgeContract != address(0), "Zero address"); require(bridgeContract.code.length > 0, "Not a contract"); bytes32 assetTypeHash = bytes32(uint256(assetType)); assetTypeToBridge[assetTypeHash] = bridgeContract; isRegisteredBridge[bridgeContract] = true; emit AssetTypeBridgeRegistered(assetTypeHash, assetType, bridgeContract); } /** * @notice Unregister bridge */ function unregisterBridge( UniversalAssetRegistry.AssetType assetType ) external onlyRole(ROUTER_ADMIN_ROLE) { bytes32 assetTypeHash = bytes32(uint256(assetType)); address bridgeContract = assetTypeToBridge[assetTypeHash]; delete assetTypeToBridge[assetTypeHash]; // Note: We don't remove from isRegisteredBridge in case it's used elsewhere emit BridgeUnregistered(assetTypeHash, bridgeContract); } /** * @notice Update routing statistics */ function _updateStats( address bridgeContract, UniversalAssetRegistry.AssetType assetType, bool success ) internal { RoutingStats storage bridgeStat = bridgeStats[bridgeContract]; RoutingStats storage typeStat = assetTypeStats[assetType]; bridgeStat.totalBridges++; typeStat.totalBridges++; if (success) { bridgeStat.successfulBridges++; typeStat.successfulBridges++; } else { bridgeStat.failedBridges++; typeStat.failedBridges++; } bridgeStat.lastBridgeTime = block.timestamp; typeStat.lastBridgeTime = block.timestamp; } /** * @notice Set default bridge */ function setDefaultBridge(address _defaultBridge) external onlyRole(DEFAULT_ADMIN_ROLE) { require(_defaultBridge != address(0), "Zero address"); defaultBridge = UniversalCCIPBridge(payable(_defaultBridge)); isRegisteredBridge[_defaultBridge] = true; } // View functions function getBridgeForAssetType(UniversalAssetRegistry.AssetType assetType) external view returns (address) { bytes32 assetTypeHash = bytes32(uint256(assetType)); address bridgeAddr = assetTypeToBridge[assetTypeHash]; return bridgeAddr != address(0) ? bridgeAddr : address(defaultBridge); } function getBridgeStats(address bridgeContract) external view returns (RoutingStats memory) { return bridgeStats[bridgeContract]; } function getAssetTypeStats(UniversalAssetRegistry.AssetType assetType) external view returns (RoutingStats memory) { return assetTypeStats[assetType]; } }