// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/utils/Pausable.sol"; import "../bridge/interop/BridgeRegistry.sol"; /** * @title TokenRegistry * @notice Registry for all tokenized assets with metadata */ contract TokenRegistry is AccessControl, Pausable { bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE"); struct TokenMetadata { address tokenAddress; string tokenId; // Fabric token ID string underlyingAsset; // EUR, USD, etc. address issuer; string backingReserve; // Reserve ID or address uint256 totalSupply; uint256 backedAmount; // Amount backed by reserves TokenStatus status; uint256 createdAt; uint256 updatedAt; } enum TokenStatus { PENDING, ACTIVE, SUSPENDED, REDEEMED } mapping(address => TokenMetadata) public tokens; mapping(string => address) public tokenIdToAddress; // Fabric tokenId -> Besu address address[] public registeredTokens; event TokenRegistered( address indexed tokenAddress, string indexed tokenId, string underlyingAsset, address indexed issuer ); event TokenUpdated( address indexed tokenAddress, TokenStatus oldStatus, TokenStatus newStatus ); event TokenSuspended(address indexed tokenAddress, string reason); event TokenActivated(address indexed tokenAddress); error TokenNotFound(); error TokenAlreadyRegistered(); error InvalidStatus(); error InvalidBacking(); constructor(address admin) { _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(REGISTRAR_ROLE, admin); } /** * @notice Register a tokenized asset * @param tokenAddress ERC-20 token address * @param tokenId Fabric token ID * @param underlyingAsset Underlying asset type (EUR, USD, etc.) * @param issuer Issuer address * @param backingReserve Reserve identifier */ function registerToken( address tokenAddress, string calldata tokenId, string calldata underlyingAsset, address issuer, string calldata backingReserve ) external onlyRole(REGISTRAR_ROLE) { if (tokens[tokenAddress].tokenAddress != address(0)) { revert TokenAlreadyRegistered(); } tokens[tokenAddress] = TokenMetadata({ tokenAddress: tokenAddress, tokenId: tokenId, underlyingAsset: underlyingAsset, issuer: issuer, backingReserve: backingReserve, totalSupply: 0, backedAmount: 0, status: TokenStatus.PENDING, createdAt: block.timestamp, updatedAt: block.timestamp }); tokenIdToAddress[tokenId] = tokenAddress; registeredTokens.push(tokenAddress); emit TokenRegistered(tokenAddress, tokenId, underlyingAsset, issuer); } /** * @notice Update token status * @param tokenAddress Token address * @param newStatus New status */ function updateTokenStatus( address tokenAddress, TokenStatus newStatus ) external onlyRole(REGISTRAR_ROLE) { TokenMetadata storage token = tokens[tokenAddress]; if (token.tokenAddress == address(0)) revert TokenNotFound(); TokenStatus oldStatus = token.status; token.status = newStatus; token.updatedAt = block.timestamp; emit TokenUpdated(tokenAddress, oldStatus, newStatus); } /** * @notice Update token supply and backing * @param tokenAddress Token address * @param totalSupply Current total supply * @param backedAmount Amount backed by reserves */ function updateTokenBacking( address tokenAddress, uint256 totalSupply, uint256 backedAmount ) external onlyRole(REGISTRAR_ROLE) { TokenMetadata storage token = tokens[tokenAddress]; if (token.tokenAddress == address(0)) revert TokenNotFound(); // Verify 1:1 backing if (totalSupply > backedAmount) { revert InvalidBacking(); } token.totalSupply = totalSupply; token.backedAmount = backedAmount; token.updatedAt = block.timestamp; } /** * @notice Suspend a token * @param tokenAddress Token address * @param reason Reason for suspension */ function suspendToken( address tokenAddress, string calldata reason ) external onlyRole(REGISTRAR_ROLE) { TokenMetadata storage token = tokens[tokenAddress]; if (token.tokenAddress == address(0)) revert TokenNotFound(); token.status = TokenStatus.SUSPENDED; token.updatedAt = block.timestamp; emit TokenSuspended(tokenAddress, reason); } /** * @notice Activate a suspended token * @param tokenAddress Token address */ function activateToken(address tokenAddress) external onlyRole(REGISTRAR_ROLE) { TokenMetadata storage token = tokens[tokenAddress]; if (token.tokenAddress == address(0)) revert TokenNotFound(); token.status = TokenStatus.ACTIVE; token.updatedAt = block.timestamp; emit TokenActivated(tokenAddress); } /** * @notice Get token metadata * @param tokenAddress Token address * @return Token metadata */ function getToken(address tokenAddress) external view returns (TokenMetadata memory) { return tokens[tokenAddress]; } /** * @notice Get token address by Fabric token ID * @param tokenId Fabric token ID * @return Token address */ function getTokenByFabricId(string calldata tokenId) external view returns (address) { return tokenIdToAddress[tokenId]; } /** * @notice Get all registered tokens * @return Array of token addresses */ function getAllTokens() external view returns (address[] memory) { return registeredTokens; } /** * @notice Check if token is active * @param tokenAddress Token address * @return True if active */ function isTokenActive(address tokenAddress) external view returns (bool) { TokenMetadata memory token = tokens[tokenAddress]; return token.status == TokenStatus.ACTIVE; } /** * @notice Pause registry */ function pause() external onlyRole(DEFAULT_ADMIN_ROLE) { _pause(); } /** * @notice Unpause registry */ function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { _unpause(); } }