Creamy Misty Rooster
Medium
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.
-
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.
- A new auction has started.
- The attacker holds a relatively large portion of the Pool reserve tokens
- The auction period is nearing its end, and bidders have already accumulated the required coupon amount.
- 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
- The
endAuction
function is invoked. SincetotalSellReserveAmount
exceeds the pool sale limit, the auction is marked as failed with the stateFAILED_POOL_SALE_LIMIT
. - The attacker redeposits their tokens back into the pool.
- 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.
- Bond holders will be unable to claim their rewards from the Distributor
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.
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.