Normal behavior dictates that a protocol exclusively using ERC20 tokens (like USDC) should not accept native ETH, preventing accidental loss of user funds.
The specific issue is that the mintNft and buy functions are marked with the payable modifier, despite the protocol handling all payments via USDC transferFrom. Because there is no withdraw() function implemented for native ETH in the contract, any ETH accidentally sent by a user or a faulty frontend to these functions will be permanently trapped inside the contract.
Solidity
// @> The 'payable' modifier allows the contract to receive ETH, but there is no mechanism to withdraw it.
function mintNft() external payable onlyWhenRevealed onlyWhitelisted {
if (msg.sender == address(0)) revert InvalidAddress();
require(tokenIdCounter < MAX_SUPPLY, "Max supply reached");
require(msg.sender != owner, "Owner can't mint NFTs");
// ...
// @> Same issue here
function buy(uint256 _listingId) external payable {
Listing memory listing = s_listings[_listingId];
if (!listing.isActive) revert ListingNotActive(_listingId);
// ...
Low/Medium. It requires user error or a frontend bug to attach msg.value to the transaction.
Medium. Any native ETH attached to these transactions is permanently lost, as the smart contract lacks a withdrawal mechanism for native currency.
Add the following test to test/NFTDealersPoC.t.sol to prove that ETH gets permanently locked.
Solidity
function test_PoC_EthTrappedInContract() public {
vm.startPrank(attacker);
dealers.mintNft();
dealers.list(1, 100e6);
vm.stopPrank();
Remove the payable modifier from both the mintNft and buy functions. The EVM will naturally revert any transaction that accidentally includes native ETH, protecting user funds.
Diff
function mintNft() external payable onlyWhenRevealed onlyWhitelisted {
function mintNft() external onlyWhenRevealed onlyWhitelisted {
if (msg.sender == address(0)) revert InvalidAddress();
require(tokenIdCounter < MAX_SUPPLY, "Max supply reached");
// ...
function buy(uint256 _listingId) external payable {
function buy(uint256 _listingId) external {
Listing memory listing = s_listings[_listingId];
if (!listing.isActive) revert ListingNotActive(_listingId);
// ...
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.