// 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 "../vendor/openzeppelin/ReentrancyGuardUpgradeable.sol"; import "../interfaces/IRegulatedAssetMetadata.sol"; /** * @title UniversalAssetRegistry * @notice Central registry for all asset types with governance and compliance * @dev Supports 10+ asset types with hybrid governance based on risk levels */ contract UniversalAssetRegistry is Initializable, AccessControlUpgradeable, ReentrancyGuardUpgradeable, UUPSUpgradeable { bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE"); bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE"); bytes32 public constant VALIDATOR_ROLE = keccak256("VALIDATOR_ROLE"); bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); bytes32 public constant JURISDICTION_MANAGER_ROLE = keccak256("JURISDICTION_MANAGER_ROLE"); bytes32 public constant REGULATOR_ROLE = keccak256("REGULATOR_ROLE"); bytes32 public constant SUPERVISOR_ROLE = keccak256("SUPERVISOR_ROLE"); bytes32 public constant EMERGENCY_ADMIN_ROLE = keccak256("EMERGENCY_ADMIN_ROLE"); // Asset classification types enum AssetType { ERC20Standard, // Standard tokens ISO4217W, // eMoney/CBDCs GRU, // Global Reserve Units (M00/M0/M1) Commodity, // Gold, oil, etc. Security, // Tokenized securities RealWorldAsset, // Real estate, art, etc. Synthetic, // Derivatives, futures Stablecoin, // USDT, USDC, etc. GovernanceToken, // DAO tokens NFTBacked // NFT-collateralized tokens } // Compliance levels enum ComplianceLevel { Public, // No restrictions KYC, // KYC required Accredited, // Accredited investors only Institutional, // Institutions only Sovereign // Central banks/governments only } // Proposal types enum ProposalType { AddAsset, RemoveAsset, UpdateRiskParams, UpdateCompliance, EmergencyPause } struct UniversalAsset { address tokenAddress; AssetType assetType; ComplianceLevel complianceLevel; // Metadata string name; string symbol; uint8 decimals; string jurisdiction; // Risk parameters uint8 volatilityScore; // 0-100 uint256 minBridgeAmount; uint256 maxBridgeAmount; uint256 dailyVolumeLimit; // PMM liquidity address pmmPool; bool hasLiquidity; uint256 liquidityReserveUSD; // Governance bool requiresGovernance; address[] validators; uint256 validationThreshold; // Status bool isActive; uint256 registeredAt; uint256 lastUpdated; // Governance, storage, and supervision bytes32 assetId; bytes32 assetVersionId; bytes32 governanceProfileId; bytes32 supervisionProfileId; bytes32 storageNamespace; address canonicalUnderlyingAsset; bool isWrappedTransport; bool supervisionRequired; bool governmentApprovalRequired; uint256 minimumUpgradeNoticePeriod; string regulatoryDisclosureURI; string reportingURI; } struct PendingAssetProposal { bytes32 proposalId; ProposalType proposalType; address proposer; uint256 proposedAt; uint256 executeAfter; uint256 votesFor; uint256 votesAgainst; bool executed; bool cancelled; // Proposal data address tokenAddress; AssetType assetType; ComplianceLevel complianceLevel; string name; string symbol; uint8 decimals; string jurisdiction; uint8 volatilityScore; uint256 minBridgeAmount; uint256 maxBridgeAmount; } struct JurisdictionProfile { bool active; bool requiresGovernmentApproval; bool requiresPeriodicReports; uint256 minimumUpgradeNoticePeriod; string supervisionURI; bytes32 policyHash; } struct JurisdictionAuthority { bool active; bool canApproveGovernance; bool canApproveUpgrades; bool canPause; bool canReceiveReports; } // Storage mapping(address => UniversalAsset) public assets; mapping(AssetType => address[]) public assetsByType; mapping(bytes32 => PendingAssetProposal) public proposals; mapping(bytes32 => mapping(address => bool)) public hasVoted; // Governance parameters uint256 public constant TIMELOCK_STANDARD = 1 days; uint256 public constant TIMELOCK_MODERATE = 3 days; uint256 public constant TIMELOCK_HIGH = 7 days; uint256 public quorumPercentage; // Validator set address[] public validators; mapping(address => bool) public isValidator; address public governanceController; mapping(bytes32 => JurisdictionProfile) private _jurisdictionProfiles; mapping(bytes32 => mapping(address => JurisdictionAuthority)) private _jurisdictionAuthorities; mapping(address => bytes32) private _assetJurisdictionIds; // Events event AssetProposed( bytes32 indexed proposalId, address indexed token, AssetType assetType, address proposer ); event AssetApproved( address indexed token, AssetType assetType, ComplianceLevel complianceLevel ); event AssetRemoved(address indexed token, AssetType assetType); event ValidatorAdded(address indexed validator); event ValidatorRemoved(address indexed validator); event ProposalVoted( bytes32 indexed proposalId, address indexed voter, bool support ); event ProposalExecuted(bytes32 indexed proposalId); event ProposalCancelled(bytes32 indexed proposalId); event JurisdictionProfileUpdated( bytes32 indexed jurisdictionId, string jurisdiction, bool active, bool requiresGovernmentApproval, bool requiresPeriodicReports, uint256 minimumUpgradeNoticePeriod, string supervisionURI, bytes32 policyHash ); event JurisdictionAuthorityUpdated( bytes32 indexed jurisdictionId, string jurisdiction, address indexed authority, bool active, bool canApproveGovernance, bool canApproveUpgrades, bool canPause, bool canReceiveReports ); event AssetGovernanceProfileUpdated( address indexed token, bytes32 governanceProfileId, bytes32 supervisionProfileId, bytes32 storageNamespace ); event AssetSupervisionProfileUpdated( address indexed token, address canonicalUnderlyingAsset, bool isWrappedTransport, bool supervisionRequired, bool governmentApprovalRequired, uint256 minimumUpgradeNoticePeriod ); error GovernanceControllerOnly(address caller); error GovernanceControllerNotConfigured(); error ZeroGovernanceController(); modifier onlyGovernanceExecution() { if (governanceController == address(0)) revert GovernanceControllerNotConfigured(); if (_msgSender() != governanceController) revert GovernanceControllerOnly(_msgSender()); _; } /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize(address admin) external initializer { __AccessControl_init(); __ReentrancyGuard_init(); __UUPSUpgradeable_init(); _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(REGISTRAR_ROLE, admin); _grantRole(PROPOSER_ROLE, admin); _grantRole(VALIDATOR_ROLE, admin); _grantRole(UPGRADER_ROLE, admin); _grantRole(JURISDICTION_MANAGER_ROLE, admin); _grantRole(REGULATOR_ROLE, admin); _grantRole(SUPERVISOR_ROLE, admin); _grantRole(EMERGENCY_ADMIN_ROLE, admin); quorumPercentage = 51; // 51% quorum } function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {} /** * @notice Propose new asset with timelock governance */ function proposeAsset( address tokenAddress, AssetType assetType, ComplianceLevel complianceLevel, string calldata name, string calldata symbol, uint8 decimals, string calldata jurisdiction, uint8 volatilityScore, uint256 minBridge, uint256 maxBridge ) external onlyRole(PROPOSER_ROLE) returns (bytes32 proposalId) { require(tokenAddress != address(0), "Zero address"); require(!assets[tokenAddress].isActive, "Already registered"); require(volatilityScore <= 100, "Invalid volatility"); require(maxBridge >= minBridge, "Invalid limits"); proposalId = keccak256(abi.encode( tokenAddress, assetType, block.timestamp, msg.sender )); uint256 timelockPeriod = _getTimelockPeriod(assetType, complianceLevel); PendingAssetProposal storage proposal = proposals[proposalId]; proposal.proposalId = proposalId; proposal.proposalType = ProposalType.AddAsset; proposal.proposer = msg.sender; proposal.proposedAt = block.timestamp; proposal.executeAfter = block.timestamp + timelockPeriod; proposal.tokenAddress = tokenAddress; proposal.assetType = assetType; proposal.complianceLevel = complianceLevel; proposal.name = name; proposal.symbol = symbol; proposal.decimals = decimals; proposal.jurisdiction = jurisdiction; proposal.volatilityScore = volatilityScore; proposal.minBridgeAmount = minBridge; proposal.maxBridgeAmount = maxBridge; emit AssetProposed(proposalId, tokenAddress, assetType, msg.sender); return proposalId; } /** * @notice Vote on asset proposal */ function voteOnProposal( bytes32 proposalId, bool support ) external onlyRole(VALIDATOR_ROLE) { PendingAssetProposal storage proposal = proposals[proposalId]; require(!proposal.executed, "Already executed"); require(!proposal.cancelled, "Cancelled"); require(!hasVoted[proposalId][msg.sender], "Already voted"); hasVoted[proposalId][msg.sender] = true; if (support) { proposal.votesFor++; } else { proposal.votesAgainst++; } emit ProposalVoted(proposalId, msg.sender, support); } /** * @notice Execute proposal after timelock */ function executeProposal(bytes32 proposalId) external nonReentrant { PendingAssetProposal storage proposal = proposals[proposalId]; require(!proposal.executed, "Already executed"); require(!proposal.cancelled, "Cancelled"); require(block.timestamp >= proposal.executeAfter, "Timelock active"); // Check quorum uint256 totalVotes = proposal.votesFor + proposal.votesAgainst; uint256 requiredVotes = (validators.length * quorumPercentage) / 100; require(totalVotes >= requiredVotes, "Quorum not met"); require(proposal.votesFor > proposal.votesAgainst, "Rejected"); // Execute based on proposal type if (proposal.proposalType == ProposalType.AddAsset) { _registerAsset(proposal); } proposal.executed = true; emit ProposalExecuted(proposalId); } /** * @notice Register a GRU compliant token (c*) directly — no timelock. For use by deploy scripts to integrate all c* with GRU ERC-2535 / bridge. * @dev Only REGISTRAR_ROLE. Registers as AssetType.GRU so GRUCCIPBridge and PoolManager accept the token. */ function registerGRUCompliantAsset( address tokenAddress, string calldata name, string calldata symbol, uint8 decimals, string calldata jurisdiction ) external onlyRole(REGISTRAR_ROLE) { require(tokenAddress != address(0), "Zero address"); require(!assets[tokenAddress].isActive, "Already registered"); _registerAssetDirect( tokenAddress, AssetType.GRU, ComplianceLevel.Institutional, name, symbol, decimals, jurisdiction, 10, // volatilityScore (low for stable) 1e6, // minBridge (1 unit with 6 decimals) 100_000_000 * 10**6 // maxBridge (100M with 6 decimals) ); } /** * @notice Register an M00 Li* RWA index token (LiXAU, LiPMG, LiBMG*) — not c* eMoney. * @dev Only REGISTRAR_ROLE. AssetType.RealWorldAsset for bridge/routing policy separation from GRU/c*. */ function registerRWAIndexAsset( address tokenAddress, string calldata name, string calldata symbol, uint8 decimals, string calldata jurisdiction ) external onlyRole(REGISTRAR_ROLE) { require(tokenAddress != address(0), "Zero address"); require(!assets[tokenAddress].isActive, "Already registered"); _registerAssetDirect( tokenAddress, AssetType.RealWorldAsset, ComplianceLevel.Institutional, name, symbol, decimals, jurisdiction, 50, 1, 100_000_000 * 10 ** 6 ); } /** * @notice Internal asset registration from proposal */ function _registerAsset(PendingAssetProposal storage proposal) internal { _registerAssetDirect( proposal.tokenAddress, proposal.assetType, proposal.complianceLevel, proposal.name, proposal.symbol, proposal.decimals, proposal.jurisdiction, proposal.volatilityScore, proposal.minBridgeAmount, proposal.maxBridgeAmount ); } /** * @notice Internal asset registration (shared by proposal execution and registerGRUCompliantAsset) */ function _registerAssetDirect( address tokenAddress, AssetType assetType, ComplianceLevel complianceLevel, string memory name, string memory symbol, uint8 decimals, string memory jurisdiction, uint8 volatilityScore, uint256 minBridgeAmount, uint256 maxBridgeAmount ) internal { UniversalAsset storage asset = assets[tokenAddress]; asset.tokenAddress = tokenAddress; asset.assetType = assetType; asset.complianceLevel = complianceLevel; asset.name = name; asset.symbol = symbol; asset.decimals = decimals; asset.jurisdiction = jurisdiction; asset.volatilityScore = volatilityScore; asset.minBridgeAmount = minBridgeAmount; asset.maxBridgeAmount = maxBridgeAmount; unchecked { asset.dailyVolumeLimit = maxBridgeAmount > type(uint256).max / 10 ? type(uint256).max : maxBridgeAmount * 10; } asset.requiresGovernance = _requiresGovernance(assetType, complianceLevel); asset.isActive = true; asset.registeredAt = block.timestamp; asset.lastUpdated = block.timestamp; _assetJurisdictionIds[tokenAddress] = jurisdictionIdFor(jurisdiction); _syncAssetMetadata(tokenAddress, asset); _applyJurisdictionDefaults(asset, _assetJurisdictionIds[tokenAddress]); assetsByType[assetType].push(tokenAddress); emit AssetApproved(tokenAddress, assetType, complianceLevel); } /** * @notice Add validator */ function addValidator(address validator) external onlyRole(DEFAULT_ADMIN_ROLE) { require(validator != address(0), "Zero address"); require(!isValidator[validator], "Already validator"); validators.push(validator); isValidator[validator] = true; _grantRole(VALIDATOR_ROLE, validator); emit ValidatorAdded(validator); } /** * @notice Remove validator */ function removeValidator(address validator) external onlyRole(DEFAULT_ADMIN_ROLE) { require(isValidator[validator], "Not validator"); isValidator[validator] = false; _revokeRole(VALIDATOR_ROLE, validator); // Remove from array for (uint256 i = 0; i < validators.length; i++) { if (validators[i] == validator) { validators[i] = validators[validators.length - 1]; validators.pop(); break; } } emit ValidatorRemoved(validator); } /** * @notice Update PMM pool for asset */ function updatePMMPool( address token, address pmmPool ) external onlyRole(REGISTRAR_ROLE) { require(assets[token].isActive, "Not registered"); assets[token].pmmPool = pmmPool; assets[token].hasLiquidity = pmmPool != address(0); assets[token].lastUpdated = block.timestamp; } function setGovernanceController(address governanceController_) external onlyRole(EMERGENCY_ADMIN_ROLE) { if (governanceController_ == address(0)) revert ZeroGovernanceController(); governanceController = governanceController_; } function setJurisdictionProfile( string calldata jurisdiction, bool active, bool requiresGovernmentApproval, bool requiresPeriodicReports, uint256 minimumUpgradeNoticePeriod, string calldata supervisionURI, bytes32 policyHash ) external onlyGovernanceExecution { _setJurisdictionProfileValue( jurisdiction, active, requiresGovernmentApproval, requiresPeriodicReports, minimumUpgradeNoticePeriod, supervisionURI, policyHash ); } function setDerivedJurisdictionProfile( address token, bool active, bool requiresGovernmentApproval, bool requiresPeriodicReports, uint256 minimumUpgradeNoticePeriod, string calldata supervisionURI, bytes32 policyHash ) external onlyGovernanceExecution { require(assets[token].isActive, "Not registered"); _setJurisdictionProfileValue( assets[token].jurisdiction, active, requiresGovernmentApproval, requiresPeriodicReports, minimumUpgradeNoticePeriod, supervisionURI, policyHash ); } function emergencySetJurisdictionProfile( string calldata jurisdiction, bool active, bool requiresGovernmentApproval, bool requiresPeriodicReports, uint256 minimumUpgradeNoticePeriod, string calldata supervisionURI, bytes32 policyHash ) external onlyRole(EMERGENCY_ADMIN_ROLE) { _setJurisdictionProfileValue( jurisdiction, active, requiresGovernmentApproval, requiresPeriodicReports, minimumUpgradeNoticePeriod, supervisionURI, policyHash ); } function _setJurisdictionProfileValue( string memory jurisdiction, bool active, bool requiresGovernmentApproval, bool requiresPeriodicReports, uint256 minimumUpgradeNoticePeriod, string memory supervisionURI, bytes32 policyHash ) internal { bytes32 jurisdictionId = jurisdictionIdFor(jurisdiction); JurisdictionProfile storage profile = _jurisdictionProfiles[jurisdictionId]; profile.active = active; profile.requiresGovernmentApproval = requiresGovernmentApproval; profile.requiresPeriodicReports = requiresPeriodicReports; profile.minimumUpgradeNoticePeriod = minimumUpgradeNoticePeriod; profile.supervisionURI = supervisionURI; profile.policyHash = policyHash; emit JurisdictionProfileUpdated( jurisdictionId, jurisdiction, active, requiresGovernmentApproval, requiresPeriodicReports, minimumUpgradeNoticePeriod, supervisionURI, policyHash ); } function setJurisdictionAuthority( string calldata jurisdiction, address authority, bool active, bool canApproveGovernance, bool canApproveUpgrades, bool canPause, bool canReceiveReports ) external onlyGovernanceExecution { _setJurisdictionAuthorityValue( jurisdiction, authority, active, canApproveGovernance, canApproveUpgrades, canPause, canReceiveReports ); } function setDerivedJurisdictionAuthority( address token, address authority, bool active, bool canApproveGovernance, bool canApproveUpgrades, bool canPause, bool canReceiveReports ) external onlyGovernanceExecution { require(assets[token].isActive, "Not registered"); _setJurisdictionAuthorityValue( assets[token].jurisdiction, authority, active, canApproveGovernance, canApproveUpgrades, canPause, canReceiveReports ); } function emergencySetJurisdictionAuthority( string calldata jurisdiction, address authority, bool active, bool canApproveGovernance, bool canApproveUpgrades, bool canPause, bool canReceiveReports ) external onlyRole(EMERGENCY_ADMIN_ROLE) { _setJurisdictionAuthorityValue( jurisdiction, authority, active, canApproveGovernance, canApproveUpgrades, canPause, canReceiveReports ); } function _setJurisdictionAuthorityValue( string memory jurisdiction, address authority, bool active, bool canApproveGovernance, bool canApproveUpgrades, bool canPause, bool canReceiveReports ) internal { require(authority != address(0), "Zero address"); bytes32 jurisdictionId = jurisdictionIdFor(jurisdiction); _jurisdictionAuthorities[jurisdictionId][authority] = JurisdictionAuthority({ active: active, canApproveGovernance: canApproveGovernance, canApproveUpgrades: canApproveUpgrades, canPause: canPause, canReceiveReports: canReceiveReports }); emit JurisdictionAuthorityUpdated( jurisdictionId, jurisdiction, authority, active, canApproveGovernance, canApproveUpgrades, canPause, canReceiveReports ); } function setAssetGovernanceProfile( address token, bytes32 governanceProfileId, bytes32 supervisionProfileId, bytes32 storageNamespace, address canonicalUnderlyingAsset, bool isWrappedTransport, bool supervisionRequired, bool governmentApprovalRequired, uint256 minimumUpgradeNoticePeriod, string calldata regulatoryDisclosureURI, string calldata reportingURI ) external onlyGovernanceExecution { _setAssetGovernanceProfileValue( token, governanceProfileId, supervisionProfileId, storageNamespace, canonicalUnderlyingAsset, isWrappedTransport, supervisionRequired, governmentApprovalRequired, minimumUpgradeNoticePeriod, regulatoryDisclosureURI, reportingURI ); } function emergencySetAssetGovernanceProfile( address token, bytes32 governanceProfileId, bytes32 supervisionProfileId, bytes32 storageNamespace, address canonicalUnderlyingAsset, bool isWrappedTransport, bool supervisionRequired, bool governmentApprovalRequired, uint256 minimumUpgradeNoticePeriod, string calldata regulatoryDisclosureURI, string calldata reportingURI ) external onlyRole(EMERGENCY_ADMIN_ROLE) { _setAssetGovernanceProfileValue( token, governanceProfileId, supervisionProfileId, storageNamespace, canonicalUnderlyingAsset, isWrappedTransport, supervisionRequired, governmentApprovalRequired, minimumUpgradeNoticePeriod, regulatoryDisclosureURI, reportingURI ); } function _setAssetGovernanceProfileValue( address token, bytes32 governanceProfileId, bytes32 supervisionProfileId, bytes32 storageNamespace, address canonicalUnderlyingAsset, bool isWrappedTransport, bool supervisionRequired, bool governmentApprovalRequired, uint256 minimumUpgradeNoticePeriod, string memory regulatoryDisclosureURI, string memory reportingURI ) internal { require(assets[token].isActive, "Not registered"); UniversalAsset storage asset = assets[token]; asset.governanceProfileId = governanceProfileId; asset.supervisionProfileId = supervisionProfileId; asset.storageNamespace = storageNamespace; asset.canonicalUnderlyingAsset = canonicalUnderlyingAsset; asset.isWrappedTransport = isWrappedTransport; asset.supervisionRequired = supervisionRequired; asset.governmentApprovalRequired = governmentApprovalRequired; asset.minimumUpgradeNoticePeriod = minimumUpgradeNoticePeriod; asset.regulatoryDisclosureURI = regulatoryDisclosureURI; asset.reportingURI = reportingURI; asset.lastUpdated = block.timestamp; emit AssetGovernanceProfileUpdated(token, governanceProfileId, supervisionProfileId, storageNamespace); emit AssetSupervisionProfileUpdated( token, canonicalUnderlyingAsset, isWrappedTransport, supervisionRequired, governmentApprovalRequired, minimumUpgradeNoticePeriod ); } function syncAssetMetadataFromToken(address token) external onlyGovernanceExecution { require(assets[token].isActive, "Not registered"); _syncAssetMetadata(token, assets[token]); assets[token].lastUpdated = block.timestamp; } function emergencySyncAssetMetadataFromToken(address token) external onlyRole(EMERGENCY_ADMIN_ROLE) { require(assets[token].isActive, "Not registered"); _syncAssetMetadata(token, assets[token]); assets[token].lastUpdated = block.timestamp; } /** * @notice Get timelock period based on asset type and compliance */ function _getTimelockPeriod( AssetType assetType, ComplianceLevel complianceLevel ) internal pure returns (uint256) { if (assetType == AssetType.Security || assetType == AssetType.ISO4217W || complianceLevel >= ComplianceLevel.Institutional) { return TIMELOCK_HIGH; } else if (assetType == AssetType.Commodity || assetType == AssetType.RealWorldAsset || complianceLevel >= ComplianceLevel.Accredited) { return TIMELOCK_MODERATE; } else { return TIMELOCK_STANDARD; } } /** * @notice Check if asset type requires governance */ function _requiresGovernance( AssetType assetType, ComplianceLevel complianceLevel ) internal pure returns (bool) { return assetType == AssetType.Security || assetType == AssetType.ISO4217W || assetType == AssetType.Commodity || complianceLevel >= ComplianceLevel.Accredited; } // View functions function getAsset(address token) external view returns (UniversalAsset memory) { return assets[token]; } function getAssetType(address token) external view returns (AssetType) { return assets[token].assetType; } function getAssetsByType(AssetType assetType) external view returns (address[] memory) { return assetsByType[assetType]; } function getValidators() external view returns (address[] memory) { return validators; } function getJurisdictionProfile(string calldata jurisdiction) external view returns (JurisdictionProfile memory) { return _jurisdictionProfiles[jurisdictionIdFor(jurisdiction)]; } function getJurisdictionAuthority( string calldata jurisdiction, address authority ) external view returns (JurisdictionAuthority memory) { return _jurisdictionAuthorities[jurisdictionIdFor(jurisdiction)][authority]; } function getAssetJurisdictionId(address token) external view returns (bytes32) { return _assetJurisdictionIds[token]; } function isGovernanceAuthority(bytes32 jurisdictionId, address authority) external view returns (bool) { JurisdictionAuthority storage permissions = _jurisdictionAuthorities[jurisdictionId][authority]; return permissions.active && permissions.canApproveGovernance; } function getJurisdictionMinimumUpgradeNotice(bytes32 jurisdictionId) external view returns (uint256) { return _jurisdictionProfiles[jurisdictionId].minimumUpgradeNoticePeriod; } function getProposal(bytes32 proposalId) external view returns (PendingAssetProposal memory) { return proposals[proposalId]; } function isAssetActive(address token) external view returns (bool) { return assets[token].isActive; } function jurisdictionIdFor(string memory jurisdiction) public pure returns (bytes32) { return keccak256(bytes(jurisdiction)); } function _applyJurisdictionDefaults(UniversalAsset storage asset, bytes32 jurisdictionId) internal { JurisdictionProfile storage profile = _jurisdictionProfiles[jurisdictionId]; if (!profile.active) { return; } asset.supervisionRequired = true; asset.governmentApprovalRequired = profile.requiresGovernmentApproval; if (profile.minimumUpgradeNoticePeriod > asset.minimumUpgradeNoticePeriod) { asset.minimumUpgradeNoticePeriod = profile.minimumUpgradeNoticePeriod; } } function _syncAssetMetadata(address tokenAddress, UniversalAsset storage asset) internal { asset.assetId = _defaultAssetId(asset.symbol); asset.assetVersionId = _defaultAssetVersionId(asset.symbol); asset.governanceProfileId = _defaultGovernanceProfileId(asset.symbol); asset.supervisionProfileId = _defaultSupervisionProfileId(asset.symbol); asset.storageNamespace = _defaultStorageNamespace(asset.symbol); try IRegulatedAssetMetadata(tokenAddress).assetId() returns (bytes32 value) { asset.assetId = value; } catch {} try IRegulatedAssetMetadata(tokenAddress).assetVersionId() returns (bytes32 value) { asset.assetVersionId = value; } catch {} try IRegulatedAssetMetadata(tokenAddress).governanceProfileId() returns (bytes32 value) { asset.governanceProfileId = value; } catch {} try IRegulatedAssetMetadata(tokenAddress).supervisionProfileId() returns (bytes32 value) { asset.supervisionProfileId = value; } catch {} try IRegulatedAssetMetadata(tokenAddress).storageNamespace() returns (bytes32 value) { asset.storageNamespace = value; } catch {} try IRegulatedAssetMetadata(tokenAddress).primaryJurisdiction() returns (string memory jurisdictionValue) { if (bytes(jurisdictionValue).length > 0) { asset.jurisdiction = jurisdictionValue; _assetJurisdictionIds[tokenAddress] = jurisdictionIdFor(jurisdictionValue); } } catch {} try IRegulatedAssetMetadata(tokenAddress).regulatoryDisclosureURI() returns (string memory value) { asset.regulatoryDisclosureURI = value; } catch {} try IRegulatedAssetMetadata(tokenAddress).reportingURI() returns (string memory value) { asset.reportingURI = value; } catch {} try IRegulatedAssetMetadata(tokenAddress).canonicalUnderlyingAsset() returns (address value) { asset.canonicalUnderlyingAsset = value; } catch {} try IRegulatedAssetMetadata(tokenAddress).supervisionRequired() returns (bool value) { asset.supervisionRequired = value; } catch {} try IRegulatedAssetMetadata(tokenAddress).governmentApprovalRequired() returns (bool value) { asset.governmentApprovalRequired = value; } catch {} try IRegulatedAssetMetadata(tokenAddress).minimumUpgradeNoticePeriod() returns (uint256 value) { asset.minimumUpgradeNoticePeriod = value; } catch {} try IRegulatedAssetMetadata(tokenAddress).wrappedTransport() returns (bool value) { asset.isWrappedTransport = value; } catch {} } function _defaultAssetId(string memory symbol) internal pure returns (bytes32) { return keccak256(bytes(string.concat("GRU:", symbol))); } function _defaultAssetVersionId(string memory symbol) internal pure returns (bytes32) { return keccak256(bytes(string.concat("GRU:", symbol, ":registry-v1"))); } function _defaultGovernanceProfileId(string memory symbol) internal pure returns (bytes32) { return keccak256(bytes(string.concat("GRU:GOV:", symbol))); } function _defaultSupervisionProfileId(string memory symbol) internal pure returns (bytes32) { return keccak256(bytes(string.concat("GRU:SUP:", symbol))); } function _defaultStorageNamespace(string memory symbol) internal pure returns (bytes32) { return keccak256(bytes(string.concat("gru.storage.registry.", symbol))); } }