158 lines
4.9 KiB
Solidity
158 lines
4.9 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "forge-std/Test.sol";
|
|
import "../src/Coupon.sol";
|
|
import "../src/interfaces/ICoupon.sol";
|
|
|
|
contract CouponTest is Test {
|
|
Coupon coupon;
|
|
|
|
address admin = address(1);
|
|
address factory = address(2);
|
|
address settler = address(3);
|
|
address user1 = address(4);
|
|
address user2 = address(5);
|
|
|
|
function setUp() public {
|
|
vm.startPrank(admin);
|
|
coupon = new Coupon();
|
|
coupon.initialize("Genex Coupon", "GXC", admin);
|
|
coupon.grantRole(keccak256("FACTORY_ROLE"), factory);
|
|
coupon.grantRole(keccak256("SETTLER_ROLE"), settler);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function test_MintSetsConfigCorrectly() public {
|
|
uint256 tokenId = _mintUtilityCoupon(user1, 100e6);
|
|
ICoupon.CouponConfig memory config = coupon.getConfig(tokenId);
|
|
|
|
assertEq(uint8(config.couponType), uint8(ICoupon.CouponType.Utility));
|
|
assertTrue(config.transferable);
|
|
assertEq(config.maxResaleCount, 3);
|
|
assertEq(coupon.ownerOf(tokenId), user1);
|
|
assertEq(coupon.getFaceValue(tokenId), 100e6);
|
|
}
|
|
|
|
function test_BurnByOwner() public {
|
|
uint256 tokenId = _mintUtilityCoupon(user1, 100e6);
|
|
vm.prank(user1);
|
|
coupon.burn(tokenId);
|
|
vm.expectRevert();
|
|
coupon.ownerOf(tokenId);
|
|
}
|
|
|
|
function test_BurnBySettler() public {
|
|
uint256 tokenId = _mintUtilityCoupon(user1, 100e6);
|
|
vm.prank(settler);
|
|
coupon.burn(tokenId);
|
|
}
|
|
|
|
function test_NonTransferableCouponReverts() public {
|
|
uint256 tokenId = _mintNonTransferableCoupon(user1, 50e6);
|
|
|
|
vm.prank(user1);
|
|
vm.expectRevert("Coupon: non-transferable");
|
|
coupon.safeTransferFrom(user1, user2, tokenId);
|
|
}
|
|
|
|
function test_TransferableTransferSucceeds() public {
|
|
uint256 tokenId = _mintUtilityCoupon(user1, 100e6);
|
|
|
|
vm.prank(user1);
|
|
coupon.safeTransferFrom(user1, user2, tokenId);
|
|
assertEq(coupon.ownerOf(tokenId), user2);
|
|
}
|
|
|
|
function test_MaxResaleCountEnforced() public {
|
|
uint256 tokenId = _mintUtilityCoupon(user1, 100e6);
|
|
|
|
// 使用 settler 增加到上限 (3)
|
|
vm.startPrank(settler);
|
|
coupon.incrementResaleCount(tokenId);
|
|
coupon.incrementResaleCount(tokenId);
|
|
coupon.incrementResaleCount(tokenId);
|
|
vm.stopPrank();
|
|
|
|
assertEq(coupon.getResaleCount(tokenId), 3);
|
|
|
|
// 转移应被拒绝
|
|
vm.prank(user1);
|
|
vm.expectRevert("Coupon: max resale count exceeded");
|
|
coupon.safeTransferFrom(user1, user2, tokenId);
|
|
}
|
|
|
|
function test_IncrementResaleCountOnlySettler() public {
|
|
uint256 tokenId = _mintUtilityCoupon(user1, 100e6);
|
|
|
|
vm.prank(user1);
|
|
vm.expectRevert();
|
|
coupon.incrementResaleCount(tokenId);
|
|
}
|
|
|
|
function test_BatchTransfer() public {
|
|
uint256[] memory tokenIds = new uint256[](3);
|
|
for (uint256 i = 0; i < 3; i++) {
|
|
tokenIds[i] = _mintUtilityCoupon(user1, 100e6);
|
|
}
|
|
|
|
vm.prank(user1);
|
|
coupon.batchTransfer(user1, user2, tokenIds);
|
|
|
|
for (uint256 i = 0; i < 3; i++) {
|
|
assertEq(coupon.ownerOf(tokenIds[i]), user2);
|
|
}
|
|
}
|
|
|
|
function test_ResaleCountInitiallyZero() public {
|
|
uint256 tokenId = _mintUtilityCoupon(user1, 100e6);
|
|
assertEq(coupon.getResaleCount(tokenId), 0);
|
|
}
|
|
|
|
function test_MintIncrementsTokenId() public {
|
|
uint256 id1 = _mintUtilityCoupon(user1, 100e6);
|
|
uint256 id2 = _mintUtilityCoupon(user1, 200e6);
|
|
assertEq(id2, id1 + 1);
|
|
}
|
|
|
|
// --- Helpers ---
|
|
|
|
function _mintUtilityCoupon(address to, uint256 faceValue) internal returns (uint256) {
|
|
bytes32[] memory stores = new bytes32[](0);
|
|
ICoupon.CouponConfig memory config = ICoupon.CouponConfig({
|
|
couponType: ICoupon.CouponType.Utility,
|
|
transferable: true,
|
|
maxResaleCount: 3,
|
|
maxPrice: faceValue,
|
|
expiryDate: block.timestamp + 180 days,
|
|
minPurchase: 0,
|
|
stackable: false,
|
|
allowedStores: stores,
|
|
issuer: admin,
|
|
faceValue: faceValue
|
|
});
|
|
|
|
vm.prank(factory);
|
|
return coupon.mint(to, faceValue, config);
|
|
}
|
|
|
|
function _mintNonTransferableCoupon(address to, uint256 faceValue) internal returns (uint256) {
|
|
bytes32[] memory stores = new bytes32[](0);
|
|
ICoupon.CouponConfig memory config = ICoupon.CouponConfig({
|
|
couponType: ICoupon.CouponType.Utility,
|
|
transferable: false,
|
|
maxResaleCount: 0,
|
|
maxPrice: faceValue,
|
|
expiryDate: block.timestamp + 180 days,
|
|
minPurchase: 0,
|
|
stackable: false,
|
|
allowedStores: stores,
|
|
issuer: admin,
|
|
faceValue: faceValue
|
|
});
|
|
|
|
vm.prank(factory);
|
|
return coupon.mint(to, faceValue, config);
|
|
}
|
|
}
|