Bid Beasts

First Flight #49
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: medium
Valid

Precision Loss in Minimum Bid Increment Calculation

Description

  • For subsequent bids, the contract should enforce a minimum increase of 5% over the previous bid to promote competitive bidding and prevent minimal increments that could prolong auctions without meaningful value addition.

  • The calculation performs division by 100 before multiplication, causing integer flooring that results in a requiredAmount lower than the true 5% increase for previousBidAmounts not divisible by 100, allowing bids that undercut the intended threshold and compromising auction fairness.

} else {
@> requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE);
require(msg.value >= requiredAmount, "Bid not high enough");

Risk

Likelihood:

  • Previous bids end up with amounts not multiples of 100 wei due to arbitrary user inputs

  • Bidders exploit the flooring by submitting the minimal accepted amount below the true increment

Impact:

  • Auctions settle at lower prices than expected, reducing seller proceeds and platform fees

  • Undermines trust in the bidding mechanism, as increments appear inconsistent or unfair

Proof of Concept

Add the following test function into the existing tests in `BidBeastsMarketPlaceTest.t.sol`

function test_PrecisionLossInIncrement() public {
// Step 1: Mint and list an NFT
_mintNFT();
_listNFT();
// Step 2: Place first bid with an amount not multiple of 100
uint256 firstBid = MIN_PRICE + 1 wei; // e.g., 1 ether + 1 wei
vm.prank(BIDDER_1);
market.placeBid{value: firstBid}(TOKEN_ID);
// Verify first bid
assertEq(market.getHighestBid(TOKEN_ID).amount, firstBid, "First bid set");
// Step 3: Calculate expected vs floored min for next bid
uint256 trueMin = (firstBid * 105 + 99) / 100; // Ceiling for exact 5%
uint256 flooredMin = (firstBid / 100) * 105;
// Ensure there's a difference for the test
if (flooredMin < trueMin) {
// Step 4: Place second bid at flooredMin (below true 5%)
vm.prank(BIDDER_2);
market.placeBid{value: flooredMin}(TOKEN_ID);
// Verify accepted despite <5%
BidBeastsNFTMarket.Bid memory highestBid = market.getHighestBid(TOKEN_ID);
assertEq(highestBid.amount, flooredMin, "Accepted bid below true 5% increment");
} else {
fail("Test setup: No precision loss in this amount; adjust firstBid");
}
}

Recommended Mitigation

Reorder the operations to multiply before dividing, minimizing precision loss. For even stricter enforcement, add ceiling logic by incorporating a numerator adjustment to ensure at least the full percentage increase.

} else {
- requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE);
+ requiredAmount = (previousBidAmount * (100 + S_MIN_BID_INCREMENT_PERCENTAGE) + 99) / 100; // With ceiling
require(msg.value >= requiredAmount, "Bid not high enough");
Updates

Lead Judging Commences

cryptoghost Lead Judge 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

BidBeasts Marketplace: Integer Division Precision Loss

Integer division in requiredAmount truncates fractions, allowing bids slightly lower than intended.

Support

FAQs

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

Give us feedback!