257 lines
7.9 KiB
Solidity
257 lines
7.9 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "forge-std/Test.sol";
|
|
import "../src/CouponBatch.sol";
|
|
import "../src/interfaces/ICouponBatch.sol";
|
|
|
|
contract CouponBatchTest is Test {
|
|
CouponBatch batch;
|
|
|
|
address admin = address(1);
|
|
address factory = address(2);
|
|
address burner = address(3);
|
|
address issuer = address(4);
|
|
address user1 = address(5);
|
|
address user2 = address(6);
|
|
|
|
bytes32 storeA = keccak256("STORE_A");
|
|
bytes32 storeB = keccak256("STORE_B");
|
|
|
|
function setUp() public {
|
|
vm.startPrank(admin);
|
|
|
|
batch = new CouponBatch();
|
|
batch.initialize("https://genex.io/api/coupon-batch/{id}.json", admin);
|
|
|
|
batch.grantRole(keccak256("FACTORY_ROLE"), factory);
|
|
batch.grantRole(keccak256("BURNER_ROLE"), burner);
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
// ==========================================
|
|
// 铸造测试
|
|
// ==========================================
|
|
|
|
function test_MintCreatesNewType() public {
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
|
|
vm.prank(factory);
|
|
uint256 typeId = batch.mint(issuer, 100e6, 1000, config);
|
|
|
|
assertEq(typeId, 1);
|
|
assertEq(batch.balanceOf(issuer, typeId), 1000);
|
|
assertEq(batch.totalSupply(typeId), 1000);
|
|
assertEq(batch.getMintedQuantity(typeId), 1000);
|
|
}
|
|
|
|
function test_MintMultipleTypes() public {
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
|
|
vm.startPrank(factory);
|
|
uint256 typeId1 = batch.mint(issuer, 100e6, 500, config);
|
|
uint256 typeId2 = batch.mint(issuer, 50e6, 2000, config);
|
|
vm.stopPrank();
|
|
|
|
assertEq(typeId1, 1);
|
|
assertEq(typeId2, 2);
|
|
assertEq(batch.balanceOf(issuer, typeId1), 500);
|
|
assertEq(batch.balanceOf(issuer, typeId2), 2000);
|
|
}
|
|
|
|
function test_MintStoresConfig() public {
|
|
bytes32[] memory stores = new bytes32[](2);
|
|
stores[0] = storeA;
|
|
stores[1] = storeB;
|
|
|
|
ICouponBatch.BatchCouponConfig memory config = ICouponBatch.BatchCouponConfig({
|
|
transferable: false,
|
|
maxPrice: 50e6,
|
|
expiryDate: block.timestamp + 90 days,
|
|
minPurchase: 10,
|
|
stackable: true,
|
|
allowedStores: stores,
|
|
issuer: issuer,
|
|
faceValue: 50e6
|
|
});
|
|
|
|
vm.prank(factory);
|
|
uint256 typeId = batch.mint(issuer, 50e6, 100, config);
|
|
|
|
ICouponBatch.BatchCouponConfig memory stored = batch.getConfig(typeId);
|
|
assertEq(stored.transferable, false);
|
|
assertEq(stored.maxPrice, 50e6);
|
|
assertEq(stored.minPurchase, 10);
|
|
assertEq(stored.stackable, true);
|
|
assertEq(stored.allowedStores.length, 2);
|
|
assertEq(stored.issuer, issuer);
|
|
assertEq(stored.faceValue, 50e6);
|
|
}
|
|
|
|
function test_MintZeroAddressReverts() public {
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
|
|
vm.prank(factory);
|
|
vm.expectRevert("CouponBatch: zero address");
|
|
batch.mint(address(0), 100e6, 1000, config);
|
|
}
|
|
|
|
function test_MintZeroFaceValueReverts() public {
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
|
|
vm.prank(factory);
|
|
vm.expectRevert("CouponBatch: zero face value");
|
|
batch.mint(issuer, 0, 1000, config);
|
|
}
|
|
|
|
function test_MintZeroQuantityReverts() public {
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
|
|
vm.prank(factory);
|
|
vm.expectRevert("CouponBatch: zero quantity");
|
|
batch.mint(issuer, 100e6, 0, config);
|
|
}
|
|
|
|
function test_MintOnlyFactoryRole() public {
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
|
|
vm.prank(user1);
|
|
vm.expectRevert();
|
|
batch.mint(issuer, 100e6, 1000, config);
|
|
}
|
|
|
|
function test_GetFaceValue() public {
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
|
|
vm.prank(factory);
|
|
uint256 typeId = batch.mint(issuer, 250e6, 100, config);
|
|
|
|
assertEq(batch.getFaceValue(typeId), 250e6);
|
|
}
|
|
|
|
function test_GetConfigNonexistentReverts() public {
|
|
vm.expectRevert("CouponBatch: nonexistent type");
|
|
batch.getConfig(999);
|
|
}
|
|
|
|
// ==========================================
|
|
// 销毁测试
|
|
// ==========================================
|
|
|
|
function test_BurnByOwner() public {
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
|
|
vm.prank(factory);
|
|
uint256 typeId = batch.mint(issuer, 100e6, 1000, config);
|
|
|
|
vm.prank(issuer);
|
|
batch.burn(issuer, typeId, 300);
|
|
|
|
assertEq(batch.balanceOf(issuer, typeId), 700);
|
|
assertEq(batch.totalSupply(typeId), 700);
|
|
}
|
|
|
|
function test_BurnByBurnerRole() public {
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
|
|
vm.prank(factory);
|
|
uint256 typeId = batch.mint(issuer, 100e6, 1000, config);
|
|
|
|
vm.prank(burner);
|
|
batch.burn(issuer, typeId, 500);
|
|
|
|
assertEq(batch.balanceOf(issuer, typeId), 500);
|
|
}
|
|
|
|
function test_BurnByApproved() public {
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
|
|
vm.prank(factory);
|
|
uint256 typeId = batch.mint(issuer, 100e6, 1000, config);
|
|
|
|
vm.prank(issuer);
|
|
batch.setApprovalForAll(user1, true);
|
|
|
|
vm.prank(user1);
|
|
batch.burn(issuer, typeId, 200);
|
|
|
|
assertEq(batch.balanceOf(issuer, typeId), 800);
|
|
}
|
|
|
|
function test_BurnUnauthorizedReverts() public {
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
|
|
vm.prank(factory);
|
|
uint256 typeId = batch.mint(issuer, 100e6, 1000, config);
|
|
|
|
vm.prank(user1);
|
|
vm.expectRevert("CouponBatch: not authorized to burn");
|
|
batch.burn(issuer, typeId, 100);
|
|
}
|
|
|
|
// ==========================================
|
|
// 转让限制测试
|
|
// ==========================================
|
|
|
|
function test_TransferableTokenCanTransfer() public {
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
config.transferable = true;
|
|
|
|
vm.prank(factory);
|
|
uint256 typeId = batch.mint(issuer, 100e6, 100, config);
|
|
|
|
vm.prank(issuer);
|
|
batch.safeTransferFrom(issuer, user1, typeId, 50, "");
|
|
|
|
assertEq(batch.balanceOf(issuer, typeId), 50);
|
|
assertEq(batch.balanceOf(user1, typeId), 50);
|
|
}
|
|
|
|
function test_NonTransferableTokenReverts() public {
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
config.transferable = false;
|
|
|
|
vm.prank(factory);
|
|
uint256 typeId = batch.mint(issuer, 100e6, 100, config);
|
|
|
|
vm.prank(issuer);
|
|
vm.expectRevert("CouponBatch: non-transferable");
|
|
batch.safeTransferFrom(issuer, user1, typeId, 10, "");
|
|
}
|
|
|
|
// ==========================================
|
|
// Fuzz 测试
|
|
// ==========================================
|
|
|
|
function testFuzz_MintQuantity(uint256 quantity) public {
|
|
quantity = bound(quantity, 1, 100_000);
|
|
ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
|
|
|
|
vm.prank(factory);
|
|
uint256 typeId = batch.mint(issuer, 100e6, quantity, config);
|
|
|
|
assertEq(batch.balanceOf(issuer, typeId), quantity);
|
|
assertEq(batch.totalSupply(typeId), quantity);
|
|
assertEq(batch.getMintedQuantity(typeId), quantity);
|
|
}
|
|
|
|
// ==========================================
|
|
// Helpers
|
|
// ==========================================
|
|
|
|
function _defaultConfig() 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
|
|
});
|
|
}
|
|
}
|