NFT Dealers

First Flight #58
Beginner FriendlyFoundry
100 EXP
Submission Details
Impact: high
Likelihood: medium

[H-1] ETH Sent to payable Functions Is Permanently Locked

Author Revealed upon completion

Root + Impact

Description

  • mintNft() and buy() are marked payable, but the contract exclusively uses USDC for all payments. There is no receive(), fallback(), or ETH withdrawal function.

  • Any ETH accidentally sent alongside a mintNft() or buy() call is permanently locked in the contract with no recovery mechanism.

// src/NFTDealers.sol
@> function mintNft() external payable onlyWhenRevealed onlyWhitelisted {
...
require(usdc.transferFrom(msg.sender, address(this), lockAmount), "USDC transfer failed");
...
}
@> function buy(uint256 _listingId) external payable {
...
bool success = usdc.transferFrom(msg.sender, address(this), listing.price);
...
}

Risk

Likelihood:

  • Users expecting ETH-based NFT mints (common pattern) send ETH and receive no refund.

  • Wallet UIs that pre-populate value fields can inadvertently include ETH in the call.

Impact:

  • ETH sent to the contract is permanently unrecoverable; there is no withdrawal path.

  • Users lose funds with no on-chain recourse.

Proof of Concept

A whitelisted user sends 1 ETH alongside the USDC mint. The ETH is accepted by the payable function and becomes permanently locked in the contract.

function testEthLockedInMint() public revealed {
vm.prank(owner);
nftDealers.whitelistWallet(userWithCash);
vm.deal(userWithCash, 1 ether);
vm.startPrank(userWithCash);
usdc.approve(address(nftDealers), 20e6);
// Sends 1 ETH alongside the USDC mint — ETH is accepted and locked
nftDealers.mintNft{value: 1 ether}();
vm.stopPrank();
// ETH is stuck — no withdrawal function exists
assertEq(address(nftDealers).balance, 1 ether);
}

Recommended Mitigation

Remove payable from both functions since no ETH payment is required. The contract should reject any ETH sent to it.

-function mintNft() external payable onlyWhenRevealed onlyWhitelisted {
+function mintNft() external onlyWhenRevealed onlyWhitelisted {
-function buy(uint256 _listingId) external payable {
+function buy(uint256 _listingId) external {

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!