// 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/ICouponBatch.sol"; import "./interfaces/ICompliance.sol"; /// @title Redemption1155 — ERC-1155 券兑付合约 /// @notice 支持多单位一次性兑付(burn),平台不介入资金 /// @dev Transparent Proxy 部署。消费者持券到店兑付,合约负责销毁。 contract Redemption1155 is Initializable, AccessControlUpgradeable, ReentrancyGuardUpgradeable { bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); ICouponBatch public couponBatchContract; ICompliance public compliance; struct RedemptionRecord { uint256 typeId; uint256 amount; // 本次兑付数量 address consumer; address issuer; bytes32 storeId; uint256 redeemedAt; } uint256 public totalRedemptions; mapping(uint256 => RedemptionRecord) public redemptions; // --- Events --- event BatchCouponRedeemed( uint256 indexed typeId, uint256 amount, address indexed consumer, address indexed issuer, bytes32 storeId, uint256 redemptionId ); function initialize( address _couponBatchContract, address _compliance, address admin ) external initializer { __AccessControl_init(); __ReentrancyGuard_init(); couponBatchContract = ICouponBatch(_couponBatchContract); compliance = ICompliance(_compliance); _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(ADMIN_ROLE, admin); } /// @notice 兑付 `amount` 单位的 typeId 券 /// @param typeId ERC-1155 token type ID /// @param amount 兑付数量 /// @param storeId 门店 ID 哈希 function redeem( uint256 typeId, uint256 amount, bytes32 storeId ) external nonReentrant { require(amount > 0, "Redemption1155: zero amount"); // 余额检查(隐含所有权验证) require( couponBatchContract.balanceOf(msg.sender, typeId) >= amount, "Redemption1155: insufficient balance" ); // 合规检查 require(!compliance.isBlacklisted(msg.sender), "Redemption1155: blacklisted"); ICouponBatch.BatchCouponConfig memory config = couponBatchContract.getConfig(typeId); // 到期检查 require(block.timestamp <= config.expiryDate, "Redemption1155: coupon expired"); // 门店限制检查 if (config.allowedStores.length > 0) { require( _isAllowedStore(storeId, config.allowedStores), "Redemption1155: store not allowed" ); } // 销毁券 couponBatchContract.burn(msg.sender, typeId, amount); // 记录兑付 uint256 redemptionId = totalRedemptions++; redemptions[redemptionId] = RedemptionRecord({ typeId: typeId, amount: amount, consumer: msg.sender, issuer: config.issuer, storeId: storeId, redeemedAt: block.timestamp }); emit BatchCouponRedeemed( typeId, amount, msg.sender, config.issuer, storeId, redemptionId ); } /// @notice 查询兑付记录 function getRedemption(uint256 redemptionId) external view returns (RedemptionRecord memory) { return redemptions[redemptionId]; } /// @dev 门店白名单检查 function _isAllowedStore( bytes32 storeId, bytes32[] memory allowedStores ) internal pure returns (bool) { for (uint256 i = 0; i < allowedStores.length; i++) { if (allowedStores[i] == storeId) return true; } return false; } }