Platform: Code4rena
Start Date: 06/12/2022
Pot Size: $36,500 USDC
Total HM: 16
Participants: 119
Period: 3 days
Judge: berndartmueller
Total Solo HM: 2
Id: 189
League: ETH
Rank: 9/119
Findings: 1
Award: $991.64
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: RaymondFam
Also found by: Josiah
991.6428 USDC - $991.64
https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol#L58-L89
The function logic of buy()
in LPDA.sol
can be exploited by shrewd buyers to achieve the lowest finalPrice
possible.
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) { sale.finalPrice = uint80(price); uint256 totalSale = price * amountSold; uint256 fee = totalSale / 20; ISaleFactory(factory).feeReceiver().transfer(fee); temp.saleReceiver.transfer(totalSale - fee); _end(); } }
From the code block above, it is noted that require(newId <= temp.finalId, "TOO MANY");
could be controlled by a crafty buyer who would constantly watch the mempool in the final hour of the sale. Whenever he sees someone trying to mint the remaining number of NFT, he will send in high gas call front-running to mint 1 NFT of the same. Upon successfully minting his 1 NFT, the other transaction will fail because newId
is now equal to temp.FinalId + 1
. He will repeatedly do this while minting his desired NFT one at a time until the endTime
is reached.
As a result, the seller is left with needing to refund more ETH to all buyers and suffering revenue cut. Besides, the sincere buyers attempting to buy the remaining amount of NFT will be left with nothing minted successfully.
Manual
It is recommended refactoring the function logic on line 68 to cater for newId > temp.finalId
such that it will still allow the buyer to mint at a lesser amount so that the if block on line 81 could still get through to end the sale.
#0 - berndartmueller
2022-12-14T10:18:28Z
Closing as invalid.
A user could always use a private relay (e..g flashbots) to submit a buy
transaction to prevent someone else from frontrunning.
Edit: After reading the related submission https://github.com/code-423n4/2022-12-escher-findings/issues/280, I'll revert my decision of closing this submission as invalid. I'll dupe it to #280
#1 - c4-judge
2022-12-14T10:18:34Z
berndartmueller marked the issue as unsatisfactory: Invalid
#2 - c4-judge
2022-12-14T10:36:31Z
berndartmueller marked the issue as satisfactory
#3 - c4-judge
2022-12-14T10:36:51Z
berndartmueller marked the issue as duplicate of #280