// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "../../vendor/openzeppelin/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "../../emoney/interfaces/IeMoneyToken.sol"; /** * @title DebtToken * @notice Token representing outstanding debt obligation (dToken equivalent) * @dev Minted when borrow occurs, burned on repayment * Interest-accruing, non-freely transferable * Extends eMoneyToken pattern */ contract DebtToken is Initializable, ERC20Upgradeable, AccessControlUpgradeable, UUPSUpgradeable { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); address public vault; address public currency; // eMoney currency address uint8 private _decimalsStorage; bool private _transferable; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } /** * @notice Initialize the debt token (5-arg for backward compatibility; decimals=18, not transferable) */ function initialize( string memory name, string memory symbol, address vault_, address currency_, address admin ) external initializer { _initializeFull(name, symbol, vault_, currency_, admin, 18, false); } /** * @notice Initialize with decimals and transferability (ERC-20; optionally DEX-transferable) * @param decimals_ Token decimals (e.g. 6 for stablecoins; 0 = use 18) * @param transferable_ If true, full ERC-20 transfers allowed (DEX-ready) */ function initializeFull( string memory name, string memory symbol, address vault_, address currency_, address admin, uint8 decimals_, bool transferable_ ) external initializer { _initializeFull(name, symbol, vault_, currency_, admin, decimals_ > 0 ? decimals_ : 18, transferable_); } function _initializeFull( string memory name, string memory symbol, address vault_, address currency_, address admin, uint8 decimals_, bool transferable_ ) internal { __ERC20_init(name, symbol); __AccessControl_init(); __UUPSUpgradeable_init(); vault = vault_; currency = currency_; _decimalsStorage = decimals_; _transferable = transferable_; _grantRole(DEFAULT_ADMIN_ROLE, admin); } /** * @notice Returns token decimals (matches underlying for DEX compatibility) */ function decimals() public view override returns (uint8) { return _decimalsStorage; } function isTransferable() external view returns (bool) { return _transferable; } /** * @notice Mint debt tokens (only by vault) * @param to Recipient address * @param amount Amount to mint */ function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { _mint(to, amount); } /** * @notice Burn debt tokens (only by vault) * @param from Source address * @param amount Amount to burn */ function burn(address from, uint256 amount) external onlyRole(BURNER_ROLE) { _burn(from, amount); } /** * @notice Override transfer: when transferable, full ERC-20; else mint/burn/vault only */ function _update(address from, address to, uint256 amount) internal virtual override { if (from == address(0) || to == address(0)) { super._update(from, to, amount); return; } if (_transferable) { super._update(from, to, amount); return; } if (from == vault || to == vault) { super._update(from, to, amount); return; } revert("DebtToken: transfers not allowed"); } /** * @notice Authorize upgrade (UUPS) */ function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {} }