Bid Beasts

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

Incorrect Bid Increment Calculation Due to Division Before Multiplication in `BidBeastsNFTMarket::placeBid`

Root + Impact

  • Root Cause: The placeBid function calculates the required bid increment using the formula requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE), performing division before multiplication. In Solidity, integer division truncates decimal results, causing requiredAmount to be lower than intended. This misordering violates the auction’s logic, which aims to enforce a minimum increment (e.g., 5%) over the previous bid.

  • Impact: This vulnerability allows attackers to place bids slightly below the intended increment, leading to under-bids being accepted. This reduces seller revenue, accumulates discrepancies over multiple bids, and results in unfair auction outcomes, undermining the marketplace’s financial integrity and user trust.

Description:

The placeBid function in the BidBeastsNFTMarket contract calculates the required bid increment for subsequent bids using the following formula:

requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE);

This calculation performs division before multiplication, which introduces integer division truncation in Solidity. Since Solidity truncates decimal results during integer division, the requiredAmount is lower than intended, allowing bids that fall below the expected minimum increment. This violates the auction's intended logic, which requires each bid to exceed the previous bid by at least S_MIN_BID_INCREMENT_PERCENTAGE (e.g., 5%).

Risk

  • Likelihood: High. The issue occurs in every bid calculation where previousBidAmount is not perfectly divisible by 100, a common scenario in auctions with varying bid amounts. Automated bidding scripts can exploit this consistently, especially in competitive auctions.

  • Impact: Medium. While not causing direct asset theft, the acceptance of under-bids reduces seller revenue and fairness, with cumulative effects over multiple bids potentially leading to significant financial discrepancies and user dissatisfaction.

Proof of Concept:

  • This test demonstrates an incorrect bid increment calculation in BidBeastsNFTMarket::placeBid.

  • Because division is performed before multiplication, integer truncation lowers the requiredAmount.

  • As a result, bids that are slightly below the intended increment are incorrectly accepted.

  1. Add the following to foundry.toml to configure fuzz testing:

[fuzz]
runs = 100
seed = '0x2'
  1. Add the following to the BidBeastsNFTMarketTest.t.sol test and run:

  • The following fuzz test shows the incorrect calculation compared to the correct one:

Proof of Code
function testFuzz_First_DivisionVsMultiply(uint256 previousBid) public {
// Bound input to useful auction ranges to avoid trivial zero cases
// e.g., ensure >= S_MIN_NFT_PRICE (0.01 ether) and not astronomically huge
vm.assume(previousBid >= 0.01 ether && previousBid <= 100 ether);
uint256 increment = 5; // 5% increment
// Contract used formula (vulnerable ordering)
uint256 wrongCalc = (previousBid / 100) * (100 + increment);
// correct formula (multiply first)
uint256 correctCalc = (previousBid * (100 + increment)) / 100;
console.log("Wrong Calculation:", wrongCalc);
console.log("Correct Calculation:", correctCalc);
// If wrongCalc < correctCalc then integer-division truncation caused under-bid allowance
// We assert that wrongCalc == correctCalc; if not, the fuzzer will try to find a counterexample
assertEq(wrongCalc, correctCalc);
// Logs:
// Wrong Calculation: 41848342096872603810
// Correct Calculation: 41848342096872603910
}

Explanation

  • Setup: The test fuzzes previousBid within a realistic range (0.01 to 100 ether) and uses a 5% increment.

  • Issue: The incorrect formula (previousBid / 100) * 105 truncates the result, while the correct formula (previousBid * 105) / 100 preserves the intended increment.

  • Result: The fuzzer identifies cases (e.g., 0.41848342096872603910 ether) where the wrong calculation (41848342096872603810) is less than the correct one (41848342096872603910), confirming the underestimation.

Recommended Mitigation:

  • Correct the calculation by reordering the operations to multiply before dividing, ensuring accuracy and preventing unintended truncation from integer division:

- requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE);
+ requiredAmount = (previousBidAmount * (100 + S_MIN_BID_INCREMENT_PERCENTAGE)) / 100;
  • Add fuzz tests to confirm requiredAmount is always calculated correctly.

  • For higher precision and long-term safety, consider adopting Math.mulDiv
    (from OpenZeppelin) or an equivalent custom implementation. This ensures precise percentage-based calculations without intermediate rounding errors and improves code readability.

Updates

Lead Judging Commences

cryptoghost Lead Judge about 1 month 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.