// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "../interop/BridgeEscrowVault.sol"; import "../../iso4217w/interfaces/IISO4217WToken.sol"; import "../../iso4217w/ComplianceGuard.sol"; /** * @title WTokenComplianceEnforcer * @notice Enforces W token compliance rules on bridge operations * @dev Ensures money multiplier = 1.0 and GRU isolation on bridge */ contract WTokenComplianceEnforcer is AccessControl { bytes32 public constant ENFORCER_ROLE = keccak256("ENFORCER_ROLE"); bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); BridgeEscrowVault public bridgeEscrowVault; ComplianceGuard public complianceGuard; mapping(address => bool) public enabledTokens; // W token => enabled event TokenEnabled(address indexed token, bool enabled); event ComplianceChecked( address indexed token, bytes32 reasonCode, bool compliant ); error ComplianceViolation(bytes32 reasonCode); error TokenNotEnabled(); error InvalidToken(); constructor( address admin, address bridgeEscrowVault_, address complianceGuard_ ) { _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(ENFORCER_ROLE, admin); _grantRole(OPERATOR_ROLE, admin); require(bridgeEscrowVault_ != address(0), "WTokenComplianceEnforcer: zero bridge"); require(complianceGuard_ != address(0), "WTokenComplianceEnforcer: zero guard"); bridgeEscrowVault = BridgeEscrowVault(bridgeEscrowVault_); complianceGuard = ComplianceGuard(complianceGuard_); } /** * @notice Check compliance before bridge operation * @param token W token address * @param bridgeAmount Amount to bridge * @return compliant True if compliant */ function checkComplianceBeforeBridge( address token, uint256 bridgeAmount ) external returns (bool compliant) { if (!enabledTokens[token]) revert TokenNotEnabled(); IISO4217WToken wToken = IISO4217WToken(token); string memory currencyCode = wToken.currencyCode(); uint256 verifiedReserve = wToken.verifiedReserve(); uint256 currentSupply = wToken.totalSupply(); uint256 newSupply = currentSupply - bridgeAmount; // Supply after bridge // Validate mint operation (simulating future state) (bool isValid, bytes32 reasonCode) = complianceGuard.validateMint( currencyCode, bridgeAmount, newSupply, verifiedReserve ); if (!isValid) { emit ComplianceChecked(token, reasonCode, false); revert ComplianceViolation(reasonCode); } // Validate money multiplier = 1.0 if (!complianceGuard.validateMoneyMultiplier(verifiedReserve, newSupply)) { bytes32 multiplierReason = keccak256("MONEY_MULTIPLIER_VIOLATION"); emit ComplianceChecked(token, multiplierReason, false); revert ComplianceViolation(multiplierReason); } // Validate GRU isolation if (complianceGuard.violatesGRUIsolation(currencyCode)) { bytes32 gruReason = keccak256("GRU_ISOLATION_VIOLATION"); emit ComplianceChecked(token, gruReason, false); revert ComplianceViolation(gruReason); } emit ComplianceChecked(token, bytes32(0), true); compliant = true; } /** * @notice Check compliance on destination chain (before minting bridged amount) * @param currencyCode ISO-4217 currency code * @param bridgeAmount Amount to mint on destination * @param destinationReserve Reserve on destination chain * @param destinationSupply Supply on destination chain (before mint) * @return compliant True if compliant */ function checkDestinationCompliance( string memory currencyCode, uint256 bridgeAmount, uint256 destinationReserve, uint256 destinationSupply ) external returns (bool compliant) { uint256 newSupply = destinationSupply + bridgeAmount; // Validate mint operation (bool isValid, bytes32 reasonCode) = complianceGuard.validateMint( currencyCode, bridgeAmount, destinationSupply, // Current supply destinationReserve ); if (!isValid) { emit ComplianceChecked(address(0), reasonCode, false); revert ComplianceViolation(reasonCode); } // Validate money multiplier = 1.0 after mint if (!complianceGuard.validateMoneyMultiplier(destinationReserve, newSupply)) { bytes32 multiplierReason = keccak256("MONEY_MULTIPLIER_VIOLATION"); emit ComplianceChecked(address(0), multiplierReason, false); revert ComplianceViolation(multiplierReason); } // Validate GRU isolation if (complianceGuard.violatesGRUIsolation(currencyCode)) { bytes32 gruReason = keccak256("GRU_ISOLATION_VIOLATION"); emit ComplianceChecked(address(0), gruReason, false); revert ComplianceViolation(gruReason); } emit ComplianceChecked(address(0), bytes32(0), true); compliant = true; } /** * @notice Enable a W token for compliance checking * @param token W token address */ function enableToken(address token) external onlyRole(OPERATOR_ROLE) { require(token != address(0), "WTokenComplianceEnforcer: zero token"); // Verify it's a valid W token try IISO4217WToken(token).currencyCode() returns (string memory) { enabledTokens[token] = true; emit TokenEnabled(token, true); } catch { revert InvalidToken(); } } /** * @notice Disable a W token * @param token W token address */ function disableToken(address token) external onlyRole(OPERATOR_ROLE) { enabledTokens[token] = false; emit TokenEnabled(token, false); } /** * @notice Check if token is enabled */ function isTokenEnabled(address token) external view returns (bool) { return enabledTokens[token]; } }