sherlock-audit / 2024-02-radicalxchange-judging

3 stars 1 forks source link

FassiSecurity - Bidder can always win an auction for an amount less than his winning bid #62

Closed sherlock-admin3 closed 8 months ago

sherlock-admin3 commented 8 months ago

FassiSecurity

high

Bidder can always win an auction for an amount less than his winning bid

Summary

As we know, canceling a bid can be done by calling _cancelBid; inside the function it ensures that the bidder is not the highest bidder.

    /**
     * @notice Cancel bid for current round if not highest bidder
     */
    function _cancelBid(
        uint256 tokenId,
        uint256 round,
        address bidder
    ) internal {
        EnglishPeriodicAuctionStorage.Layout
            storage l = EnglishPeriodicAuctionStorage.layout();

        address currentBidder;
        if (IStewardLicense(address(this)).exists(tokenId)) {
            currentBidder = IStewardLicense(address(this)).ownerOf(tokenId);
        } else {
            currentBidder = l.initialBidder;
        }

        require(
 ->           bidder != l.highestBids[tokenId][round].bidder,
            'EnglishPeriodicAuction: Cannot cancel bid if highest bidder'
        );

        Bid storage bid = l.bids[tokenId][round][bidder];

        require(
            bid.collateralAmount > 0,
            'EnglishPeriodicAuction: No bid to cancel'
        );

        // Make collateral available to withdraw
        l.availableCollateral[bidder] += bid.collateralAmount;

        // Reset collateral and bid
        bid.collateralAmount = 0;
        bid.bidAmount = 0;
    }

And as the docs state:

Only bids that are no longer the highest bid can be canceled and their associated collateral withdrawn.

Vulnerability Detail

However, a bidder also has the option to call _cancelAllBids instead.

    /**
     * @notice Cancel bids for all rounds
     */
    function _cancelAllBids(uint256 tokenId, address bidder) internal {
        EnglishPeriodicAuctionStorage.Layout
            storage l = EnglishPeriodicAuctionStorage.layout();

        uint256 currentAuctionRound = l.currentAuctionRound[tokenId];

        for (uint256 i = 0; i <= currentAuctionRound; i++) {
            Bid storage bid = l.bids[tokenId][i][bidder];

            if (bid.collateralAmount > 0) {
                // Make collateral available to withdraw
                l.availableCollateral[bidder] += bid.collateralAmount;

                // Reset collateral and bid
                bid.collateralAmount = 0;
                bid.bidAmount = 0;
            }
        }
    }

In this function, there is no validation to confirm whether the bidder currently is the highest bidder. Consequently, this enables the bidder to bypass the highest bidder check by calling _cancelAllBids rather than _cancelBid.

Impact

With this in mind the following scenario can be done:

Code Snippet

https://github.com/sherlock-audit/2024-02-radicalxchange/blob/459dfbfa73f74ed3422e894f6ff5fe2bbed146dd/pco-art/contracts/auction/EnglishPeriodicAuctionInternal.sol#L413-L434

Tool used

Manual Review

Recommendation

Make sure that when _cancelAllBids gets called it applies the highest bidder check

Duplicate of #14