146 lines
4.3 KiB
Solidity
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);
|
|
}
|
|
}
|