Bid Beasts

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

Incorrect Bid Increment Calculation Allows Underpaid Bids

Root + Impact

Description

  • Normal Behavior:
    In an auction, the next bid should be calculated using the formula:

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

This ensures a correct percentage-based increment from the last bid.

  • Issue:
    The current implementation uses:

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

This causes rounding errors due to integer division, potentially lowering the required next bid and letting bidders bypass the intended minimum increment.

// Wrong formula
requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE);
@> integer division causes loss of precision

Risk

Likelihood:

  • Occurs whenever previousBidAmount is not perfectly divisible by 100.

  • Happens frequently in ETH amounts (e.g., 0.015 ETH, 1.333 ETH).

Impact:

  • Allows bids lower than the true required increment.

  • Sellers lose money due to under-enforced bidding rules.

Proof of Concept

Example with previousBidAmount = 101 wei, S_MIN_BID_INCREMENT_PERCENTAGE = 5:

  • Expected Required Amount = 101 * 105 / 100 = 106

  • Actual Required Amount = (101 / 100) * 105 = 105

Result: Bidder can underpay by 1 wei, bypassing intended rules.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "forge-std/Test.sol";
import "../src/BidBeasts_NFT_ERC721.sol"; // your market contract
import "../src/BidBeastsNFTMarketPlace.sol"; // your ERC721 contract
contract BidIncrementPrecisionTest is Test {
BidBeasts public nft;
BidBeastsNFTMarket public market;
address public owner = address(0x1);
address seller = address(0x2);
address bidder = address(0x3);
function setUp() public {
vm.startPrank(owner);
// deploy NFT
nft = new BidBeasts();
// deploy market
market = new BidBeastsNFTMarket(address(nft));
// mint NFT to seller
nft.mint(seller);
vm.stopPrank();
// approve market
vm.startPrank(seller);
nft.approve(address(market), 0);
// list NFT with minPrice = 3 ether, buyNow = 5 ether
market.listNFT(0, 3 ether, 5 ether);
vm.stopPrank();
}
function test_BidIncrementCalculation_PrecisionIssue() public {
vm.deal(bidder, 4 ether);
// place first bid = minPrice
vm.prank(bidder);
market.placeBid{value: 3 ether}(0);
// simulate requiredAmount calculation (same as in contract)
uint256 previousBidAmount = 106; // deliberately low to expose rounding issue
uint256 minIncrement = market.S_MIN_BID_INCREMENT_PERCENTAGE(); // = 5
uint256 actualRequired = (previousBidAmount / 100) * (100 + minIncrement);
// expected (float math) = previousBidAmount * 1.05
uint256 expectedRequired = (previousBidAmount * (100 + minIncrement)) / 100;
console.log("Expected Required:", expectedRequired); // = 111
console.log("Actual Required:", actualRequired); // = 106
}
}

Recommended Mitigation

- requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE);
+ requiredAmount = (previousBidAmount * (100 + S_MIN_BID_INCREMENT_PERCENTAGE)) / 100;
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!