291 lines
12 KiB
Solidity
291 lines
12 KiB
Solidity
// 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_
|
|
);
|
|
}
|
|
}
|