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

308 lines
10 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/CouponFactory.sol";
import "../src/Coupon.sol";
import "../src/Compliance.sol";
import "../src/interfaces/ICoupon.sol";
import "../src/CouponBatch.sol";
import "../src/interfaces/ICouponBatch.sol";
contract CouponFactoryTest is Test {
CouponFactory factory;
Coupon coupon;
Compliance compliance;
CouponBatch couponBatch;
address admin = address(1);
address issuer = address(2);
address minter = address(3);
function setUp() public {
vm.startPrank(admin);
// 部署 Compliance
compliance = new Compliance();
compliance.initialize(admin);
// 部署 Coupon
coupon = new Coupon();
coupon.initialize("Genex Coupon", "GXC", admin);
// 部署 CouponFactory
factory = new CouponFactory();
factory.initialize(address(coupon), address(compliance), admin);
// 部署 CouponBatch
couponBatch = new CouponBatch();
couponBatch.initialize("https://genex.io/api/coupon-batch/{id}.json", admin);
// 授权
coupon.grantRole(keccak256("FACTORY_ROLE"), address(factory));
factory.grantRole(keccak256("MINTER_ROLE"), minter);
couponBatch.grantRole(keccak256("FACTORY_ROLE"), address(factory));
// 关联 CouponBatch
factory.setCouponBatchContract(address(couponBatch));
// 设置发行方 KYC
compliance.setKycLevel(issuer, 3);
vm.stopPrank();
}
function test_MintBatchUtility() public {
vm.prank(minter);
ICoupon.CouponConfig memory config = _utilityConfig();
uint256[] memory tokenIds = factory.mintBatch(issuer, 100e6, 5, config);
assertEq(tokenIds.length, 5);
for (uint256 i = 0; i < 5; i++) {
assertEq(coupon.ownerOf(tokenIds[i]), issuer);
assertEq(coupon.getFaceValue(tokenIds[i]), 100e6);
}
}
function test_MintBatchSecurity() public {
vm.prank(minter);
ICoupon.CouponConfig memory config = _securityConfig();
uint256[] memory tokenIds = factory.mintBatch(issuer, 1000e6, 3, config);
assertEq(tokenIds.length, 3);
}
function test_UtilityMaxPriceExceedsFaceValue() public {
vm.prank(minter);
ICoupon.CouponConfig memory config = _utilityConfig();
config.maxPrice = 200e6; // 超过面值 100e6
vm.expectRevert("Utility: maxPrice <= faceValue");
factory.mintBatch(issuer, 100e6, 1, config);
}
function test_UtilityMaxExpiry12Months() public {
vm.prank(minter);
ICoupon.CouponConfig memory config = _utilityConfig();
config.expiryDate = block.timestamp + 400 days;
vm.expectRevert("Utility: max 12 months");
factory.mintBatch(issuer, 100e6, 1, config);
}
function test_SecurityRequiresExpiry() public {
vm.prank(minter);
ICoupon.CouponConfig memory config = _securityConfig();
config.expiryDate = 0;
vm.expectRevert("Security: expiry required");
factory.mintBatch(issuer, 1000e6, 1, config);
}
function test_SecurityRequiresKycL3() public {
address lowKycIssuer = address(10);
vm.prank(admin);
compliance.setKycLevel(lowKycIssuer, 1);
vm.prank(minter);
ICoupon.CouponConfig memory config = _securityConfig();
vm.expectRevert("Compliance: insufficient KYC level");
factory.mintBatch(lowKycIssuer, 1000e6, 1, config);
}
function test_OnlyMinterCanMint() public {
vm.prank(issuer);
ICoupon.CouponConfig memory config = _utilityConfig();
vm.expectRevert();
factory.mintBatch(issuer, 100e6, 1, config);
}
function test_ZeroQuantityReverts() public {
vm.prank(minter);
ICoupon.CouponConfig memory config = _utilityConfig();
vm.expectRevert("CouponFactory: invalid quantity");
factory.mintBatch(issuer, 100e6, 0, config);
}
function test_ExcessiveQuantityReverts() public {
vm.prank(minter);
ICoupon.CouponConfig memory config = _utilityConfig();
vm.expectRevert("CouponFactory: invalid quantity");
factory.mintBatch(issuer, 100e6, 10001, config);
}
function test_BatchInfoRecorded() public {
vm.prank(minter);
ICoupon.CouponConfig memory config = _utilityConfig();
factory.mintBatch(issuer, 100e6, 3, config);
CouponFactory.BatchInfo memory batch = factory.getBatchInfo(0);
assertEq(batch.issuer, issuer);
assertEq(batch.faceValue, 100e6);
assertEq(batch.quantity, 3);
assertEq(batch.tokenIds.length, 3);
}
function testFuzz_MintBatchQuantity(uint256 quantity) public {
quantity = bound(quantity, 1, 100);
vm.prank(minter);
ICoupon.CouponConfig memory config = _utilityConfig();
uint256[] memory tokenIds = factory.mintBatch(issuer, 100e6, quantity, config);
assertEq(tokenIds.length, quantity);
}
// --- Helpers ---
function _utilityConfig() internal view returns (ICoupon.CouponConfig memory) {
bytes32[] memory stores = new bytes32[](0);
return ICoupon.CouponConfig({
couponType: ICoupon.CouponType.Utility,
transferable: true,
maxResaleCount: 3,
maxPrice: 100e6,
expiryDate: block.timestamp + 180 days,
minPurchase: 0,
stackable: false,
allowedStores: stores,
issuer: issuer,
faceValue: 100e6
});
}
function _securityConfig() internal view returns (ICoupon.CouponConfig memory) {
bytes32[] memory stores = new bytes32[](0);
return ICoupon.CouponConfig({
couponType: ICoupon.CouponType.Security,
transferable: true,
maxResaleCount: 10,
maxPrice: 0,
expiryDate: block.timestamp + 365 days,
minPurchase: 0,
stackable: false,
allowedStores: stores,
issuer: issuer,
faceValue: 1000e6
});
}
function _batchConfig() internal view returns (ICouponBatch.BatchCouponConfig memory) {
bytes32[] memory stores = new bytes32[](0);
return ICouponBatch.BatchCouponConfig({
transferable: true,
maxPrice: 100e6,
expiryDate: block.timestamp + 180 days,
minPurchase: 0,
stackable: false,
allowedStores: stores,
issuer: issuer,
faceValue: 100e6
});
}
// ==========================================
// ERC-1155 (mintBatch1155) 测试
// ==========================================
function test_MintBatch1155Success() public {
vm.prank(minter);
ICouponBatch.BatchCouponConfig memory config = _batchConfig();
uint256 typeId = factory.mintBatch1155(issuer, 100e6, 10000, config);
assertEq(typeId, 1);
assertEq(couponBatch.balanceOf(issuer, typeId), 10000);
}
function test_MintBatch1155LargeQuantity() public {
vm.prank(minter);
ICouponBatch.BatchCouponConfig memory config = _batchConfig();
uint256 typeId = factory.mintBatch1155(issuer, 100e6, 100000, config);
assertEq(couponBatch.balanceOf(issuer, typeId), 100000);
}
function test_MintBatch1155RecordsBatchInfo() public {
vm.prank(minter);
ICouponBatch.BatchCouponConfig memory config = _batchConfig();
factory.mintBatch1155(issuer, 100e6, 5000, config);
CouponFactory.BatchInfo memory info = factory.getBatchInfo(0);
assertEq(info.issuer, issuer);
assertEq(info.faceValue, 100e6);
assertEq(info.quantity, 5000);
assertEq(info.tokenIds.length, 0); // ERC-1155 uses typeId, not tokenIds
}
function test_MintBatch1155ZeroIssuerReverts() public {
vm.prank(minter);
ICouponBatch.BatchCouponConfig memory config = _batchConfig();
vm.expectRevert("CouponFactory: zero address issuer");
factory.mintBatch1155(address(0), 100e6, 1000, config);
}
function test_MintBatch1155ZeroFaceValueReverts() public {
vm.prank(minter);
ICouponBatch.BatchCouponConfig memory config = _batchConfig();
vm.expectRevert("CouponFactory: zero face value");
factory.mintBatch1155(issuer, 0, 1000, config);
}
function test_MintBatch1155ExcessiveQuantityReverts() public {
vm.prank(minter);
ICouponBatch.BatchCouponConfig memory config = _batchConfig();
vm.expectRevert("CouponFactory: invalid quantity");
factory.mintBatch1155(issuer, 100e6, 100001, config);
}
function test_MintBatch1155MaxPriceExceedsFaceValueReverts() public {
vm.prank(minter);
ICouponBatch.BatchCouponConfig memory config = _batchConfig();
config.maxPrice = 200e6; // 超过面值
vm.expectRevert("Utility: maxPrice <= faceValue");
factory.mintBatch1155(issuer, 100e6, 1000, config);
}
function test_MintBatch1155ExpiryTooFarReverts() public {
vm.prank(minter);
ICouponBatch.BatchCouponConfig memory config = _batchConfig();
config.expiryDate = block.timestamp + 400 days;
vm.expectRevert("Utility: max 12 months");
factory.mintBatch1155(issuer, 100e6, 1000, config);
}
function test_MintBatch1155ExpiredReverts() public {
vm.prank(minter);
ICouponBatch.BatchCouponConfig memory config = _batchConfig();
config.expiryDate = block.timestamp - 1;
vm.expectRevert("Utility: expiry must be future");
factory.mintBatch1155(issuer, 100e6, 1000, config);
}
function test_MintBatch1155BatchContractNotSetReverts() public {
// 部署新工厂,不设置 CouponBatch
vm.startPrank(admin);
CouponFactory factory2 = new CouponFactory();
factory2.initialize(address(coupon), address(compliance), admin);
factory2.grantRole(keccak256("MINTER_ROLE"), minter);
vm.stopPrank();
vm.prank(minter);
ICouponBatch.BatchCouponConfig memory config = _batchConfig();
vm.expectRevert("CouponFactory: batch contract not set");
factory2.mintBatch1155(issuer, 100e6, 1000, config);
}
function test_MintBatch1155OnlyMinterRole() public {
vm.prank(issuer);
ICouponBatch.BatchCouponConfig memory config = _batchConfig();
vm.expectRevert();
factory.mintBatch1155(issuer, 100e6, 1000, config);
}
}