gcx/blockchain/genex-contracts/src/CouponFactory.sol

212 lines
8.0 KiB
Solidity
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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,000ERC-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);
}
}