Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Invalid

DoS via transfer in placeBid function

Summary

The placeBid function uses transfer to refund the previous highest bidder, and if this refund fails, the entire bid transaction is reverted. This flaw allows a malicious bidder to block the auction by making refunds fail.

Vulnerability Details

The placeBid function in the NFTLiquidator contract uses Solidity’s transfer to immediately refund the previous highest bidder.

if (data.highestBidder != address(0)) {
// Refund the previous highest bidder
payable(data.highestBidder).transfer(data.highestBid);
}

This approach violates the security guarantee that critical auction operations must be atomic and not dependent on external contract behavior.

A malicious bidder uses a contract with a fallback function that reverts or consumes excessive gas. When a new bid is placed, the refund attempt fails, causing the entire transaction to revert and thereby blocking any further bids from being accepted.

Impact

I've rated this a Medium since it prevents new bids from being processed, effectively halting the auction process. A malicious bidder who has successfully placed a bid gains the ability to block all subsequent bids, freezing the auction and potentially causing significant financial disruption with minimal effort when a bidder uses a contract programmed to revert on refund.

contract MaliciousBidder {
// Fallback function reverting on any Eth transfer.
fallback() external payable {
revert("Refund rejected");
}
// Function to place a bid on NFTLiquidator.
function placeMaliciousBid(address nftLiquidator, uint256 tokenId) external payable {
// Calls the NFTLiquidator's placeBid function and forces a refund failure on subsequent bids.
(bool success, ) = nftLiquidator.call{value: msg.value}(
abi.encodeWithSignature("placeBid(uint256)", tokenId)
);
require(success, "Bid failed");
}
}

Tools Used

Manual Review

Recommendations

Implement a pull pattern for refunds so failed refunds do not cancel the bid transaction.

// Define a mapping to track pending refund amounts.
mapping(address => uint256) public pendingReturns;
function placeBid(uint256 tokenId) external payable {
TokenData storage data = tokenData[tokenId];
if (block.timestamp >= data.auctionEndTime) revert AuctionHasEnded();
uint256 minBidAmount = data.highestBid + (data.highestBid * minBidIncreasePercentage / 100);
if (msg.value <= minBidAmount) revert BidTooLow(minBidAmount);
// Instead of immediately refunding, record the amount owed.
if (data.highestBidder != address(0)) {
pendingReturns[data.highestBidder] += data.highestBid;
}
data.highestBid = msg.value;
data.highestBidder = msg.sender;
emit BidPlaced(tokenId, msg.sender, msg.value);
}
// Decouple the function allowing users to withdraw their refunds independently.
function withdrawRefund() external {
uint256 amount = pendingReturns[msg.sender];
require(amount > 0, "No refund available");
pendingReturns[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Withdrawal failed");
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

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