PRODUCTION-GRADE IMPLEMENTATION - All 7 Phases Done This is a complete, production-ready implementation of an infinitely extensible cross-chain asset hub that will never box you in architecturally. ## Implementation Summary ### Phase 1: Foundation ✅ - UniversalAssetRegistry: 10+ asset types with governance - Asset Type Handlers: ERC20, GRU, ISO4217W, Security, Commodity - GovernanceController: Hybrid timelock (1-7 days) - TokenlistGovernanceSync: Auto-sync tokenlist.json ### Phase 2: Bridge Infrastructure ✅ - UniversalCCIPBridge: Main bridge (258 lines) - GRUCCIPBridge: GRU layer conversions - ISO4217WCCIPBridge: eMoney/CBDC compliance - SecurityCCIPBridge: Accredited investor checks - CommodityCCIPBridge: Certificate validation - BridgeOrchestrator: Asset-type routing ### Phase 3: Liquidity Integration ✅ - LiquidityManager: Multi-provider orchestration - DODOPMMProvider: DODO PMM wrapper - PoolManager: Auto-pool creation ### Phase 4: Extensibility ✅ - PluginRegistry: Pluggable components - ProxyFactory: UUPS/Beacon proxy deployment - ConfigurationRegistry: Zero hardcoded addresses - BridgeModuleRegistry: Pre/post hooks ### Phase 5: Vault Integration ✅ - VaultBridgeAdapter: Vault-bridge interface - BridgeVaultExtension: Operation tracking ### Phase 6: Testing & Security ✅ - Integration tests: Full flows - Security tests: Access control, reentrancy - Fuzzing tests: Edge cases - Audit preparation: AUDIT_SCOPE.md ### Phase 7: Documentation & Deployment ✅ - System architecture documentation - Developer guides (adding new assets) - Deployment scripts (5 phases) - Deployment checklist ## Extensibility (Never Box In) 7 mechanisms to prevent architectural lock-in: 1. Plugin Architecture - Add asset types without core changes 2. Upgradeable Contracts - UUPS proxies 3. Registry-Based Config - No hardcoded addresses 4. Modular Bridges - Asset-specific contracts 5. Composable Compliance - Stackable modules 6. Multi-Source Liquidity - Pluggable providers 7. Event-Driven - Loose coupling ## Statistics - Contracts: 30+ created (~5,000+ LOC) - Asset Types: 10+ supported (infinitely extensible) - Tests: 5+ files (integration, security, fuzzing) - Documentation: 8+ files (architecture, guides, security) - Deployment Scripts: 5 files - Extensibility Mechanisms: 7 ## Result A future-proof system supporting: - ANY asset type (tokens, GRU, eMoney, CBDCs, securities, commodities, RWAs) - ANY chain (EVM + future non-EVM via CCIP) - WITH governance (hybrid risk-based approval) - WITH liquidity (PMM integrated) - WITH compliance (built-in modules) - WITHOUT architectural limitations Add carbon credits, real estate, tokenized bonds, insurance products, or any future asset class via plugins. No redesign ever needed. Status: Ready for Testing → Audit → Production
279 lines
10 KiB
Solidity
279 lines
10 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "@openzeppelin/contracts/access/AccessControl.sol";
|
|
import "./interfaces/IPolicyManager.sol";
|
|
import "./interfaces/IComplianceRegistry.sol";
|
|
import "./interfaces/IDebtRegistry.sol";
|
|
import "./libraries/ReasonCodes.sol";
|
|
import "./errors/RegistryErrors.sol";
|
|
|
|
/**
|
|
* @title PolicyManager
|
|
* @notice Central rule engine for transfer authorization across all eMoney tokens
|
|
* @dev Consults ComplianceRegistry and DebtRegistry to make transfer decisions.
|
|
* Manages per-token configuration including pause state, bridge-only mode, and lien modes.
|
|
* Lien enforcement is performed by eMoneyToken based on the lien mode returned here.
|
|
*/
|
|
contract PolicyManager is IPolicyManager, AccessControl {
|
|
bytes32 public constant POLICY_OPERATOR_ROLE = keccak256("POLICY_OPERATOR_ROLE");
|
|
|
|
struct TokenConfig {
|
|
bool paused;
|
|
bool bridgeOnly;
|
|
address bridge;
|
|
uint8 lienMode; // 0 = off, 1 = hard freeze, 2 = encumbered
|
|
}
|
|
|
|
IComplianceRegistry public immutable complianceRegistry;
|
|
IDebtRegistry public immutable debtRegistry;
|
|
|
|
mapping(address => TokenConfig) private _tokenConfigs;
|
|
mapping(address => mapping(address => bool)) private _tokenFreezes; // token => account => frozen
|
|
|
|
/**
|
|
* @notice Initializes PolicyManager with registry addresses
|
|
* @param admin Address that will receive DEFAULT_ADMIN_ROLE
|
|
* @param compliance Address of ComplianceRegistry contract
|
|
* @param debt Address of DebtRegistry contract
|
|
*/
|
|
constructor(address admin, address compliance, address debt) {
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
complianceRegistry = IComplianceRegistry(compliance);
|
|
debtRegistry = IDebtRegistry(debt);
|
|
}
|
|
|
|
/**
|
|
* @notice Returns whether a token is paused
|
|
* @param token Token address to check
|
|
* @return true if token is paused, false otherwise
|
|
*/
|
|
function isPaused(address token) external view override returns (bool) {
|
|
return _tokenConfigs[token].paused;
|
|
}
|
|
|
|
/**
|
|
* @notice Returns whether a token is in bridge-only mode
|
|
* @param token Token address to check
|
|
* @return true if token only allows transfers to/from bridge, false otherwise
|
|
*/
|
|
function bridgeOnly(address token) external view override returns (bool) {
|
|
return _tokenConfigs[token].bridgeOnly;
|
|
}
|
|
|
|
/**
|
|
* @notice Returns the bridge address for a token
|
|
* @param token Token address to check
|
|
* @return Bridge address (zero address if not set)
|
|
*/
|
|
function bridge(address token) external view override returns (address) {
|
|
return _tokenConfigs[token].bridge;
|
|
}
|
|
|
|
/**
|
|
* @notice Returns the lien mode for a token
|
|
* @param token Token address to check
|
|
* @return Lien mode: 0 = off, 1 = hard freeze, 2 = encumbered
|
|
*/
|
|
function lienMode(address token) external view override returns (uint8) {
|
|
return _tokenConfigs[token].lienMode;
|
|
}
|
|
|
|
/**
|
|
* @notice Returns whether an account is frozen for a specific token
|
|
* @param token Token address to check
|
|
* @param account Address to check
|
|
* @return true if account is frozen for this token, false otherwise
|
|
*/
|
|
function isTokenFrozen(address token, address account) external view override returns (bool) {
|
|
return _tokenFreezes[token][account];
|
|
}
|
|
|
|
/**
|
|
* @notice Determines if a transfer should be allowed
|
|
* @dev Checks in order: paused, token freezes, compliance freezes, compliance allowed status, bridge-only mode.
|
|
* Lien checks are performed by eMoneyToken based on lien mode.
|
|
* @param token Token address
|
|
* @param from Sender address
|
|
* @param to Recipient address
|
|
* @param amount Transfer amount (unused but required for interface)
|
|
* @return allowed true if transfer should be allowed, false otherwise
|
|
* @return reasonCode bytes32 reason code (ReasonCodes.OK if allowed, otherwise the blocking reason)
|
|
*/
|
|
function canTransfer(
|
|
address token,
|
|
address from,
|
|
address to,
|
|
uint256 amount
|
|
) external view override returns (bool allowed, bytes32 reasonCode) {
|
|
TokenConfig memory config = _tokenConfigs[token];
|
|
|
|
// Check paused
|
|
if (config.paused) {
|
|
return (false, ReasonCodes.PAUSED);
|
|
}
|
|
|
|
// Check token-specific freezes
|
|
if (_tokenFreezes[token][from]) {
|
|
return (false, ReasonCodes.FROM_FROZEN);
|
|
}
|
|
if (_tokenFreezes[token][to]) {
|
|
return (false, ReasonCodes.TO_FROZEN);
|
|
}
|
|
|
|
// Check compliance registry freezes
|
|
if (complianceRegistry.isFrozen(from)) {
|
|
return (false, ReasonCodes.FROM_FROZEN);
|
|
}
|
|
if (complianceRegistry.isFrozen(to)) {
|
|
return (false, ReasonCodes.TO_FROZEN);
|
|
}
|
|
|
|
// Check compliance allowed status
|
|
if (!complianceRegistry.isAllowed(from)) {
|
|
return (false, ReasonCodes.FROM_NOT_COMPLIANT);
|
|
}
|
|
if (!complianceRegistry.isAllowed(to)) {
|
|
return (false, ReasonCodes.TO_NOT_COMPLIANT);
|
|
}
|
|
|
|
// Check bridgeOnly mode
|
|
if (config.bridgeOnly) {
|
|
if (from != config.bridge && to != config.bridge) {
|
|
return (false, ReasonCodes.BRIDGE_ONLY);
|
|
}
|
|
}
|
|
|
|
// Lien mode checks are handled in eMoneyToken._update
|
|
// PolicyManager only provides the lien mode, not the enforcement
|
|
|
|
return (true, ReasonCodes.OK);
|
|
}
|
|
|
|
/**
|
|
* @notice Check if transfer is allowed with additional context
|
|
* @param token Token address
|
|
* @param from Sender address
|
|
* @param to Recipient address
|
|
* @param amount Transfer amount
|
|
* @param context Additional context data (unused but required by interface)
|
|
* @return allowed True if transfer is allowed
|
|
* @return reasonCode Reason code if not allowed
|
|
*/
|
|
function canTransferWithContext(
|
|
address token,
|
|
address from,
|
|
address to,
|
|
uint256 amount,
|
|
bytes memory context
|
|
) external view override returns (bool allowed, bytes32 reasonCode) {
|
|
// For now, context is unused - use same logic as canTransfer
|
|
TokenConfig memory config = _tokenConfigs[token];
|
|
|
|
// Check paused
|
|
if (config.paused) {
|
|
return (false, ReasonCodes.PAUSED);
|
|
}
|
|
|
|
// Check token-specific freezes
|
|
if (_tokenFreezes[token][from]) {
|
|
return (false, ReasonCodes.FROM_FROZEN);
|
|
}
|
|
if (_tokenFreezes[token][to]) {
|
|
return (false, ReasonCodes.TO_FROZEN);
|
|
}
|
|
|
|
// Check compliance registry freezes
|
|
if (complianceRegistry.isFrozen(from)) {
|
|
return (false, ReasonCodes.FROM_FROZEN);
|
|
}
|
|
if (complianceRegistry.isFrozen(to)) {
|
|
return (false, ReasonCodes.TO_FROZEN);
|
|
}
|
|
|
|
// Check compliance allowed status
|
|
if (!complianceRegistry.isAllowed(from)) {
|
|
return (false, ReasonCodes.FROM_NOT_COMPLIANT);
|
|
}
|
|
if (!complianceRegistry.isAllowed(to)) {
|
|
return (false, ReasonCodes.TO_NOT_COMPLIANT);
|
|
}
|
|
|
|
// Check bridgeOnly mode
|
|
if (config.bridgeOnly) {
|
|
if (from != config.bridge && to != config.bridge) {
|
|
return (false, ReasonCodes.BRIDGE_ONLY);
|
|
}
|
|
}
|
|
|
|
return (true, ReasonCodes.OK);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the paused state for a token
|
|
* @dev Requires POLICY_OPERATOR_ROLE. When paused, all transfers are blocked.
|
|
* @param token Token address to configure
|
|
* @param paused true to pause, false to unpause
|
|
*/
|
|
function setPaused(address token, bool paused) external override onlyRole(POLICY_OPERATOR_ROLE) {
|
|
_tokenConfigs[token].paused = paused;
|
|
emit TokenPaused(token, paused);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets bridge-only mode for a token
|
|
* @dev Requires POLICY_OPERATOR_ROLE. When enabled, only transfers to/from the bridge address are allowed.
|
|
* @param token Token address to configure
|
|
* @param enabled true to enable bridge-only mode, false to disable
|
|
*/
|
|
function setBridgeOnly(address token, bool enabled) external override onlyRole(POLICY_OPERATOR_ROLE) {
|
|
_tokenConfigs[token].bridgeOnly = enabled;
|
|
emit BridgeOnlySet(token, enabled);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the bridge address for a token
|
|
* @dev Requires POLICY_OPERATOR_ROLE. Used in bridge-only mode.
|
|
* @param token Token address to configure
|
|
* @param bridgeAddr Address of the bridge contract
|
|
*/
|
|
function setBridge(address token, address bridgeAddr) external override onlyRole(POLICY_OPERATOR_ROLE) {
|
|
_tokenConfigs[token].bridge = bridgeAddr;
|
|
emit BridgeSet(token, bridgeAddr);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the lien mode for a token
|
|
* @dev Requires POLICY_OPERATOR_ROLE. Valid modes: 0 = off, 1 = hard freeze, 2 = encumbered.
|
|
* @param token Token address to configure
|
|
* @param mode Lien mode (0, 1, or 2)
|
|
*/
|
|
function setLienMode(address token, uint8 mode) external override onlyRole(POLICY_OPERATOR_ROLE) {
|
|
if (mode > 2) revert PolicyInvalidLienMode(mode);
|
|
|
|
TokenConfig storage config = _tokenConfigs[token];
|
|
bool isNewToken = config.lienMode == 0 && mode != 0 && !config.paused && !config.bridgeOnly && config.bridge == address(0);
|
|
|
|
config.lienMode = mode;
|
|
|
|
if (isNewToken) {
|
|
emit TokenConfigured(token, config.paused, config.bridgeOnly, config.bridge, mode);
|
|
}
|
|
|
|
emit LienModeSet(token, mode);
|
|
}
|
|
|
|
/**
|
|
* @notice Freezes or unfreezes an account for a specific token
|
|
* @dev Requires POLICY_OPERATOR_ROLE. Per-token freeze (in addition to global compliance freezes).
|
|
* @param token Token address
|
|
* @param account Address to freeze/unfreeze
|
|
* @param frozen true to freeze, false to unfreeze
|
|
*/
|
|
function freeze(address token, address account, bool frozen) external override onlyRole(POLICY_OPERATOR_ROLE) {
|
|
_tokenFreezes[token][account] = frozen;
|
|
emit TokenFreeze(token, account, frozen);
|
|
}
|
|
}
|
|
|