Skip to content

Latest commit

 

History

History
84 lines (55 loc) · 4.64 KB

File metadata and controls

84 lines (55 loc) · 4.64 KB

Creamy Misty Rooster

Medium

Attacker can force auctions to fail preventing bond holders from getting rewards

Summary

An attacker with control over a significant portion of the pool's reserve tokens can manipulate auctions to fail. By withdrawing their tokens from the pool just before the auction ends, the attacker triggers the FAILED_POOL_SALE_LIMIT condition. This failure prevents rewards from being distributed for that period, meaning neither the attacker nor any other bondholders will receive coupon rewards for that timeframe.

Root Cause

  • Auction can fail if it exceeds the pool sale limit.

  • Impossible to start a new auction for a given period if the first one failed.

Internal Pre-conditions

  • A new auction has started.

External Pre-conditions

  • The attacker holds a relatively large portion of the Pool reserve tokens

Attack Path

  1. The auction period is nearing its end, and bidders have already accumulated the required coupon amount.
  2. The attacker redeems all their tokens from the Pool contract or a significant portion, reducing the pool balance and triggering the condition:
    totalSellReserveAmount >= (IERC20(sellReserveToken).balanceOf(pool) * poolSaleLimit) / 100
  3. The endAuction function is invoked. Since totalSellReserveAmount exceeds the pool sale limit, the auction is marked as failed with the state FAILED_POOL_SALE_LIMIT.
  4. The attacker redeposits their tokens back into the pool.
  5. Because the auction has failed and cannot be restarted for that period, no rewards are distributed to bondholders.

The attacker can repeat these steps multiple times, preventing all bondholders from claiming coupon rewards for successive distribution periods.

Impact

  • Bond holders will be unable to claim their rewards from the Distributor

PoC

For each distribution period, a new auction is initiated to collect the necessary coupon tokens required to reward bondholders. This process is triggered by calling the startAuction() function. The auction runs for a specific duration (10 days) and can either succeed or fail based on certain conditions defined in the [endAuction](https://github.com/sherlock-audit/2024-12-plaza-finance/blob/main/plaza-evm/src/Auction.sol#L336-L350) function, as shown below:

function endAuction() external auctionExpired whenNotPaused {
    if (state != State.BIDDING) revert AuctionAlreadyEnded();

    if (currentCouponAmount < totalBuyCouponAmount) {
        state = State.FAILED_UNDERSOLD;
    } else if (totalSellReserveAmount >= (IERC20(sellReserveToken).balanceOf(pool) * poolSaleLimit) / 100) {
        state = State.FAILED_POOL_SALE_LIMIT;
    } else {
        state = State.SUCCEEDED;
        Pool(pool).transferReserveToAuction(totalSellReserveAmount);
        IERC20(buyCouponToken).safeTransfer(beneficiary, IERC20(buyCouponToken).balanceOf(address(this)));
    }

    emit AuctionEnded(state, totalSellReserveAmount, totalBuyCouponAmount);
}

The condition relevant to this issue is the second failure scenario, where the amount of reserve tokens to be sold surpasses the pool sale limit (FAILED_POOL_SALE_LIMIT).

Since users can deposit or redeem tokens from the pool even during the auction, the value of IERC20(sellReserveToken).balanceOf(pool) can be manipulated.

An attacker holding a relatively large portion of the pool reserves can exploit this by withdrawing the required amount of tokens from the pool just before the auction ends. This will trigger the FAILED_POOL_SALE_LIMIT condition, forcing the auction to fail.

Once an auction fails, it cannot be restarted for the same distribution period due to the check inside the [startAuction()](https://github.com/sherlock-audit/2024-12-plaza-finance/blob/main/plaza-evm/src/Pool.sol#L530-L532) function:

function startAuction() external whenNotPaused() {
    // Check if distribution period has passed
    require(lastDistribution + distributionPeriod < block.timestamp, DistributionPeriodNotPassed());
}

This ensures that only one auction can occur per distribution period. Consequently, no coupon amounts will be distributed for any period in which the auction fails. This results in a financial loss for all bondholders, as they are unable to claim their rewards for that period.

Mitigation

There is no definitive way to completely prevent this attack, but one of the simplest solutions is to disable deposit and redeem operations during the auction period.