// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; import "../src/CouponFactory.sol"; import "../src/Coupon.sol"; import "../src/CouponBatch.sol"; import "../src/Redemption.sol"; import "../src/Redemption1155.sol"; import "../src/Settlement.sol"; import "../src/Compliance.sol"; import "../src/interfaces/ICoupon.sol"; import "../src/interfaces/ICouponBatch.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockUSDC1155 is ERC20 { constructor() ERC20("USD Coin", "USDC") {} function decimals() public pure override returns (uint8) { return 6; } function mint(address to, uint256 amount) external { _mint(to, amount); } } /// @title ERC-721 + ERC-1155 混合端到端集成测试 contract Integration1155Test is Test { CouponFactory factory; Coupon coupon; CouponBatch couponBatch; Settlement settlement; Redemption redemption; Redemption1155 redemption1155; Compliance compliance; MockUSDC1155 usdc; address admin = address(1); address issuer = address(2); address consumer1 = address(3); address consumer2 = address(4); bytes32 storeA = keccak256("STORE_A"); function setUp() public { vm.startPrank(admin); usdc = new MockUSDC1155(); // 部署合约 compliance = new Compliance(); compliance.initialize(admin); coupon = new Coupon(); coupon.initialize("Genex Coupon", "GXC", admin); couponBatch = new CouponBatch(); couponBatch.initialize("https://genex.io/api/coupon-batch/{id}.json", admin); factory = new CouponFactory(); factory.initialize(address(coupon), address(compliance), admin); settlement = new Settlement(); settlement.initialize(address(coupon), address(compliance), address(usdc), admin); redemption = new Redemption(); redemption.initialize(address(coupon), address(compliance), admin); redemption1155 = new Redemption1155(); redemption1155.initialize(address(couponBatch), address(compliance), admin); // 角色授权 coupon.grantRole(keccak256("FACTORY_ROLE"), address(factory)); coupon.grantRole(keccak256("SETTLER_ROLE"), address(settlement)); coupon.grantRole(keccak256("SETTLER_ROLE"), address(redemption)); couponBatch.grantRole(keccak256("FACTORY_ROLE"), address(factory)); couponBatch.grantRole(keccak256("BURNER_ROLE"), address(redemption1155)); // 关联 CouponBatch factory.setCouponBatchContract(address(couponBatch)); // KYC compliance.setKycLevel(issuer, 3); compliance.setKycLevel(consumer1, 1); compliance.setKycLevel(consumer2, 1); // 资金 usdc.mint(consumer1, 500_000e6); usdc.mint(consumer2, 500_000e6); vm.stopPrank(); // Approvals vm.prank(consumer1); usdc.approve(address(settlement), type(uint256).max); vm.prank(consumer1); coupon.setApprovalForAll(address(settlement), true); vm.prank(issuer); coupon.setApprovalForAll(address(settlement), true); } /// @notice ERC-1155 完整生命周期: Factory铸造 → 分发 → 兑付 function test_ERC1155FullLifecycle() public { // Step 1: 通过 Factory 铸造 ERC-1155 券 bytes32[] memory stores = new bytes32[](0); ICouponBatch.BatchCouponConfig memory batchConfig = ICouponBatch.BatchCouponConfig({ transferable: true, maxPrice: 50e6, expiryDate: block.timestamp + 180 days, minPurchase: 0, stackable: false, allowedStores: stores, issuer: issuer, faceValue: 50e6 }); vm.prank(admin); uint256 typeId = factory.mintBatch1155(issuer, 50e6, 10000, batchConfig); assertEq(couponBatch.balanceOf(issuer, typeId), 10000); assertEq(couponBatch.totalSupply(typeId), 10000); // Step 2: 发行方分发给消费者 vm.prank(issuer); couponBatch.safeTransferFrom(issuer, consumer1, typeId, 100, ""); assertEq(couponBatch.balanceOf(consumer1, typeId), 100); assertEq(couponBatch.balanceOf(issuer, typeId), 9900); // Step 3: 消费者兑付 vm.prank(consumer1); redemption1155.redeem(typeId, 5, storeA); assertEq(couponBatch.balanceOf(consumer1, typeId), 95); assertEq(couponBatch.totalSupply(typeId), 9995); // 5 已销毁 Redemption1155.RedemptionRecord memory record = redemption1155.getRedemption(0); assertEq(record.consumer, consumer1); assertEq(record.amount, 5); } /// @notice 同一 Factory 同时管理 ERC-721 和 ERC-1155 券 function test_MixedERC721AndERC1155() public { // 铸造 ERC-721 券 ICoupon.CouponConfig memory config721 = ICoupon.CouponConfig({ couponType: ICoupon.CouponType.Utility, transferable: true, maxResaleCount: 3, maxPrice: 100e6, expiryDate: block.timestamp + 180 days, minPurchase: 0, stackable: false, allowedStores: new bytes32[](0), issuer: issuer, faceValue: 100e6 }); vm.prank(admin); uint256[] memory tokenIds = factory.mintBatch(issuer, 100e6, 3, config721); // 铸造 ERC-1155 券 ICouponBatch.BatchCouponConfig memory config1155 = ICouponBatch.BatchCouponConfig({ transferable: true, maxPrice: 50e6, expiryDate: block.timestamp + 180 days, minPurchase: 0, stackable: false, allowedStores: new bytes32[](0), issuer: issuer, faceValue: 50e6 }); vm.prank(admin); uint256 typeId = factory.mintBatch1155(issuer, 50e6, 5000, config1155); // 验证两种标准互不干扰 assertEq(coupon.ownerOf(tokenIds[0]), issuer); assertEq(couponBatch.balanceOf(issuer, typeId), 5000); // BatchInfo 共享 nextBatchId 计数器 CouponFactory.BatchInfo memory batch0 = factory.getBatchInfo(0); assertEq(batch0.tokenIds.length, 3); // ERC-721 CouponFactory.BatchInfo memory batch1 = factory.getBatchInfo(1); assertEq(batch1.tokenIds.length, 0); // ERC-1155 assertEq(batch1.quantity, 5000); } /// @notice ERC-721 走 Settlement 二级市场,ERC-1155 走 Redemption1155 兑付 function test_DualTrackSettlementAndRedemption() public { // ERC-721: 铸造 + 一级购买 + 兑付 ICoupon.CouponConfig memory config721 = ICoupon.CouponConfig({ couponType: ICoupon.CouponType.Utility, transferable: true, maxResaleCount: 3, maxPrice: 100e6, expiryDate: block.timestamp + 180 days, minPurchase: 0, stackable: false, allowedStores: new bytes32[](0), issuer: issuer, faceValue: 100e6 }); vm.prank(admin); uint256[] memory tokenIds = factory.mintBatch(issuer, 100e6, 1, config721); vm.prank(admin); settlement.executeSwap(tokenIds[0], consumer1, issuer, 100e6, address(usdc)); assertEq(coupon.ownerOf(tokenIds[0]), consumer1); vm.prank(consumer1); redemption.redeem(tokenIds[0], storeA); // ERC-1155: 铸造 + 分发 + 兑付 ICouponBatch.BatchCouponConfig memory config1155 = ICouponBatch.BatchCouponConfig({ transferable: true, maxPrice: 20e6, expiryDate: block.timestamp + 90 days, minPurchase: 0, stackable: true, allowedStores: new bytes32[](0), issuer: issuer, faceValue: 20e6 }); vm.prank(admin); uint256 typeId = factory.mintBatch1155(issuer, 20e6, 50000, config1155); vm.prank(issuer); couponBatch.safeTransferFrom(issuer, consumer1, typeId, 200, ""); vm.prank(consumer1); redemption1155.redeem(typeId, 10, storeA); assertEq(couponBatch.balanceOf(consumer1, typeId), 190); } /// @notice 合规系统同时影响两种标准 function test_ComplianceAffectsBothStandards() public { // 铸造 ERC-1155 ICouponBatch.BatchCouponConfig memory config1155 = ICouponBatch.BatchCouponConfig({ transferable: true, maxPrice: 100e6, expiryDate: block.timestamp + 180 days, minPurchase: 0, stackable: false, allowedStores: new bytes32[](0), issuer: issuer, faceValue: 100e6 }); vm.prank(admin); uint256 typeId = factory.mintBatch1155(issuer, 100e6, 1000, config1155); vm.prank(issuer); couponBatch.safeTransferFrom(issuer, consumer1, typeId, 50, ""); // 将 consumer1 加入黑名单 vm.prank(admin); compliance.addToBlacklist(consumer1, "OFAC"); // ERC-1155 兑付被拒绝 vm.prank(consumer1); vm.expectRevert("Redemption1155: blacklisted"); redemption1155.redeem(typeId, 1, storeA); } /// @notice 非可转让 ERC-1155 券不能转移 function test_NonTransferableERC1155() public { ICouponBatch.BatchCouponConfig memory config = ICouponBatch.BatchCouponConfig({ transferable: false, maxPrice: 30e6, expiryDate: block.timestamp + 90 days, minPurchase: 0, stackable: false, allowedStores: new bytes32[](0), issuer: issuer, faceValue: 30e6 }); vm.prank(admin); uint256 typeId = factory.mintBatch1155(issuer, 30e6, 1000, config); // 不可转让 vm.prank(issuer); vm.expectRevert("CouponBatch: non-transferable"); couponBatch.safeTransferFrom(issuer, consumer1, typeId, 10, ""); // 但发行方自己可以兑付(通过 BURNER_ROLE 销毁) // — 不过 Redemption1155 检查的是 msg.sender 的 balance assertEq(couponBatch.balanceOf(issuer, typeId), 1000); } }