Lucene search

K
code423n4Code4renaCODE423N4:2022-12-ESCHER-FINDINGS-ISSUES-457
HistoryDec 09, 2022 - 12:00 a.m.

Funds reserved for refunding users can be steal in LPDA sale

2022-12-0900:00:00
Code4rena
github.com
9
lpda sale
dutch auction
early buyers
refund
funds
vulnerability
buy function
malicious actor
abuse
loss of funds

Lines of code

Vulnerability details

Impact

LPDA sale works like a Dutch Auction, where early buyers will get refund after the sale ended.

In addition, in buy() function, when last NFT is saled, it is automatically ending the LPDA sale and send payments to sale receiver, fee to fee receiver. And there are some funds left in the contract for early buyers calling refund() to get back.

However, malicious actor can abuse this by calling buy() with amount = 0 after the sale is ended. Logic in contract is still counted this as last buy and execute ending process. It will use funds reserved for early users to send to sale receiver and fee receiver.

The impact is obviously loss of funds for early buyers.

Proof of Concept

Function LPDA.buy() did not check amount != 0

function buy(uint256 _amount) external payable {
    uint48 amount = uint48(_amount);
    Sale memory temp = sale;
    IEscher721 nft = IEscher721(temp.edition);
    require(block.timestamp >= temp.startTime, "TOO SOON");
    uint256 price = getPrice();
    require(msg.value >= amount * price, "WRONG PRICE");

    amountSold += amount;
    uint48 newId = amount + temp.currentId;
    require(newId <= temp.finalId, "TOO MANY");

    receipts[msg.sender].amount += amount;
    receipts[msg.sender].balance += uint80(msg.value);

    for (uint256 x = temp.currentId + 1; x <= newId; x++) {
        nft.mint(msg.sender, x);
    }

    sale.currentId = newId;

    emit Buy(msg.sender, amount, msg.value, temp);

    if (newId == temp.finalId) { // @audit can be called multiple times
        sale.finalPrice = uint80(price);
        uint256 totalSale = price * amountSold;
        uint256 fee = totalSale / 20;
        ISaleFactory(factory).feeReceiver().transfer(fee);
        temp.saleReceiver.transfer(totalSale - fee);
        _end();
    }
}

Tools Used

Manual Review

Recommended Mitigation Steps

Adding check for amount != 0 in function buy() for all sales contract.


The text was updated successfully, but these errors were encountered:

All reactions