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 28 days 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.