Summary
A issue in the auction contract will lead to permanent locking of auction tokens if a user places a bid right at the end of an auction for all or enough spiceToken
without a bid but never calls the claim
function to retrieve their tokens will lock the tokens in the contract forever. This issue prevents recovery of the tokens via the recoverAuctionTokenForZeroBidAuction
and recoverToken
functions, resulting in a loss of functionality and funds.
Vulnerability Details
The claim
function in the auction contract is responsible for allowing users to claim their share of auction tokens based on their bid. However, if a user bids the entire amount of auction tokens and does not call claim
, the tokens remain locked in the contract. The recoverAuctionTokenForZeroBidAuction
function cannot be used to recover these tokens because it checks if the total bid amount for the epoch is zero. Similarly, the recoverToken
function is prevented from recovering unclaimed tokens due to the checks in place.
recoverAuctionTokenForZeroBidAuction
Function
https://github.com/Cyfrin/2024-07-templegold/blob/main/protocol%2Fcontracts%2Ftemplegold%2FSpiceAuction.sol#L275-L283
function recoverAuctionTokenForZeroBidAuction(uint256 epochId, address to) external override onlyDAOExecutor {
if (to == address(0)) { revert CommonEventsAndErrors.InvalidAddress(); }
if (epochId > _currentEpochId) { revert InvalidEpoch(); }
EpochInfo storage epochInfo = epochs[epochId];
if (!epochInfo.hasEnded()) { revert AuctionActive(); }
if (epochInfo.totalBidTokenAmount > 0) { revert InvalidOperation(); }
SpiceAuctionConfig storage config = auctionConfigs[epochId];
(, address auctionToken) = _getBidAndAuctionTokens(config);
uint256 amount = epochInfo.totalAuctionTokenAmount;
_totalAuctionTokenAllocation[auctionToken] -= amount;
emit CommonEventsAndErrors.TokenRecovered(to, auctionToken, amount);
IERC20(auctionToken).safeTransfer(to, amount);
}
This line reverts the operation because there is a bid
https://github.com/Cyfrin/2024-07-templegold/blob/main/protocol%2Fcontracts%2Ftemplegold%2FSpiceAuction.sol#L283
if (epochInfo.totalBidTokenAmount > 0) { revert InvalidOperation(); }
recoverToken
Function
https://github.com/Cyfrin/2024-07-templegold/blob/main/protocol%2Fcontracts%2Ftemplegold%2FSpiceAuction.sol#L234-L238
function recoverToken(
address token,
address to,
uint256 amount
) external override onlyDAOExecutor {
if (to == address(0)) { revert CommonEventsAndErrors.InvalidAddress(); }
if (amount == 0) { revert CommonEventsAndErrors.ExpectedNonZero(); }
if (token != spiceToken && token != templeGold) {
emit CommonEventsAndErrors.TokenRecovered(to, token, amount);
IERC20(token).safeTransfer(to, amount);
return;
}
uint256 epochId = _currentEpochId;
EpochInfo storage info = epochs[epochId];
if (info.startTime == 0) { revert InvalidConfigOperation(); }
if (!info.hasEnded() && auctionConfigs[epochId+1].duration == 0) { revert RemoveAuctionConfig(); }
uint256 totalAuctionTokenAllocation = _totalAuctionTokenAllocation[token];
uint256 balance = IERC20(token).balanceOf(address(this));
uint256 maxRecoverAmount = balance - (totalAuctionTokenAllocation - _claimedAuctionTokens[token]);
if (amount > maxRecoverAmount) { revert CommonEventsAndErrors.InvalidParam(); }
IERC20(token).safeTransfer(to, amount);
emit CommonEventsAndErrors.TokenRecovered(to, token, amount);
}
This restricts recovery of tokens waiting to be claimed
https://github.com/Cyfrin/2024-07-templegold/blob/main/protocol%2Fcontracts%2Ftemplegold%2FSpiceAuction.sol#L261
uint256 maxRecoverAmount = balance - (totalAuctionTokenAllocation - _claimedAuctionTokens[token]);
Impact
This leads to permanent locking of auction tokens and Inability to recover these tokens using the recoverAuctionTokenForZeroBidAuction
and recoverToken
functions.
Tools Used
Manual code review and analysis.
Recommendations
Maybe add a function that allows anyone to trigger the claim on behalf of users who haven’t claimed after a certain period, possibly with an incentive for the caller.