Blunt Plastic Raccoon
High
When the distribution period is over, anyone can start the auction which auctions off reserve tokens to cover the coupons needed to payout bondETH holders. Auction::startAuction()
deploys a new auction contract and stores the address in the auctions
mapping using the currentPeriod
as an index. Then bondToken.increaseIndexedAssetPeriod()
increments currentPeriod
:
function startAuction() external whenNotPaused() {
...
auctions[currentPeriod] = Utils.deploy(
address(new Auction()),
abi.encodeWithSelector(
Auction.initialize.selector,
address(couponToken),
address(reserveToken),
couponAmountToDistribute,
block.timestamp + auctionPeriod,
1000,
address(this),
poolSaleLimit
)
);
// Increase the bond token period
@> bondToken.increaseIndexedAssetPeriod(sharesPerToken);
...
}
function increaseIndexedAssetPeriod(uint256 sharesPerToken) public onlyRole(DISTRIBUTOR_ROLE) whenNotPaused() {
globalPool.previousPoolAmounts.push(
PoolAmount({
period: globalPool.currentPeriod,
amount: totalSupply(),
sharesPerToken: globalPool.sharesPerToken
})
);
@> globalPool.currentPeriod++;
...
}
After the auction period is over (e.g. 10 days), endAuction()
determines if the auction was successful for not. If it was successful, it calls Pool.transferReserveToAuction()
:
function endAuction() external auctionExpired whenNotPaused {
...
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);
...
}
The problem is that Pool.transferReserveToAuction()
uses the currentPeriod
to get the address of the auction that is over. But currentPeriod()
was incremented when the auction started so auctions[currentPeriod]
will return address(0)
causing endAuction
to always revert:
function transferReserveToAuction(uint256 amount) external virtual {
(uint256 currentPeriod, ) = bondToken.globalPool();
@> address auctionAddress = auctions[currentPeriod];
@> require(msg.sender == auctionAddress, CallerIsNotAuction());
IERC20(reserveToken).safeTransfer(msg.sender, amount);
}
This breaks the auction component of the protocol.
auctions[currentPeriod]
will always return address(0)
causing the next line to always revert.
function transferReserveToAuction(uint256 amount) external virtual {
(uint256 currentPeriod, ) = bondToken.globalPool();
@> address auctionAddress = auctions[currentPeriod];
@> require(msg.sender == auctionAddress, CallerIsNotAuction());
IERC20(reserveToken).safeTransfer(msg.sender, amount);
}
- Someone calls endAuction() after can auction finishes successfully
n/a
- The distribution period on a pool ends.
- startAuction() is called.
- The auction period ends.
- The auction was successful in obtaining the necessary amount of coupon tokens to payout bondETH holders.
- endAuction() is called, but reverts due to the coding mistake.
Auction participants funds get stuck in auction that can't be ended.
No response
function transferReserveToAuction(uint256 amount) external virtual {
(uint256 currentPeriod, ) = bondToken.globalPool();
- address auctionAddress = auctions[currentPeriod];
+ address auctionAddress = auctions[currentPeriod - 1];
require(msg.sender == auctionAddress, CallerIsNotAuction());
IERC20(reserveToken).safeTransfer(msg.sender, amount);
}