// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "../interfaces/IRegulatedAssetMetadata.sol"; /** * @title CompliantWrappedToken * @notice ERC-20 wrapper for bridged compliant tokens (cWUSDT, cWUSDC, etc.) on public chains. * @dev Only MINTER_ROLE can mint (e.g. CCIP receiver/bridge); BURNER_ROLE can burn (bridge-back). * Admin can grant/revoke roles. Used for cW* representation of Chain 138 compliant tokens. */ contract CompliantWrappedToken is ERC20, AccessControl, IRegulatedAssetMetadata { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); bytes32 public constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE"); bytes32 public constant JURISDICTION_ADMIN_ROLE = keccak256("JURISDICTION_ADMIN_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"); uint8 private immutable _decimalsStorage; bool public operationalRolesFrozen; address public governanceController; bytes32 public immutable assetId; bytes32 public immutable assetVersionId; bytes32 public governanceProfileId; bytes32 public supervisionProfileId; bytes32 public storageNamespace; string public primaryJurisdiction; string private _regulatoryDisclosureURI; string private _reportingURI; address public canonicalUnderlyingAsset; bool public supervisionRequired; bool public governmentApprovalRequired; uint256 public minimumUpgradeNoticePeriod; event OperationalRolesFrozen(address indexed caller); event GovernanceProfileUpdated(bytes32 governanceProfileId); event SupervisionProfileUpdated(bytes32 supervisionProfileId); event StorageNamespaceUpdated(bytes32 storageNamespace); event PrimaryJurisdictionUpdated(string jurisdiction); event RegulatoryDisclosureURIUpdated(string disclosureURI); event ReportingURIUpdated(string reportingURI); event CanonicalUnderlyingAssetUpdated(address canonicalUnderlyingAsset); event SupervisionConfigurationUpdated( bool supervisionRequired, bool governmentApprovalRequired, uint256 minimumUpgradeNoticePeriod ); event RegulatoryApprovalRecorded(bytes32 indexed approvalId, string actionType, bytes32 referenceHash); event SupervisoryNoticeRecorded(bytes32 indexed noticeId, string category, string uri); error OperationalRolesAreFrozen(); error GovernanceControllerOnly(address caller); error GovernanceControllerNotConfigured(); error ZeroGovernanceController(); modifier onlyGovernanceExecution() { if (governanceController == address(0)) revert GovernanceControllerNotConfigured(); if (msg.sender != governanceController) revert GovernanceControllerOnly(msg.sender); _; } constructor( string memory name_, string memory symbol_, uint8 decimals_, address admin_ ) ERC20(name_, symbol_) { _decimalsStorage = decimals_; assetId = keccak256(bytes(string.concat("GRU:", symbol_))); assetVersionId = keccak256(bytes(string.concat("GRU:", symbol_, ":transport"))); governanceProfileId = keccak256(bytes(string.concat("GRU:GOV:", symbol_, ":transport"))); supervisionProfileId = keccak256(bytes(string.concat("GRU:SUP:", symbol_, ":transport"))); storageNamespace = keccak256(bytes(string.concat("gru.storage.transport.", symbol_))); primaryJurisdiction = "Destination Public Network"; supervisionRequired = true; minimumUpgradeNoticePeriod = 7 days; _grantRole(DEFAULT_ADMIN_ROLE, admin_); _grantRole(MINTER_ROLE, admin_); _grantRole(BURNER_ROLE, admin_); _grantRole(GOVERNANCE_ROLE, admin_); _grantRole(JURISDICTION_ADMIN_ROLE, admin_); _grantRole(REGULATOR_ROLE, admin_); _grantRole(SUPERVISOR_ROLE, admin_); _grantRole(EMERGENCY_ADMIN_ROLE, admin_); } function decimals() public view override returns (uint8) { return _decimalsStorage; } function wrappedTransport() external pure returns (bool) { return true; } function regulatoryDisclosureURI() external view returns (string memory) { return _regulatoryDisclosureURI; } function reportingURI() external view returns (string memory) { return _reportingURI; } /** * @notice Permanently freeze future MINTER_ROLE / BURNER_ROLE changes. * @dev Existing bridge roles keep working; only admin churn is disabled. */ function freezeOperationalRoles() external onlyRole(DEFAULT_ADMIN_ROLE) { if (operationalRolesFrozen) { return; } operationalRolesFrozen = true; emit OperationalRolesFrozen(msg.sender); } function grantRole(bytes32 role, address account) public override onlyRole(getRoleAdmin(role)) { _revertIfFrozenOperationalRole(role); super.grantRole(role, account); } function revokeRole(bytes32 role, address account) public override onlyRole(getRoleAdmin(role)) { _revertIfFrozenOperationalRole(role); super.revokeRole(role, account); } function setGovernanceController(address governanceController_) external onlyRole(EMERGENCY_ADMIN_ROLE) { if (governanceController_ == address(0)) revert ZeroGovernanceController(); governanceController = governanceController_; } function setGovernanceProfileId(bytes32 governanceProfileId_) external onlyGovernanceExecution { _setGovernanceProfileIdValue(governanceProfileId_); } function setSupervisionProfileId(bytes32 supervisionProfileId_) external onlyGovernanceExecution { _setSupervisionProfileIdValue(supervisionProfileId_); } function setStorageNamespace(bytes32 storageNamespace_) external onlyGovernanceExecution { _setStorageNamespaceValue(storageNamespace_); } function setPrimaryJurisdiction(string calldata jurisdiction_) external onlyGovernanceExecution { _setPrimaryJurisdictionValue(jurisdiction_); } function setRegulatoryDisclosureURI(string calldata disclosureURI_) external onlyGovernanceExecution { _setRegulatoryDisclosureURIValue(disclosureURI_); } function setReportingURI(string calldata reportingURI_) external onlyGovernanceExecution { _setReportingURIValue(reportingURI_); } function setCanonicalUnderlyingAsset(address canonicalUnderlyingAsset_) external onlyGovernanceExecution { _setCanonicalUnderlyingAssetValue(canonicalUnderlyingAsset_); } function setSupervisionConfiguration( bool supervisionRequired_, bool governmentApprovalRequired_, uint256 minimumUpgradeNoticePeriod_ ) external onlyGovernanceExecution { _setSupervisionConfigurationValue( supervisionRequired_, governmentApprovalRequired_, minimumUpgradeNoticePeriod_ ); } function emergencySetGovernanceMetadata( bytes32 governanceProfileId_, bytes32 supervisionProfileId_, bytes32 storageNamespace_, string calldata jurisdiction_, address canonicalUnderlyingAsset_, bool supervisionRequired_, bool governmentApprovalRequired_, uint256 minimumUpgradeNoticePeriod_ ) external onlyRole(EMERGENCY_ADMIN_ROLE) { _setGovernanceProfileIdValue(governanceProfileId_); _setSupervisionProfileIdValue(supervisionProfileId_); _setStorageNamespaceValue(storageNamespace_); _setPrimaryJurisdictionValue(jurisdiction_); _setCanonicalUnderlyingAssetValue(canonicalUnderlyingAsset_); _setSupervisionConfigurationValue( supervisionRequired_, governmentApprovalRequired_, minimumUpgradeNoticePeriod_ ); } function emergencySetDisclosureMetadata( string calldata disclosureURI_, string calldata reportingURI_ ) external onlyRole(EMERGENCY_ADMIN_ROLE) { _setRegulatoryDisclosureURIValue(disclosureURI_); _setReportingURIValue(reportingURI_); } function recordRegulatoryApproval( bytes32 approvalId, string calldata actionType, bytes32 referenceHash ) external onlyRole(REGULATOR_ROLE) { emit RegulatoryApprovalRecorded(approvalId, actionType, referenceHash); } function recordSupervisoryNotice( bytes32 noticeId, string calldata category, string calldata uri ) external onlyRole(SUPERVISOR_ROLE) { emit SupervisoryNoticeRecorded(noticeId, category, uri); } /// @notice Mint to account (only MINTER_ROLE, e.g. bridge receiver). function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { _mint(to, amount); } /// @notice Burn from account (only BURNER_ROLE, e.g. bridge for burn-on-exit). function burn(address from, uint256 amount) external onlyRole(BURNER_ROLE) { _burn(from, amount); } /// @notice Burn from account (only BURNER_ROLE). Enables TwoWayTokenBridgeL2 and other bridges that call burnFrom(user, amount) for outbound. function burnFrom(address from, uint256 amount) external onlyRole(BURNER_ROLE) { _burn(from, amount); } function _revertIfFrozenOperationalRole(bytes32 role) internal view { if (operationalRolesFrozen && (role == MINTER_ROLE || role == BURNER_ROLE)) { revert OperationalRolesAreFrozen(); } } function _setGovernanceProfileIdValue(bytes32 governanceProfileId_) internal { governanceProfileId = governanceProfileId_; emit GovernanceProfileUpdated(governanceProfileId_); } function _setSupervisionProfileIdValue(bytes32 supervisionProfileId_) internal { supervisionProfileId = supervisionProfileId_; emit SupervisionProfileUpdated(supervisionProfileId_); } function _setStorageNamespaceValue(bytes32 storageNamespace_) internal { storageNamespace = storageNamespace_; emit StorageNamespaceUpdated(storageNamespace_); } function _setPrimaryJurisdictionValue(string memory jurisdiction_) internal { primaryJurisdiction = jurisdiction_; emit PrimaryJurisdictionUpdated(jurisdiction_); } function _setRegulatoryDisclosureURIValue(string memory disclosureURI_) internal { _regulatoryDisclosureURI = disclosureURI_; emit RegulatoryDisclosureURIUpdated(disclosureURI_); } function _setReportingURIValue(string memory reportingURI_) internal { _reportingURI = reportingURI_; emit ReportingURIUpdated(reportingURI_); } function _setCanonicalUnderlyingAssetValue(address canonicalUnderlyingAsset_) internal { canonicalUnderlyingAsset = canonicalUnderlyingAsset_; emit CanonicalUnderlyingAssetUpdated(canonicalUnderlyingAsset_); } function _setSupervisionConfigurationValue( bool supervisionRequired_, bool governmentApprovalRequired_, uint256 minimumUpgradeNoticePeriod_ ) internal { supervisionRequired = supervisionRequired_; governmentApprovalRequired = governmentApprovalRequired_; minimumUpgradeNoticePeriod = minimumUpgradeNoticePeriod_; emit SupervisionConfigurationUpdated( supervisionRequired_, governmentApprovalRequired_, minimumUpgradeNoticePeriod_ ); } }