Puppy Raffle

AI First Flight #1
Beginner FriendlyFoundrySolidityNFT
EXP
Submission Details
Impact: low
Likelihood: low

Forced ETH (e.g. `selfdestruct`) permanently bricks `withdrawFees()`

Author Revealed upon completion

Root + Impact

Description

withdrawFees() requires the contract balance to be exactly equal to totalFees. But ETH can be forced into the contract without a receive() function (e.g. via selfdestruct from another contract). A single wei of forced ETH makes the check fail forever, and there is no way to remove it — no sweep function exists.

@> require(address(this).balance == uint256(totalFees), "PuppyRaffle: There are currently players active!");

Risk

Likelihood: Low — it requires a deliberate griefer to force ETH into the contract.

Impact: Low — a griefer can permanently lock all protocol fees (the 20% cut) for 1 wei. No player funds are at risk, but the protocol's revenue is bricked.

Proof of Concept

The test runs a normal raffle (fees accrue to 0.8 ETH), forces 1 wei in via selfdestruct, and shows withdrawFees() reverts forever afterwards.

contract ForceSend {
function destroy(address payable to) external {
selfdestruct(to);
}
}
​
function test_L4_ForcedEthBricksWithdrawFees() public {
_enterPlayers(4, 100);
vm.warp(block.timestamp + duration + 1);
raffle.selectWinner();
assertEq(uint256(raffle.totalFees()), 0.8 ether);
assertEq(address(raffle).balance, 0.8 ether);
​
ForceSend fs = new ForceSend();
vm.deal(address(fs), 1 wei);
fs.destroy(payable(address(raffle))); // 1 wei forced in via selfdestruct
​
assertEq(address(raffle).balance, 0.8 ether + 1 wei);
vm.expectRevert(bytes("PuppyRaffle: There are currently players active!"));
raffle.withdrawFees(); // balance != totalFees forever -> fees locked
}

Recommended Mitigation

Use >= instead of ==, or track fees in a way that does not depend on the exact contract balance.

- require(address(this).balance == uint256(totalFees), "PuppyRaffle: There are currently players active!");
+ require(address(this).balance >= uint256(totalFees), "PuppyRaffle: There are currently players active!");

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!