Bid Beasts

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

Insufficient bid increment due to integer truncation (allows zero/tiny overbids)

Root + Impact

  • Root: The required next-bid calculation divides before multiplying, causing integer truncation.

  • Impact: Highest bidder can outbid with less than the intended +5% (even 0 extra in edge cases), breaking auction economics and fairness.

Description

  • Normal behavior:

    Subsequent bids should be at least 5% higher than the current highest bid to prevent tiny/nuisance increments and ensure fair price discovery.

  • Issue:

    The code computes the required amount as (prev / 100) * (100 + 5). Because Solidity does integer division, prev / 100 truncates; for small prev, this becomes 0, and for larger values it underestimates the true 5%, allowing bids that increase by less than 5%.

// In placeBid (regular bidding branch)
uint256 requiredAmount;
// ...
requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE);
// @> Integer division happens first: previousBidAmount / 100 truncates
// @> This reduces or even nullifies the intended +5% increment
require(msg.value >= requiredAmount, "Bid not high enough");

Risk

Likelihood:

  • Reason 1 // Any common bid values not divisible by 100 produce truncation, regularly undercharging the 5% step.

  • Reason 2 // Early-phase bids (small amounts) often result in previousBidAmount / 100 == 0, permitting effectively 0 increment.


Impact:

  • Impact 1 // Auction revenue loss and unfair price discovery; strategic bidders can “creep” the price.

  • Impact 2 // Reputation/UX damage: rules say +5%, enforcement is weaker in practice.


Proof of Concept

below we have some explanation:

// Demonstration:
// prev = 99 wei:
// badRequired = (99/100)*105 = 0*105 = 0 (allows zero-increment outbid)
// goodRequired = 99*105/100 = 103 (requires +4 wei, ~+4.04%)
//
// prev = 1 ether (1e18):
// badRequired = (1e18/100)*105 = 1.05e18 (but loses precision for many amounts <-> weaker enforcement)
// goodRequired = 1e18*105/100 = 1.05e18 (correct step with maximal precision)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
contract MinIncrementTruncationPoC {
uint256 public S_MIN_BID_INCREMENT_PERCENTAGE = 5;
function badRequired(uint256 prev) public view returns (uint256) {
// Vulnerable formula from the market
return (prev / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE);
}
function goodRequired(uint256 prev) public view returns (uint256) {
// Correct: multiply first, then divide
return prev * (100 + S_MIN_BID_INCREMENT_PERCENTAGE) / 100;
}
}

Recommended Mitigation

  • Use multiply-then-divide (or basis points), ensuring full precision before truncation:

// Replace:
requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE);
// With:
requiredAmount = previousBidAmount * (100 + S_MIN_BID_INCREMENT_PERCENTAGE) / 100;
// (Preferred) Using BPS for clarity & flexibility:
uint256 constant MIN_INCREMENT_BPS = 500; // 5%
requiredAmount = previousBidAmount * (10_000 + MIN_INCREMENT_BPS) / 10_000;
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!