Skip to content

Never ending refundable dutch#

Medium Risk

A canvas can be put on refundable dutch auction. When setting up an auction, the seller defines totalQuantity. The auction is considered to be over when the amount of tokens minted matches the canvas.totalQuantity. However, that value can be set so high (intentionally or unintentionally) that the auction never ends. Or there simply is not enough interested buyers. Since the auction is considered as ongoing, funds cannot be withdrawn from contract.

Recommendation#

Allow withdrawing even if totalQuantity is not reached, as long as dutchEndTime is. In that case you can be sure that the end price will equal canvas.dutchEndPrice. So you can take canvas.dutchEndPrice instead of ds.canvasSystem[canvasId].dutchEndPrice (which is set when last token is minted) to calculate how much was earned so far and how much can be refunded. Notice that in such case, it would also be important to track how much was paid out so far. It would be recommended to limit how long an auction can last, otherwise if canvas.dutchEndTime is set too far in the future, this solution will not help.

In payoutAction you can do it like this:

if (canvas.refundableDutch) {
    uint endPrice = ds.canvasSystem[canvasId].dutchEndPrice
    if(endPrice == 0) {
        if(canvas.dutchEndTime > block.timestamp) revert SaleOngoing();
        endPrice = canvas.dutchEndPrice;
    }

    uint tokenIdCounter = ds.canvasSystem[canvasId].tokenIdCounter;
    revenue = (tokenIdCounter - ds.canvasSystem[canvasId].payoutCounter) * endPrice;

    ds.canvasSystem[canvasId].payoutCounter = tokenIdCounter;
    if(tokenIdCounter == canvas.totalQuantity) {
        ds.canvasSystem[canvasId].auctionPayout = true;
    }
}
Notice that ds.canvasSystem[canvasId].payoutCounter is added.

In claimRefundableDutch you can do it like this:

uint endPrice = ds.canvasSystem[canvasId].dutchEndPrice

if(endPrice == 0) {
    if(canvas.dutchEndTime > block.timestamp) revert SaleOngoing();
    endPrice = canvas.dutchEndPrice;
}

uint256 refund = purchaseTracker.spend - (purchaseTracker.quantity * endPrice);