// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract VSampleToken is ERC20, Ownable {
// 規則參數(使用更節省gas的數據類型)
uint256 public constant DAILY_CLAIM_AMOUNT = 100 * 10**18;
uint8 public constant DAILY_CLAIM_LIMIT = 2;
uint256 public constant TOTAL_CLAIM_LIMIT = 1000 * 10**18;
uint256 public constant MIN_RESERVE = 100000 * 10**18;
uint256 public constant MAX_SUPPLY = 1000000 * 10**18;
struct ClaimRecord {
uint32 lastClaimDay;
uint8 dailyClaimCount;
uint96 totalClaimed;
}
mapping(address => ClaimRecord) public claimRecords;
// 事件:成功領取
event Claimed(address indexed user, uint256 amount);
constructor() ERC20("VSampleToken", "VST") Ownable(msg.sender) {
_mint(address(this), MAX_SUPPLY);
}
// 統一余額檢查邏輯
function _checkReserve() private view {
require(
balanceOf(address(this)) >= DAILY_CLAIM_AMOUNT + MIN_RESERVE,
"Insufficient contract reserve"
);
}
function claim() external {
address user = msg.sender;
_checkReserve();
if (user == owner()) {
_transfer(address(this), user, DAILY_CLAIM_AMOUNT);
return;
}
ClaimRecord storage record = claimRecords[user];
uint32 currentDay = uint32(block.timestamp / 1 days);
// 新的一天重置計數器
if (record.lastClaimDay != currentDay) {
record.lastClaimDay = currentDay;
record.dailyClaimCount = 0;
}
require(record.dailyClaimCount < DAILY_CLAIM_LIMIT, "Daily limit reached");
require(
record.totalClaimed + DAILY_CLAIM_AMOUNT <= TOTAL_CLAIM_LIMIT,
"Total claim limit exceeded"
);
// 更新記錄(使用安全類型轉換)
record.dailyClaimCount += 1;
record.totalClaimed += uint96(DAILY_CLAIM_AMOUNT);
_transfer(address(this), user, DAILY_CLAIM_AMOUNT);
emit Claimed(user, DAILY_CLAIM_AMOUNT);
}
function withdrawRemaining(address to, uint256 amount) external onlyOwner {
require(
balanceOf(address(this)) - amount >= MIN_RESERVE,
"Cannot break reserve requirement"
);
_transfer(address(this), to, amount);
}
function getClaimStatus(address user) external view returns (
bool canClaim,
uint8 availableToday,
uint256 remainingTotal
) {
if (user == owner()) {
return (
balanceOf(address(this)) >= DAILY_CLAIM_AMOUNT + MIN_RESERVE,
type(uint8).max,
type(uint256).max
);
}
ClaimRecord memory record = claimRecords[user];
uint32 currentDay = uint32(block.timestamp / 1 days);
availableToday = record.lastClaimDay == currentDay
? DAILY_CLAIM_LIMIT - record.dailyClaimCount
: DAILY_CLAIM_LIMIT;
remainingTotal = TOTAL_CLAIM_LIMIT - record.totalClaimed;
canClaim = (availableToday > 0) &&
(remainingTotal >= DAILY_CLAIM_AMOUNT) &&
(balanceOf(address(this)) >= DAILY_CLAIM_AMOUNT + MIN_RESERVE);
}
}