Extending the state tracking as shown enforces per-day and per-user quotas, neutralizing the Sybil DoS vector.
@@ -10,6 +10,7 @@ contract RaiseBoxFaucet is ERC20, Ownable {
mapping(address => uint256) private lastClaimTime;
mapping(address => bool) private hasClaimedEth;
+ mapping(address => uint256) private dailyClaimsByAddress;
address public faucetClaimer;
@@ -17,6 +18,9 @@ contract RaiseBoxFaucet is ERC20, Ownable {
uint256 public dailyClaimLimit = 100;
+ // Maximum claims per address per day to prevent multi-address attacks
+ uint256 public maxClaimsPerAddressPerDay = 1;
+
//= 1000 * 10 ** 18;
// assuming 18 decimals
uint256 public faucetDrip;
@@ -28,6 +32,8 @@ contract RaiseBoxFaucet is ERC20, Ownable {
uint256 public lastFaucetDripDay;
+ uint256 public lastResetDay;
+
uint256 public dailyDrips;
// Sep Eth drip for first timer claimers = 0.01 ether;
@@ -54,6 +60,7 @@ contract RaiseBoxFaucet is ERC20, Ownable {
sepEthAmountToDrip = sepEthDrip_;
dailySepEthCap = dailySepEthCap_;
+ lastResetDay = block.timestamp / 1 days;
_mint(address(this), INITIAL_SUPPLY); // mint initial supply to contract on deployment
}
@@ -83,6 +90,7 @@ contract RaiseBoxFaucet is ERC20, Ownable {
error RaiseBoxFaucet_OwnerOrZeroOrContractAddressCannotCallClaim();
error RaiseBoxFaucet_DailyClaimLimitReached();
error RaiseBoxFaucet_InsufficientContractBalance();
+ error RaiseBoxFaucet_AddressDailyClaimLimitReached();
// -----------------------------------------------------------------------
// OWNER FUNCTIONS
@@ -117,6 +125,24 @@ contract RaiseBoxFaucet is ERC20, Ownable {
_burn(msg.sender, amountToBurn);
}
+ /// @notice Set maximum claims per address per day
+ /// @dev Helps prevent multi-address DoS attacks
+ /// @param newLimit New maximum claims per address per day
+ function setMaxClaimsPerAddressPerDay(uint256 newLimit) external onlyOwner {
+ require(newLimit > 0, "Limit must be greater than 0");
+ maxClaimsPerAddressPerDay = newLimit;
+ }
+
+ /// @notice Reset daily claim counters manually (emergency function)
+ /// @dev Should only be used in exceptional circumstances
+ function resetDailyCounters() external onlyOwner {
+ uint256 currentDay = block.timestamp / 1 days;
+ lastResetDay = currentDay;
+ lastFaucetDripDay = block.timestamp;
+ dailyClaimCount = 0;
+ // Note: Individual address counters will be reset naturally on next claim
+ }
+
/// @notice Adjust the daily claim limit for the contract
/// @dev Increases or decreases the `dailyClaimLimit` by the given amount
/// @param by The amount to adjust the `dailyClaimLimit` by
@@ -125,6 +151,10 @@ contract RaiseBoxFaucet is ERC20, Ownable {
function adjustDailyClaimLimit(uint256 by, bool increaseClaimLimit) public onlyOwner {
if (increaseClaimLimit) {
dailyClaimLimit += by;
} else {
+ // Prevent owner from setting limit below current daily claim count
+ if (dailyClaimLimit - by < dailyClaimCount) {
+ revert RaiseBoxFaucet_CurrentClaimLimitIsLessThanBy();
+ }
if (by > dailyClaimLimit) {
revert RaiseBoxFaucet_CurrentClaimLimitIsLessThanBy();
}
@@ -148,6 +178,20 @@ contract RaiseBoxFaucet is ERC20, Ownable {
revert RaiseBoxFaucet_OwnerOrZeroOrContractAddressCannotCallClaim();
}
+ // Reset daily counters if a new day has started
+ uint256 currentDay = block.timestamp / 1 days;
+ if (currentDay > lastResetDay) {
+ lastResetDay = currentDay;
+ dailyClaimCount = 0;
+ // lastFaucetDripDay is handled separately below
+ }
+
+ // Check per-address daily limit
+ if (dailyClaimsByAddress[faucetClaimer] >= maxClaimsPerAddressPerDay) {
+ revert RaiseBoxFaucet_AddressDailyClaimLimitReached();
+ }
+
if (balanceOf(address(this)) <= faucetDrip) {
revert RaiseBoxFaucet_InsufficientContractBalance();
}
@@ -193,6 +237,8 @@ contract RaiseBoxFaucet is ERC20, Ownable {
if (block.timestamp > lastFaucetDripDay + 1 days) {
lastFaucetDripDay = block.timestamp;
dailyClaimCount = 0;
+ // Reset per-address claim counts (handled via currentDay check above)
+ delete dailyClaimsByAddress[faucetClaimer];
}
// Effects
lastClaimTime[faucetClaimer] = block.timestamp;
dailyClaimCount++;
+ dailyClaimsByAddress[faucetClaimer]++;
// Interactions
_transfer(address(this), faucetClaimer, faucetDrip);