188 lines
5.6 KiB
Solidity
188 lines
5.6 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "forge-std/Test.sol";
|
|
import "../src/Redemption1155.sol";
|
|
import "../src/CouponBatch.sol";
|
|
import "../src/Compliance.sol";
|
|
import "../src/interfaces/ICouponBatch.sol";
|
|
|
|
contract Redemption1155Test is Test {
|
|
Redemption1155 redemption;
|
|
CouponBatch batch;
|
|
Compliance compliance;
|
|
|
|
address admin = address(1);
|
|
address factory = address(2);
|
|
address consumer = address(4);
|
|
address issuer = address(5);
|
|
|
|
bytes32 storeA = keccak256("STORE_A");
|
|
bytes32 storeB = keccak256("STORE_B");
|
|
|
|
function setUp() public {
|
|
vm.startPrank(admin);
|
|
|
|
// 部署 Compliance
|
|
compliance = new Compliance();
|
|
compliance.initialize(admin);
|
|
|
|
// 部署 CouponBatch
|
|
batch = new CouponBatch();
|
|
batch.initialize("https://genex.io/api/coupon-batch/{id}.json", admin);
|
|
|
|
// 部署 Redemption1155
|
|
redemption = new Redemption1155();
|
|
redemption.initialize(address(batch), address(compliance), admin);
|
|
|
|
// 角色授权
|
|
batch.grantRole(keccak256("FACTORY_ROLE"), factory);
|
|
batch.grantRole(keccak256("BURNER_ROLE"), address(redemption));
|
|
|
|
compliance.setKycLevel(consumer, 1);
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function test_RedeemSuccess() public {
|
|
uint256 typeId = _mintBatch(consumer, 100e6, 10, false);
|
|
|
|
vm.prank(consumer);
|
|
redemption.redeem(typeId, 3, storeA);
|
|
|
|
// 余额减少
|
|
assertEq(batch.balanceOf(consumer, typeId), 7);
|
|
|
|
// 记录已创建
|
|
Redemption1155.RedemptionRecord memory record = redemption.getRedemption(0);
|
|
assertEq(record.typeId, typeId);
|
|
assertEq(record.amount, 3);
|
|
assertEq(record.consumer, consumer);
|
|
assertEq(record.issuer, issuer);
|
|
assertEq(record.storeId, storeA);
|
|
}
|
|
|
|
function test_RedeemAllUnits() public {
|
|
uint256 typeId = _mintBatch(consumer, 50e6, 5, false);
|
|
|
|
vm.prank(consumer);
|
|
redemption.redeem(typeId, 5, storeA);
|
|
|
|
assertEq(batch.balanceOf(consumer, typeId), 0);
|
|
assertEq(redemption.totalRedemptions(), 1);
|
|
}
|
|
|
|
function test_RedeemMultipleTimes() public {
|
|
uint256 typeId = _mintBatch(consumer, 100e6, 100, false);
|
|
|
|
vm.startPrank(consumer);
|
|
redemption.redeem(typeId, 30, storeA);
|
|
redemption.redeem(typeId, 50, storeA);
|
|
vm.stopPrank();
|
|
|
|
assertEq(batch.balanceOf(consumer, typeId), 20);
|
|
assertEq(redemption.totalRedemptions(), 2);
|
|
|
|
Redemption1155.RedemptionRecord memory r0 = redemption.getRedemption(0);
|
|
assertEq(r0.amount, 30);
|
|
Redemption1155.RedemptionRecord memory r1 = redemption.getRedemption(1);
|
|
assertEq(r1.amount, 50);
|
|
}
|
|
|
|
function test_RedeemZeroAmountReverts() public {
|
|
uint256 typeId = _mintBatch(consumer, 100e6, 10, false);
|
|
|
|
vm.prank(consumer);
|
|
vm.expectRevert("Redemption1155: zero amount");
|
|
redemption.redeem(typeId, 0, storeA);
|
|
}
|
|
|
|
function test_RedeemInsufficientBalanceReverts() public {
|
|
uint256 typeId = _mintBatch(consumer, 100e6, 5, false);
|
|
|
|
vm.prank(consumer);
|
|
vm.expectRevert("Redemption1155: insufficient balance");
|
|
redemption.redeem(typeId, 10, storeA);
|
|
}
|
|
|
|
function test_RedeemBlacklistedReverts() public {
|
|
uint256 typeId = _mintBatch(consumer, 100e6, 10, false);
|
|
|
|
vm.prank(admin);
|
|
compliance.addToBlacklist(consumer, "OFAC");
|
|
|
|
vm.prank(consumer);
|
|
vm.expectRevert("Redemption1155: blacklisted");
|
|
redemption.redeem(typeId, 1, storeA);
|
|
}
|
|
|
|
function test_RedeemExpiredReverts() public {
|
|
uint256 typeId = _mintBatch(consumer, 100e6, 10, false);
|
|
|
|
// 快进到过期
|
|
vm.warp(block.timestamp + 200 days);
|
|
|
|
vm.prank(consumer);
|
|
vm.expectRevert("Redemption1155: coupon expired");
|
|
redemption.redeem(typeId, 1, storeA);
|
|
}
|
|
|
|
function test_RedeemStoreRestricted() public {
|
|
uint256 typeId = _mintBatch(consumer, 100e6, 10, true);
|
|
|
|
// 不在允许门店列表中
|
|
bytes32 storeC = keccak256("STORE_C");
|
|
vm.prank(consumer);
|
|
vm.expectRevert("Redemption1155: store not allowed");
|
|
redemption.redeem(typeId, 1, storeC);
|
|
|
|
// 使用允许的门店
|
|
vm.prank(consumer);
|
|
redemption.redeem(typeId, 1, storeA);
|
|
assertEq(batch.balanceOf(consumer, typeId), 9);
|
|
}
|
|
|
|
function test_RedeemNoBalanceReverts() public {
|
|
uint256 typeId = _mintBatch(issuer, 100e6, 10, false);
|
|
|
|
// consumer 没有该 typeId 的余额
|
|
vm.prank(consumer);
|
|
vm.expectRevert("Redemption1155: insufficient balance");
|
|
redemption.redeem(typeId, 1, storeA);
|
|
}
|
|
|
|
// ==========================================
|
|
// Helpers
|
|
// ==========================================
|
|
|
|
function _mintBatch(
|
|
address to,
|
|
uint256 faceValue,
|
|
uint256 quantity,
|
|
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);
|
|
}
|
|
|
|
ICouponBatch.BatchCouponConfig memory config = ICouponBatch.BatchCouponConfig({
|
|
transferable: true,
|
|
maxPrice: faceValue,
|
|
expiryDate: block.timestamp + 180 days,
|
|
minPurchase: 0,
|
|
stackable: false,
|
|
allowedStores: stores,
|
|
issuer: issuer,
|
|
faceValue: faceValue
|
|
});
|
|
|
|
vm.prank(factory);
|
|
return batch.mint(to, faceValue, quantity, config);
|
|
}
|
|
}
|