// 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 "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "./interfaces/IISO4217WToken.sol"; import "./libraries/ISO4217WCompliance.sol"; /** * @title ISO4217WToken * @notice ISO-4217 W token (e.g., USDW, EURW, GBPW) - M1 eMoney token * @dev Represents 1:1 redeemable digital claim on fiat currency * * COMPLIANCE: * - Classification: M1 eMoney * - Legal Tender: NO * - Synthetic / Reserve Unit: NO * - Commodity-Backed: NO * - Money Multiplier: m = 1.0 (hard-fixed, no fractional reserve) * - Backing: 1:1 with fiat currency in segregated custodial accounts * - GRU Isolation: Direct/indirect GRU conversion prohibited */ contract ISO4217WToken is IISO4217WToken, Initializable, ERC20Upgradeable, AccessControlUpgradeable, UUPSUpgradeable, ReentrancyGuardUpgradeable { using ISO4217WCompliance for *; bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); bytes32 public constant RESERVE_UPDATE_ROLE = keccak256("RESERVE_UPDATE_ROLE"); string private _currencyCode; // ISO-4217 code (e.g., "USD") uint8 private _decimals; // Token decimals (typically 2 for fiat) uint256 private _verifiedReserve; // Verified reserve balance in base currency units address private _custodian; address private _mintController; address private _burnController; address private _complianceGuard; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } /** * @notice Initialize the ISO-4217 W token * @param name Token name (e.g., "USDW Token") * @param symbol Token symbol (e.g., "USDW") * @param currencyCode_ ISO-4217 currency code (e.g., "USD") * @param decimals_ Token decimals (typically 2 for fiat) * @param custodian_ Custodian address * @param mintController_ Mint controller address * @param burnController_ Burn controller address * @param complianceGuard_ Compliance guard address * @param admin Admin address */ function initialize( string memory name, string memory symbol, string memory currencyCode_, uint8 decimals_, address custodian_, address mintController_, address burnController_, address complianceGuard_, address admin ) external initializer { __ERC20_init(name, symbol); __AccessControl_init(); __UUPSUpgradeable_init(); __ReentrancyGuard_init(); // Validate ISO-4217 format require( ISO4217WCompliance.isValidISO4217Format(currencyCode_), "ISO4217WToken: invalid ISO-4217 format" ); // Validate token symbol matches W pattern require( ISO4217WCompliance.validateTokenSymbol(currencyCode_, symbol), "ISO4217WToken: token symbol must be W" ); // Validate GRU isolation require( !ISO4217WCompliance.violatesGRUIsolation(currencyCode_), "ISO4217WToken: GRU isolation violation" ); require(custodian_ != address(0), "ISO4217WToken: zero custodian"); require(mintController_ != address(0), "ISO4217WToken: zero mint controller"); require(burnController_ != address(0), "ISO4217WToken: zero burn controller"); require(complianceGuard_ != address(0), "ISO4217WToken: zero compliance guard"); _currencyCode = currencyCode_; _decimals = decimals_; _custodian = custodian_; _mintController = mintController_; _burnController = burnController_; _complianceGuard = complianceGuard_; _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(MINTER_ROLE, mintController_); _grantRole(BURNER_ROLE, burnController_); } /** * @notice Get the ISO-4217 currency code this token represents * @return currencyCode 3-letter ISO-4217 code */ function currencyCode() external view override returns (string memory) { return _currencyCode; } /** * @notice Override totalSupply to resolve multiple inheritance conflict * @return Total supply of tokens */ function totalSupply() public view override(ERC20Upgradeable, IISO4217WToken) returns (uint256) { return super.totalSupply(); } /** * @notice Get verified reserve balance * @return reserveBalance Reserve balance in base currency units */ function verifiedReserve() external view override returns (uint256) { return _verifiedReserve; } /** * @notice Check if reserves are sufficient * @dev Reserve MUST be >= supply (enforcing 1:1 backing) * @return isSufficient True if verifiedReserve >= totalSupply */ function isReserveSufficient() external view override returns (bool) { return ISO4217WCompliance.isReserveSufficient(_verifiedReserve, totalSupply()); } /** * @notice Get custodian address */ function custodian() external view override returns (address) { return _custodian; } /** * @notice Get mint controller address */ function mintController() external view override returns (address) { return _mintController; } /** * @notice Get burn controller address */ function burnController() external view override returns (address) { return _burnController; } /** * @notice Get compliance guard address */ function complianceGuard() external view override returns (address) { return _complianceGuard; } /** * @notice Update verified reserve (oracle/attestation) * @dev Can only be called by authorized reserve update role * @param newReserve New reserve balance */ function updateVerifiedReserve(uint256 newReserve) external onlyRole(RESERVE_UPDATE_ROLE) { uint256 currentSupply = totalSupply(); // Enforce money multiplier = 1.0 // Reserve MUST be >= supply (1:1 backing or better) if (newReserve < currentSupply) { emit ReserveInsufficient(newReserve, currentSupply); // Do not revert - allow flagging for monitoring } _verifiedReserve = newReserve; emit ReserveUpdated(newReserve, block.timestamp); } /** * @notice Mint tokens (only by mint controller) * @param to Recipient address * @param amount Amount to mint */ function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) nonReentrant { require(to != address(0), "ISO4217WToken: zero address"); require(amount > 0, "ISO4217WToken: zero amount"); uint256 currentSupply = totalSupply(); uint256 newSupply = currentSupply + amount; // Enforce money multiplier = 1.0 // Reserve MUST be >= new supply (1:1 backing) require( _verifiedReserve >= newSupply, "ISO4217WToken: reserve insufficient - money multiplier violation" ); _mint(to, amount); emit Minted(to, amount, _currencyCode); } /** * @notice Burn tokens (only by burn controller) * @param from Source address * @param amount Amount to burn */ function burn(address from, uint256 amount) external onlyRole(BURNER_ROLE) nonReentrant { require(amount > 0, "ISO4217WToken: zero amount"); _burn(from, amount); emit Burned(from, amount, _currencyCode); } /** * @notice Override decimals (typically 2 for fiat currencies) */ function decimals() public view virtual override returns (uint8) { return _decimals; } /** * @notice Authorize upgrade (UUPS) * @dev Only non-monetary components may be upgraded */ function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) { // In production, add checks to ensure monetary logic is immutable // Only allow upgrades to non-monetary components } }