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

162 lines
5.5 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/token/ERC1155/ERC1155Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155SupplyUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "./interfaces/ICouponBatch.sol";
/// @title CouponBatch — ERC-1155 同质化券合约
/// @notice 支持批量铸造相同配置的券,适用于促销折扣券、空投券等场景
/// @dev Utility Track 专用无转售次数追踪。Transparent Proxy 部署。
contract CouponBatch is
Initializable,
ERC1155Upgradeable,
ERC1155SupplyUpgradeable,
AccessControlUpgradeable
{
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
bytes32 public constant FACTORY_ROLE = keccak256("FACTORY_ROLE");
uint256 private _nextTypeId;
/// @dev typeId → 券配置(铸造后不可更改)
mapping(uint256 => ICouponBatch.BatchCouponConfig) private _configs;
/// @dev typeId → 原始铸造数量(用于统计)
mapping(uint256 => uint256) private _mintedQuantities;
// --- Events ---
event BatchCouponMinted(
uint256 indexed typeId,
address indexed issuer,
uint256 faceValue,
uint256 quantity
);
event BatchCouponBurned(
uint256 indexed typeId,
address indexed account,
uint256 amount
);
function initialize(string memory uri_, address admin) external initializer {
__ERC1155_init(uri_);
__ERC1155Supply_init();
__AccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MINTER_ROLE, admin);
_nextTypeId = 1;
}
/// @notice 创建新券类型并铸造 `quantity` 单位到 `to`
/// @param to 接收者地址
/// @param faceValue 面值
/// @param quantity 数量
/// @param config 券配置
/// @return typeId 新创建的 token type ID
function mint(
address to,
uint256 faceValue,
uint256 quantity,
ICouponBatch.BatchCouponConfig calldata config
) external onlyRole(FACTORY_ROLE) returns (uint256 typeId) {
require(to != address(0), "CouponBatch: zero address");
require(faceValue > 0, "CouponBatch: zero face value");
require(quantity > 0, "CouponBatch: zero quantity");
typeId = _nextTypeId++;
// 存储配置(确保 faceValue 和 issuer 字段一致)
_configs[typeId] = ICouponBatch.BatchCouponConfig({
transferable: config.transferable,
maxPrice: config.maxPrice,
expiryDate: config.expiryDate,
minPurchase: config.minPurchase,
stackable: config.stackable,
allowedStores: config.allowedStores,
issuer: config.issuer,
faceValue: faceValue
});
_mintedQuantities[typeId] = quantity;
// 单次 ERC-1155 mint — O(1) 存储
_mint(to, typeId, quantity, "");
emit BatchCouponMinted(typeId, config.issuer, faceValue, quantity);
}
/// @notice 销毁 `amount` 单位的 typeId 券
/// @param account 被销毁的地址
/// @param typeId 券类型 ID
/// @param amount 销毁数量
function burn(
address account,
uint256 typeId,
uint256 amount
) external {
require(
account == _msgSender()
|| isApprovedForAll(account, _msgSender())
|| hasRole(BURNER_ROLE, _msgSender()),
"CouponBatch: not authorized to burn"
);
_burn(account, typeId, amount);
emit BatchCouponBurned(typeId, account, amount);
}
// --- 查询函数 ---
/// @notice 获取券类型配置
function getConfig(uint256 typeId) external view returns (ICouponBatch.BatchCouponConfig memory) {
require(exists(typeId), "CouponBatch: nonexistent type");
return _configs[typeId];
}
/// @notice 获取面值
function getFaceValue(uint256 typeId) external view returns (uint256) {
require(exists(typeId), "CouponBatch: nonexistent type");
return _configs[typeId].faceValue;
}
/// @notice 获取原始铸造数量
function getMintedQuantity(uint256 typeId) external view returns (uint256) {
return _mintedQuantities[typeId];
}
// --- 转让限制 ---
/// @dev 重写 _beforeTokenTransfer 强制非转让检查
function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual override(ERC1155Upgradeable, ERC1155SupplyUpgradeable) {
super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
// 铸造 (from=0) 和销毁 (to=0) 不受限
if (from == address(0) || to == address(0)) return;
// 检查每个 token type 的可转让性
for (uint256 i = 0; i < ids.length; i++) {
ICouponBatch.BatchCouponConfig memory config = _configs[ids[i]];
require(config.transferable, "CouponBatch: non-transferable");
}
}
// --- ERC165 ---
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC1155Upgradeable, AccessControlUpgradeable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}