977 lines
33 KiB
Solidity
977 lines
33 KiB
Solidity
// 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 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;
|
|
asset.dailyVolumeLimit = 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)));
|
|
}
|
|
}
|