// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "./interfaces/IRegulatedEntityRegistry.sol"; /** * @title RegulatedEntityRegistry * @notice Registry for tracking regulated financial entities eligible for vault operations * @dev Separate from eMoney ComplianceRegistry (used for transfers) */ contract RegulatedEntityRegistry is IRegulatedEntityRegistry, AccessControl { bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE"); bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); struct Entity { bool registered; bool suspended; bytes32 jurisdictionHash; address[] authorizedWallets; mapping(address => bool) isAuthorizedWallet; mapping(address => bool) isOperator; uint256 registeredAt; } mapping(address => Entity) private _entities; constructor(address admin) { _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(REGISTRAR_ROLE, admin); } /** * @notice Register a regulated entity * @param entity Entity address * @param jurisdictionHash Hash of jurisdiction identifier * @param authorizedWallets Initial authorized wallets */ function registerEntity( address entity, bytes32 jurisdictionHash, address[] calldata authorizedWallets ) external onlyRole(REGISTRAR_ROLE) { require(entity != address(0), "RegulatedEntityRegistry: zero address"); require(!_entities[entity].registered, "RegulatedEntityRegistry: already registered"); Entity storage entityData = _entities[entity]; entityData.registered = true; entityData.jurisdictionHash = jurisdictionHash; entityData.registeredAt = block.timestamp; for (uint256 i = 0; i < authorizedWallets.length; i++) { require(authorizedWallets[i] != address(0), "RegulatedEntityRegistry: zero wallet"); entityData.authorizedWallets.push(authorizedWallets[i]); entityData.isAuthorizedWallet[authorizedWallets[i]] = true; } emit EntityRegistered(entity, jurisdictionHash, block.timestamp); } /** * @notice Check if an entity is registered and eligible * @param entity Entity address * @return isEligible True if entity is registered and not suspended */ function isEligible(address entity) external view override returns (bool) { Entity storage entityData = _entities[entity]; return entityData.registered && !entityData.suspended; } /** * @notice Check if a wallet is authorized for an entity * @param entity Entity address * @param wallet Wallet address * @return isAuthorized True if wallet is authorized */ function isAuthorized(address entity, address wallet) external view override returns (bool) { return _entities[entity].isAuthorizedWallet[wallet]; } /** * @notice Check if an address is an operator for an entity * @param entity Entity address * @param operator Operator address * @return isOperator True if address is an operator */ function isOperator(address entity, address operator) external view override returns (bool) { return _entities[entity].isOperator[operator]; } /** * @notice Add authorized wallet to an entity * @param entity Entity address * @param wallet Wallet address to authorize */ function addAuthorizedWallet(address entity, address wallet) external override { require(_entities[entity].registered, "RegulatedEntityRegistry: entity not registered"); require( hasRole(REGISTRAR_ROLE, msg.sender) || _entities[entity].isAuthorizedWallet[msg.sender] || _entities[entity].isOperator[msg.sender], "RegulatedEntityRegistry: not authorized" ); require(wallet != address(0), "RegulatedEntityRegistry: zero wallet"); require(!_entities[entity].isAuthorizedWallet[wallet], "RegulatedEntityRegistry: already authorized"); _entities[entity].authorizedWallets.push(wallet); _entities[entity].isAuthorizedWallet[wallet] = true; emit AuthorizedWalletAdded(entity, wallet); } /** * @notice Remove authorized wallet from an entity * @param entity Entity address * @param wallet Wallet address to remove */ function removeAuthorizedWallet(address entity, address wallet) external override { require(_entities[entity].registered, "RegulatedEntityRegistry: entity not registered"); require( hasRole(REGISTRAR_ROLE, msg.sender) || _entities[entity].isAuthorizedWallet[msg.sender] || _entities[entity].isOperator[msg.sender], "RegulatedEntityRegistry: not authorized" ); require(_entities[entity].isAuthorizedWallet[wallet], "RegulatedEntityRegistry: not authorized"); _entities[entity].isAuthorizedWallet[wallet] = false; // Remove from array address[] storage wallets = _entities[entity].authorizedWallets; for (uint256 i = 0; i < wallets.length; i++) { if (wallets[i] == wallet) { wallets[i] = wallets[wallets.length - 1]; wallets.pop(); break; } } emit AuthorizedWalletRemoved(entity, wallet); } /** * @notice Set operator status for an entity * @param entity Entity address * @param operator Operator address * @param status True to grant operator status, false to revoke */ function setOperator(address entity, address operator, bool status) external override { require(_entities[entity].registered, "RegulatedEntityRegistry: entity not registered"); require( hasRole(REGISTRAR_ROLE, msg.sender) || _entities[entity].isAuthorizedWallet[msg.sender], "RegulatedEntityRegistry: not authorized" ); require(operator != address(0), "RegulatedEntityRegistry: zero operator"); _entities[entity].isOperator[operator] = status; emit OperatorSet(entity, operator, status); } /** * @notice Suspend an entity * @param entity Entity address */ function suspendEntity(address entity) external onlyRole(REGISTRAR_ROLE) { require(_entities[entity].registered, "RegulatedEntityRegistry: entity not registered"); require(!_entities[entity].suspended, "RegulatedEntityRegistry: already suspended"); _entities[entity].suspended = true; emit EntitySuspended(entity, block.timestamp); } /** * @notice Unsuspend an entity * @param entity Entity address */ function unsuspendEntity(address entity) external onlyRole(REGISTRAR_ROLE) { require(_entities[entity].registered, "RegulatedEntityRegistry: entity not registered"); require(_entities[entity].suspended, "RegulatedEntityRegistry: not suspended"); _entities[entity].suspended = false; emit EntityUnsuspended(entity, block.timestamp); } /** * @notice Get entity information * @param entity Entity address * @return registered True if entity is registered * @return suspended True if entity is suspended * @return jurisdictionHash Jurisdiction hash * @return authorizedWallets List of authorized wallets */ function getEntity(address entity) external view override returns ( bool registered, bool suspended, bytes32 jurisdictionHash, address[] memory authorizedWallets ) { Entity storage entityData = _entities[entity]; return ( entityData.registered, entityData.suspended, entityData.jurisdictionHash, entityData.authorizedWallets ); } }