// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/AccessControl.sol"; import "./interfaces/IDebtRegistry.sol"; import "./errors/RegistryErrors.sol"; /** * @title DebtRegistry * @notice Manages liens (encumbrances) on accounts for debt/liability enforcement * @dev Supports multiple liens per account with aggregation. Uses hard expiry policy - expiry is informational and requires explicit release. * Liens are used by eMoneyToken to enforce transfer restrictions (hard freeze or encumbered modes). */ contract DebtRegistry is IDebtRegistry, AccessControl { bytes32 public constant DEBT_AUTHORITY_ROLE = keccak256("DEBT_AUTHORITY_ROLE"); uint256 private _nextLienId; mapping(uint256 => Lien) private _liens; mapping(address => uint256) private _activeEncumbrance; mapping(address => uint256) private _activeLienCount; /** * @notice Initializes the DebtRegistry with an admin address * @param admin Address that will receive DEFAULT_ADMIN_ROLE */ constructor(address admin) { _grantRole(DEFAULT_ADMIN_ROLE, admin); } /** * @notice Returns the total active encumbrance (sum of all active lien amounts) for a debtor * @param debtor Address to check * @return Total amount encumbered across all active liens */ function activeLienAmount(address debtor) external view override returns (uint256) { return _activeEncumbrance[debtor]; } /** * @notice Returns whether a debtor has any active liens * @param debtor Address to check * @return true if debtor has at least one active lien, false otherwise */ function hasActiveLien(address debtor) external view override returns (bool) { return _activeLienCount[debtor] > 0; } /** * @notice Returns the number of active liens for a debtor * @param debtor Address to check * @return Count of active liens */ function activeLienCount(address debtor) external view override returns (uint256) { return _activeLienCount[debtor]; } /** * @notice Returns full lien information for a given lien ID * @param lienId The lien identifier * @return Lien struct containing all lien details */ function getLien(uint256 lienId) external view override returns (Lien memory) { return _liens[lienId]; } /** * @notice Places a new lien on a debtor account * @dev Requires DEBT_AUTHORITY_ROLE. Increments active encumbrance and lien count. * @param debtor Address to place lien on * @param amount Amount to encumber * @param expiry Expiry timestamp (0 = no expiry). Note: expiry is informational; explicit release required. * @param priority Priority level (0-255) * @param reasonCode Reason code for the lien (e.g., ReasonCodes.LIEN_BLOCK) * @return lienId The assigned lien identifier */ function placeLien( address debtor, uint256 amount, uint64 expiry, uint8 priority, bytes32 reasonCode ) external override onlyRole(DEBT_AUTHORITY_ROLE) returns (uint256 lienId) { if (debtor == address(0)) revert DebtZeroDebtor(); if (amount == 0) revert DebtZeroAmount(); lienId = _nextLienId++; _liens[lienId] = Lien({ debtor: debtor, amount: amount, expiry: expiry, priority: priority, authority: msg.sender, reasonCode: reasonCode, active: true }); _activeEncumbrance[debtor] += amount; _activeLienCount[debtor]++; emit LienPlaced(lienId, debtor, amount, expiry, priority, msg.sender, reasonCode); } /** * @notice Reduces the amount of an active lien * @dev Requires DEBT_AUTHORITY_ROLE. Updates active encumbrance accordingly. * @param lienId The lien identifier * @param reduceBy Amount to reduce the lien by (must not exceed current lien amount) */ function reduceLien(uint256 lienId, uint256 reduceBy) external override onlyRole(DEBT_AUTHORITY_ROLE) { Lien storage lien = _liens[lienId]; if (!lien.active) revert DebtLienNotActive(lienId); uint256 oldAmount = lien.amount; if (reduceBy > oldAmount) revert DebtReduceByExceedsAmount(lienId, reduceBy, oldAmount); uint256 newAmount = oldAmount - reduceBy; lien.amount = newAmount; _activeEncumbrance[lien.debtor] -= reduceBy; emit LienReduced(lienId, reduceBy, newAmount); } /** * @notice Releases an active lien, removing it from active tracking * @dev Requires DEBT_AUTHORITY_ROLE. Decrements active encumbrance and lien count. * @param lienId The lien identifier to release */ function releaseLien(uint256 lienId) external override onlyRole(DEBT_AUTHORITY_ROLE) { Lien storage lien = _liens[lienId]; if (!lien.active) revert DebtLienNotActive(lienId); lien.active = false; _activeEncumbrance[lien.debtor] -= lien.amount; _activeLienCount[lien.debtor]--; emit LienReleased(lienId); } }