gcx/blockchain/genex-contracts/test/Integration1155.t.sol

290 lines
10 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 "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);
}
}