// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; import "../interfaces/ICompliance.sol"; /** * @title Compliance * @dev Handles KYC whitelisting, sanctions screening, and AML velocity limits */ contract Compliance is ICompliance, AccessControl, Pausable { bytes32 public constant KYC_MANAGER_ROLE = keccak256("KYC_MANAGER_ROLE"); bytes32 public constant SANCTIONS_MANAGER_ROLE = keccak256("SANCTIONS_MANAGER_ROLE"); bytes32 public constant AML_MANAGER_ROLE = keccak256("AML_MANAGER_ROLE"); // KYC storage mapping(address => KYCRecord) public kycRecords; mapping(address => bool) public sanctionedAddresses; mapping(address => VelocityLimits) public velocityLimits; // Global settings uint256 public defaultDailyLimit = 10000 * 10**18; // 10,000 tokens default uint256 public defaultMonthlyLimit = 100000 * 10**18; // 100,000 tokens default uint256 public ctrThreshold = 10000 * 10**18; // Currency Transaction Report threshold uint256 public sarThreshold = 5000 * 10**18; // Suspicious Activity Report threshold uint256 public sanctionsListHash; // Hash of current sanctions list // KYC levels and their associated limits mapping(uint256 => uint256) public kycLevelDailyLimits; mapping(uint256 => uint256) public kycLevelMonthlyLimits; constructor(address admin) { require(admin != address(0), "Invalid admin address"); _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(KYC_MANAGER_ROLE, admin); _grantRole(SANCTIONS_MANAGER_ROLE, admin); _grantRole(AML_MANAGER_ROLE, admin); // Set default KYC level limits kycLevelDailyLimits[1] = 1000 * 10**18; // Basic: 1,000 kycLevelDailyLimits[2] = 10000 * 10**18; // Enhanced: 10,000 kycLevelDailyLimits[3] = 100000 * 10**18; // Institutional: 100,000 kycLevelMonthlyLimits[1] = 10000 * 10**18; // Basic: 10,000 kycLevelMonthlyLimits[2] = 100000 * 10**18; // Enhanced: 100,000 kycLevelMonthlyLimits[3] = 1000000 * 10**18; // Institutional: 1,000,000 } /** * @dev Add address to KYC whitelist * @param account Address to whitelist * @param kycLevel KYC level (1=basic, 2=enhanced, 3=institutional) * @param jurisdiction KYC jurisdiction (e.g., "US", "UK", "EU") * @param expiryTime When KYC expires (0 for no expiry) */ function whitelistAddress( address account, uint256 kycLevel, string memory jurisdiction, uint256 expiryTime ) external onlyRole(KYC_MANAGER_ROLE) { require(account != address(0), "Invalid address"); require(kycLevel >= 1 && kycLevel <= 3, "Invalid KYC level"); require(expiryTime == 0 || expiryTime > block.timestamp, "Invalid expiry time"); kycRecords[account] = KYCRecord({ isWhitelisted: true, kycLevel: kycLevel, whitelistTime: block.timestamp, expiryTime: expiryTime, jurisdiction: jurisdiction }); // Set velocity limits based on KYC level _setVelocityLimits( account, kycLevelDailyLimits[kycLevel], kycLevelMonthlyLimits[kycLevel] ); emit AccountWhitelisted(account, kycLevel, jurisdiction); } /** * @dev Remove address from KYC whitelist * @param account Address to remove * @param reason Reason for removal */ function blacklistAddress( address account, string memory reason ) external onlyRole(KYC_MANAGER_ROLE) { require(account != address(0), "Invalid address"); kycRecords[account].isWhitelisted = false; emit AccountBlacklisted(account, reason); } /** * @dev Add address to sanctions list * @param account Address to sanction */ function addToSanctionsList(address account) external onlyRole(SANCTIONS_MANAGER_ROLE) { require(account != address(0), "Invalid address"); sanctionedAddresses[account] = true; } /** * @dev Remove address from sanctions list * @param account Address to remove from sanctions */ function removeFromSanctionsList(address account) external onlyRole(SANCTIONS_MANAGER_ROLE) { sanctionedAddresses[account] = false; } /** * @dev Batch update sanctions list * @param accounts Array of addresses to update * @param sanctioned Array of sanctions status (true/false) * @param newListHash Hash of the new sanctions list for verification */ function batchUpdateSanctions( address[] memory accounts, bool[] memory sanctioned, uint256 newListHash ) external onlyRole(SANCTIONS_MANAGER_ROLE) { require(accounts.length == sanctioned.length, "Array length mismatch"); require(newListHash != 0, "Invalid list hash"); for (uint256 i = 0; i < accounts.length; i++) { sanctionedAddresses[accounts[i]] = sanctioned[i]; } sanctionsListHash = newListHash; emit SanctionsListUpdated(newListHash); } /** * @dev Set custom velocity limits for address * @param account Address to set limits for * @param dailyLimit Daily transaction limit * @param monthlyLimit Monthly transaction limit */ function setVelocityLimits( address account, uint256 dailyLimit, uint256 monthlyLimit ) external onlyRole(AML_MANAGER_ROLE) { _setVelocityLimits(account, dailyLimit, monthlyLimit); } /** * @dev Internal function to set velocity limits */ function _setVelocityLimits( address account, uint256 dailyLimit, uint256 monthlyLimit ) internal { require(account != address(0), "Invalid address"); require(dailyLimit > 0 && monthlyLimit > 0, "Limits must be positive"); require(monthlyLimit >= dailyLimit, "Monthly limit must be >= daily limit"); VelocityLimits storage limits = velocityLimits[account]; uint256 today = block.timestamp / 1 days; uint256 thisMonth = block.timestamp / 30 days; // Reset counters if this is first time setting limits if (limits.dailyLimit == 0) { limits.dailySpent = 0; limits.monthlySpent = 0; limits.lastDayReset = today; limits.lastMonthReset = thisMonth; } limits.dailyLimit = dailyLimit; limits.monthlyLimit = monthlyLimit; emit VelocityLimitsUpdated(account, dailyLimit, monthlyLimit); } /** * @dev Check if address is whitelisted */ function isWhitelisted(address account) external view override returns (bool) { KYCRecord memory record = kycRecords[account]; if (!record.isWhitelisted) { return false; } // Check expiry if (record.expiryTime != 0 && record.expiryTime <= block.timestamp) { return false; } return true; } /** * @dev Check if address is sanctioned */ function isSanctioned(address account) external view override returns (bool) { return sanctionedAddresses[account]; } /** * @dev Check velocity limits for transaction */ function checkVelocityLimits(address account, uint256 amount) external view override returns (bool) { VelocityLimits memory limits = velocityLimits[account]; // If no limits set, use defaults based on KYC level if (limits.dailyLimit == 0) { KYCRecord memory kycRecord = kycRecords[account]; if (kycRecord.kycLevel == 0) { return amount <= defaultDailyLimit; } return amount <= kycLevelDailyLimits[kycRecord.kycLevel]; } uint256 today = block.timestamp / 1 days; uint256 thisMonth = block.timestamp / 30 days; // Reset daily counter if needed uint256 dailySpent = limits.dailySpent; if (today > limits.lastDayReset) { dailySpent = 0; } // Reset monthly counter if needed uint256 monthlySpent = limits.monthlySpent; if (thisMonth > limits.lastMonthReset) { monthlySpent = 0; } // Check both daily and monthly limits return (dailySpent + amount <= limits.dailyLimit) && (monthlySpent + amount <= limits.monthlyLimit); } /** * @dev Update velocity tracking after transaction */ function updateVelocityTracking(address account, uint256 amount) external override { // Only allow calls from authorized token contracts require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Unauthorized velocity update"); VelocityLimits storage limits = velocityLimits[account]; // Initialize if needed if (limits.dailyLimit == 0) { KYCRecord memory kycRecord = kycRecords[account]; if (kycRecord.kycLevel > 0) { _setVelocityLimits( account, kycLevelDailyLimits[kycRecord.kycLevel], kycLevelMonthlyLimits[kycRecord.kycLevel] ); } else { _setVelocityLimits(account, defaultDailyLimit, defaultMonthlyLimit); } } uint256 today = block.timestamp / 1 days; uint256 thisMonth = block.timestamp / 30 days; // Reset daily counter if new day if (today > limits.lastDayReset) { limits.dailySpent = 0; limits.lastDayReset = today; } // Reset monthly counter if new month if (thisMonth > limits.lastMonthReset) { limits.monthlySpent = 0; limits.lastMonthReset = thisMonth; } // Update spending limits.dailySpent += amount; limits.monthlySpent += amount; } /** * @dev Check if transaction should trigger AML reporting */ function checkReportingThresholds( address from, address to, uint256 amount ) external view override returns (bool shouldReport, uint8 reportType) { // CTR - Currency Transaction Report (large transactions) if (amount >= ctrThreshold) { return (true, 1); } // SAR - Suspicious Activity Report (pattern analysis would go here) if (amount >= sarThreshold) { // In practice, this would include more sophisticated pattern analysis // For now, just check if it's a large transaction to/from new addresses KYCRecord memory fromRecord = kycRecords[from]; KYCRecord memory toRecord = kycRecords[to]; if (fromRecord.whitelistTime == 0 || toRecord.whitelistTime == 0) { return (true, 2); } // Check if accounts were recently whitelisted (< 7 days) if (block.timestamp - fromRecord.whitelistTime < 7 days || block.timestamp - toRecord.whitelistTime < 7 days) { return (true, 2); } } return (false, 0); } /** * @dev Get KYC record for address */ function getKYCRecord(address account) external view override returns (KYCRecord memory) { return kycRecords[account]; } /** * @dev Get velocity limits for address */ function getVelocityLimits(address account) external view override returns (VelocityLimits memory) { return velocityLimits[account]; } /** * @dev Update reporting thresholds */ function updateReportingThresholds( uint256 newCtrThreshold, uint256 newSarThreshold ) external onlyRole(AML_MANAGER_ROLE) { require(newCtrThreshold > 0, "CTR threshold must be positive"); require(newSarThreshold > 0, "SAR threshold must be positive"); ctrThreshold = newCtrThreshold; sarThreshold = newSarThreshold; } /** * @dev Update KYC level limits */ function updateKYCLevelLimits( uint256 kycLevel, uint256 dailyLimit, uint256 monthlyLimit ) external onlyRole(DEFAULT_ADMIN_ROLE) { require(kycLevel >= 1 && kycLevel <= 3, "Invalid KYC level"); require(dailyLimit > 0 && monthlyLimit > 0, "Limits must be positive"); require(monthlyLimit >= dailyLimit, "Monthly must be >= daily"); kycLevelDailyLimits[kycLevel] = dailyLimit; kycLevelMonthlyLimits[kycLevel] = monthlyLimit; } /** * @dev Pause compliance operations */ function pause() external onlyRole(DEFAULT_ADMIN_ROLE) { _pause(); } /** * @dev Unpause compliance operations */ function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { _unpause(); } }