// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import "./interfaces/ICoupon.sol"; import "./interfaces/ICouponBatch.sol"; import "./interfaces/ICompliance.sol"; /// @title CouponFactory — 券发行工厂 /// @notice 负责批量铸造券,支持 ERC-721(逐张追踪)和 ERC-1155(同质化批量)双模式 /// @dev 使用 Transparent Proxy 部署,可升级 contract CouponFactory is Initializable, AccessControlUpgradeable, ReentrancyGuardUpgradeable { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); ICoupon public couponContract; ICouponBatch public couponBatchContract; ICompliance public compliance; uint256 public nextBatchId; // 批次信息 struct BatchInfo { address issuer; uint256 faceValue; uint256 quantity; ICoupon.CouponType couponType; uint256 createdAt; uint256[] tokenIds; } mapping(uint256 => BatchInfo) public batches; // --- Events --- event CouponBatchMinted( address indexed issuer, uint256 indexed batchId, uint256 faceValue, uint256 quantity, ICoupon.CouponType couponType ); event CouponContractUpdated(address indexed newCouponContract); event CouponBatchContractUpdated(address indexed newCouponBatchContract); event ComplianceContractUpdated(address indexed newCompliance); event CouponBatch1155Minted( address indexed issuer, uint256 indexed batchId, uint256 typeId, uint256 faceValue, uint256 quantity ); function initialize( address _couponContract, address _compliance, address admin ) external initializer { __AccessControl_init(); __ReentrancyGuard_init(); couponContract = ICoupon(_couponContract); compliance = ICompliance(_compliance); _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(ADMIN_ROLE, admin); _grantRole(MINTER_ROLE, admin); } /// @notice 批量铸造券 /// @param issuer 发行方地址 /// @param faceValue 面值(USDC 精度,6位小数) /// @param quantity 数量 /// @param config 券配置 /// @return tokenIds 铸造的 token ID 数组 function mintBatch( address issuer, uint256 faceValue, uint256 quantity, ICoupon.CouponConfig calldata config ) external onlyRole(MINTER_ROLE) nonReentrant returns (uint256[] memory tokenIds) { require(issuer != address(0), "CouponFactory: zero address issuer"); require(faceValue > 0, "CouponFactory: zero face value"); require(quantity > 0 && quantity <= 10000, "CouponFactory: invalid quantity"); // Utility Track 强制:价格上限 = 面值,最长12个月 if (config.couponType == ICoupon.CouponType.Utility) { require(config.maxPrice <= faceValue, "Utility: maxPrice <= faceValue"); require( config.expiryDate <= block.timestamp + 365 days, "Utility: max 12 months" ); require(config.expiryDate > block.timestamp, "Utility: expiry must be future"); } // Securities Track 强制:必须通过合格投资者验证 if (config.couponType == ICoupon.CouponType.Security) { require(config.expiryDate > 0, "Security: expiry required"); // 发行方必须 KYC L3 compliance.requireKycLevel(issuer, 3); } // 铸造 tokenIds = new uint256[](quantity); ICoupon.CouponConfig memory mintConfig = ICoupon.CouponConfig({ couponType: config.couponType, transferable: config.transferable, maxResaleCount: config.maxResaleCount, maxPrice: config.maxPrice, expiryDate: config.expiryDate, minPurchase: config.minPurchase, stackable: config.stackable, allowedStores: config.allowedStores, issuer: issuer, faceValue: faceValue }); for (uint256 i = 0; i < quantity; i++) { tokenIds[i] = couponContract.mint(issuer, faceValue, mintConfig); } // 记录批次 uint256 batchId = nextBatchId++; batches[batchId] = BatchInfo({ issuer: issuer, faceValue: faceValue, quantity: quantity, couponType: config.couponType, createdAt: block.timestamp, tokenIds: tokenIds }); emit CouponBatchMinted(issuer, batchId, faceValue, quantity, config.couponType); } /// @notice 查询批次信息 function getBatchInfo(uint256 batchId) external view returns (BatchInfo memory) { return batches[batchId]; } /// @notice 更新 Coupon 合约地址 function setCouponContract(address _couponContract) external onlyRole(ADMIN_ROLE) { require(_couponContract != address(0), "CouponFactory: zero address"); couponContract = ICoupon(_couponContract); emit CouponContractUpdated(_couponContract); } /// @notice 更新 Compliance 合约地址 function setComplianceContract(address _compliance) external onlyRole(ADMIN_ROLE) { require(_compliance != address(0), "CouponFactory: zero address"); compliance = ICompliance(_compliance); emit ComplianceContractUpdated(_compliance); } // ===================================================== // ERC-1155 同质化券铸造(Utility Track 专用) // ===================================================== /// @notice 铸造 ERC-1155 同质化券(Utility Track 专用) /// @param issuer 发行方地址 /// @param faceValue 面值(USDC 精度,6位小数) /// @param quantity 数量(上限 100,000,ERC-1155 为 O(1) 铸造) /// @param config 券配置 /// @return typeId ERC-1155 token type ID function mintBatch1155( address issuer, uint256 faceValue, uint256 quantity, ICouponBatch.BatchCouponConfig calldata config ) external onlyRole(MINTER_ROLE) nonReentrant returns (uint256 typeId) { require(issuer != address(0), "CouponFactory: zero address issuer"); require(faceValue > 0, "CouponFactory: zero face value"); require(quantity > 0 && quantity <= 100000, "CouponFactory: invalid quantity"); require( address(couponBatchContract) != address(0), "CouponFactory: batch contract not set" ); // Utility Track 强制(ERC-1155 仅限 Utility) require(config.maxPrice <= faceValue, "Utility: maxPrice <= faceValue"); require( config.expiryDate <= block.timestamp + 365 days, "Utility: max 12 months" ); require(config.expiryDate > block.timestamp, "Utility: expiry must be future"); // 通过 CouponBatch 合约铸造 typeId = couponBatchContract.mint(issuer, faceValue, quantity, config); // 记录批次(tokenIds 为空,ERC-1155 使用 typeId + quantity) uint256 batchId = nextBatchId++; batches[batchId] = BatchInfo({ issuer: issuer, faceValue: faceValue, quantity: quantity, couponType: ICoupon.CouponType.Utility, createdAt: block.timestamp, tokenIds: new uint256[](0) }); emit CouponBatch1155Minted(issuer, batchId, typeId, faceValue, quantity); } /// @notice 设置 CouponBatch (ERC-1155) 合约地址 function setCouponBatchContract(address _couponBatchContract) external onlyRole(ADMIN_ROLE) { require(_couponBatchContract != address(0), "CouponFactory: zero address"); couponBatchContract = ICouponBatch(_couponBatchContract); emit CouponBatchContractUpdated(_couponBatchContract); } }