Shaggy Clay Cobra
High
The wrong check for Auction contract address in Pool contract can cause the auction unable to be ended successfully
- The function
Pool::transferReserveToAuction()
checks if caller is the Auction contract of the current period. - However, in the function
Pool::startAuction()
, the auction is started with the current period fetched from Bond token contract. And at the end of the function, the Pool contract calls to Bond contract to increase bond token periodbondToken.increaseIndexedAssetPeriod(sharesPerToken)
. This call will cause the callbondToken.globalPool()
in the functionPool::transferReserveToAuction()
to return the new token period, which is different from the period that auction is started. Obviously, the auction for this period is not yet created, and the checkrequire(msg.sender == auctionAddress, CallerIsNotAuction())
will revert.
function startAuction() external whenNotPaused() {
// Check if distribution period has passed
require(lastDistribution + distributionPeriod < block.timestamp, DistributionPeriodNotPassed());
// Check if auction period hasn't passed
require(lastDistribution + distributionPeriod + auctionPeriod >= block.timestamp, AuctionPeriodPassed());
// Check if auction for current period has already started
@> (uint256 currentPeriod,) = bondToken.globalPool();
require(auctions[currentPeriod] == address(0), AuctionAlreadyStarted());
uint8 bondDecimals = bondToken.decimals();
uint8 sharesDecimals = bondToken.SHARES_DECIMALS();
uint8 maxDecimals = bondDecimals > sharesDecimals ? bondDecimals : sharesDecimals;
uint256 normalizedTotalSupply = bondToken.totalSupply().normalizeAmount(bondDecimals, maxDecimals);
uint256 normalizedShares = sharesPerToken.normalizeAmount(sharesDecimals, maxDecimals);
// Calculate the coupon amount to distribute
uint256 couponAmountToDistribute = (normalizedTotalSupply * normalizedShares)
.toBaseUnit(maxDecimals * 2 - IERC20(couponToken).safeDecimals());
@> 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);
// Update last distribution time
lastDistribution = block.timestamp;
}
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);
}
- From the above points, the function
Auction::endAuction()
will be unable to end with SUCCEEDED state because it will revert when callPool::transferReserveToAuction()
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)));
}
No response
No response
- An auction starts
- Bidding happens
- Auction period passes and the auction succeed.
- Call
Auction::endAuction()
reverts and funds can not be settled
- Unable to end auction.
- Bid funds lost in the Auction contract
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);
}