Multiple functions perform external calls before finalising state updates, violating the Checks-Effects-Interactions (CEI) pattern. A malicious token with transfer hooks can re-enter the contract and drain funds or manipulate the listing state.
buy() calls _safeTransfer before setting s_listings[_listingId].isActive = false, allowing a reentrant call to purchase the same listing again
cancelListing() calls usdc.safeTransfer before zeroing collateralForMinting, allowing a reentrant call to drain collateral multiple times
collectUsdcFromSelling() has no reentrancy guard and makes two consecutive external safeTransfer calls with no state finalization in between
No ReentrancyGuard is applied anywhere in the contract
Likelihood:
Requires a malicious or hook-enabled ERC20 token, or a buyer contract that implements onERC721Received to re-enter
More likely in permissionless or upgradeable token deployments
Impact:
Attacker can buy the same NFT listing multiple times before isActive is set to false
Attacker can drain collateral from cancelListing by re-entering before collateralForMinting is zeroed
Contract USDC balance can be fully drained in a single transaction
Since _safeTransfer calls onERC721Received on the recipient before isActive is set to false, an attacker can deploy a contract that re-enters buy() inside that callback. Each reentrant call sees the listing as still active and successfully purchases the NFT again, draining USDC from the contract on every iteration until the balance is exhausted.
Two complementary fixes should be applied together. First, inherit OpenZeppelin's ReentrancyGuard and apply nonReentrant to all functions that make external calls. Second, restructure each affected function to follow the CEI pattern — all state changes must complete before any external call is made. This ensures that even if a reentrant call is attempted, the state will already reflect the completed operation and the call will revert.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.