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

146 lines
4.3 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Redemption.sol";
import "../src/Coupon.sol";
import "../src/Compliance.sol";
import "../src/interfaces/ICoupon.sol";
contract RedemptionTest is Test {
Redemption redemption;
Coupon coupon;
Compliance compliance;
address admin = address(1);
address consumer = address(4);
bytes32 storeA = keccak256("STORE_A");
bytes32 storeB = keccak256("STORE_B");
function setUp() public {
vm.startPrank(admin);
compliance = new Compliance();
compliance.initialize(admin);
coupon = new Coupon();
coupon.initialize("Genex Coupon", "GXC", admin);
coupon.grantRole(keccak256("FACTORY_ROLE"), admin);
redemption = new Redemption();
redemption.initialize(address(coupon), address(compliance), admin);
// Grant Redemption contract the SETTLER_ROLE so it can burn coupons
coupon.grantRole(keccak256("SETTLER_ROLE"), address(redemption));
compliance.setKycLevel(consumer, 1);
vm.stopPrank();
}
function test_RedeemSuccess() public {
uint256 tokenId = _mintCoupon(consumer, 100e6, false);
vm.prank(consumer);
redemption.redeem(tokenId, storeA);
// 券已销毁
vm.expectRevert();
coupon.ownerOf(tokenId);
// 记录已创建
Redemption.RedemptionRecord memory record = redemption.getRedemption(0);
assertEq(record.tokenId, tokenId);
assertEq(record.consumer, consumer);
assertEq(record.storeId, storeA);
}
function test_RedeemExpiredCouponReverts() public {
uint256 tokenId = _mintCoupon(consumer, 100e6, false);
vm.warp(block.timestamp + 200 days);
vm.prank(consumer);
vm.expectRevert("Redemption: coupon expired");
redemption.redeem(tokenId, storeA);
}
function test_RedeemNotOwnerReverts() public {
uint256 tokenId = _mintCoupon(consumer, 100e6, false);
vm.prank(admin);
vm.expectRevert("Redemption: not owner");
redemption.redeem(tokenId, storeA);
}
function test_RedeemBlacklistedReverts() public {
uint256 tokenId = _mintCoupon(consumer, 100e6, false);
vm.prank(admin);
compliance.addToBlacklist(consumer, "OFAC");
vm.prank(consumer);
vm.expectRevert("Redemption: blacklisted");
redemption.redeem(tokenId, storeA);
}
function test_RedeemStoreRestricted() public {
uint256 tokenId = _mintCouponWithStore(consumer, 100e6);
// 使用不在列表中的门店
bytes32 storeC = keccak256("STORE_C");
vm.prank(consumer);
vm.expectRevert("Redemption: store not allowed");
redemption.redeem(tokenId, storeC);
// 使用允许的门店
vm.prank(consumer);
redemption.redeem(tokenId, storeA);
}
function test_TotalRedemptionsIncrement() public {
uint256 tokenId1 = _mintCoupon(consumer, 100e6, false);
uint256 tokenId2 = _mintCoupon(consumer, 200e6, false);
vm.startPrank(consumer);
redemption.redeem(tokenId1, storeA);
redemption.redeem(tokenId2, storeA);
vm.stopPrank();
assertEq(redemption.totalRedemptions(), 2);
}
// --- Helpers ---
function _mintCoupon(address to, uint256 faceValue, bool withStores) internal returns (uint256) {
bytes32[] memory stores;
if (withStores) {
stores = new bytes32[](2);
stores[0] = storeA;
stores[1] = storeB;
} else {
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(admin);
return coupon.mint(to, faceValue, config);
}
function _mintCouponWithStore(address to, uint256 faceValue) internal returns (uint256) {
return _mintCoupon(to, faceValue, true);
}
}